Skip to content

Conversation

@alexandre-daubois
Copy link
Member

Use zend_update_property() instead of doing things "manually".

@alexandre-daubois alexandre-daubois linked an issue Nov 3, 2025 that may be closed by this pull request
@alexandre-daubois alexandre-daubois marked this pull request as ready for review November 3, 2025 09:02
Copy link
Member

@ndossche ndossche left a comment

Choose a reason for hiding this comment

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

On a phone , so can't check. But you're looking at the property info now, however dynamic properties don't have any. So that means that a dynamically created property $stream would've been overwritten before this patch but now no longer. So that is an unintended behaviour change.

@alexandre-daubois
Copy link
Member Author

Oh alright, I wasn't aware of this subtlety of dynamic properties. Let's refine that.

@bukka
Copy link
Member

bukka commented Nov 3, 2025

Hmm so that would require to someone create the dynamic property in filter first and it could then be used in subsequent calls for stream, right? If so, that sounds like a proper edge case... :)

@ndossche
Copy link
Member

ndossche commented Nov 3, 2025

The easiest way to test this is to create a onCreate method that sets a dynamic property on $this

@ndossche
Copy link
Member

ndossche commented Nov 4, 2025

This will work, except for a pre-existing bug:
When there is an unset or uninit typed property, but it is declared, its existence will be ignored.

It would also be great to try to avoid OBJPROP so that the property table isn't rebuilt, but that isn't critical.

@alexandre-daubois
Copy link
Member Author

alexandre-daubois commented Nov 4, 2025

I tried to implement what you have in mind in 04e0955

@alexandre-daubois alexandre-daubois force-pushed the user-stream-filter-props branch 2 times, most recently from 7208c60 to 04e0955 Compare November 4, 2025 08:33
Comment on lines 153 to 165
if (prop_info) {
/* Declared property (may be uninitialized typed property): always update */
zval stream_zval;
php_stream_to_zval(stream, &stream_zval);
zend_update_property(Z_OBJCE_P(obj), Z_OBJ_P(obj), "stream", sizeof("stream")-1, &stream_zval);
} else {
/* Check if it's a dynamic property */
zval *stream_prop = zend_hash_str_find(Z_OBJPROP_P(obj), "stream", sizeof("stream")-1);
if (stream_prop) {
zval stream_zval;
php_stream_to_zval(stream, &stream_zval);
zend_update_property(Z_OBJCE_P(obj), Z_OBJ_P(obj), "stream", sizeof("stream")-1, &stream_zval);
}
Copy link
Member

Choose a reason for hiding this comment

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

The existing behavior skips magic methods and hooks, in addition to ignoring types and readonly. The updated code doesn't.

I think we should consider this an existing bug, so the update is fine.

In this case, we should also use obj->handlers->has_property() to check for property existence, and simplify to something like this:

	const zend_class_entry *old_scope = EG(fake_scope);
	EG(fake_scope) = Z_OBJCE_P(obj);

    zend_string *name = zend_string_init("stream", strlen("stream"));
	if (Z_OBJHANDLER_P(obj, has_property)(obj, name, ZEND_PROPERTY_EXISTS)) {
		zval stream_zval;
		php_stream_to_zval(stream, &stream_zval);
		zend_update_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, &stream_zval);
    }

    EG(fake_scope) = old_scope;

This way, we have less chances of diverging from normal behavior.

We should also check for exception after this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the details! I tried to implement in 3d5358f

if (stream_prop) {
/* Use direct manipulation to avoid type checking during cleanup.
* We need to break the resource reference regardless of property type constraints. */
convert_to_null(stream_prop);
Copy link
Member

Choose a reason for hiding this comment

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

This needs to be changed as well, as setting a typed property to null may be incorrect. We should use Z_OBJHANDLER_P(obj, unset_property).

Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason you could not use unset_property here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Did the updates iteratively and thought I could push not so long after the first one, but actually I'm struggling a bit to implement it here. Still on it 🙂

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, I wonder if it should be really used. It works on first call, then the property is fully uninitialized. Calling another stream operation breaks because of this (and many tests break when trying to implement it). The current approach "maintains" the property by just setting it to null. I guess we may keep it like this, unless I missed the point?

Copy link
Member

Choose a reason for hiding this comment

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

Ah ok, I was asking because you had marked this as resolved :)

Right, unsetting will not work. Thinking more about it, as the property was assigned with a resource above, it implies that the property has no type hint (or it's mixed). So we can simply assign null here, after all (with the same method as above).

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.

User filters allow breaking typed properties

5 participants