Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Optional interfaces #17288

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft

Conversation

tontonsb
Copy link

@tontonsb tontonsb commented Dec 28, 2024

This is an implementation attempt of https://wiki.php.net/rfc/optional-interfaces

It's my first attempt at modifying PHP sources, so I am very open to any suggestions and guidance on how this should be actually solved. But for now I'll briefly explain my approach:

  • Types interface_name_list and interface_name are added to parser. interface_name_list is used instead of class_name_list for implements_list and interface_extends_list. The interface_name is a name that can be prefixed by a ? in which case it will set a ZEND_CLASS_NAME_OPTIONAL flag on the attr of that token.
  • The struct zend_interface_name is defined and used for interface_names. The difference from zend_class_name is that the zend_interface_name also holds a boolean is_optional which is set according to the ZEND_CLASS_NAME_OPTIONAL flag.
  • The inheritance mechanism uses the ZEND_FETCH_CLASS_SILENT flag on zend_fetch_class_by_name if the interface name is_optional. In case an optional interface fails to fetch, it's just skipped.

I'm not really sure what should be done for Opcache here and I haven't yet understood how to work with booleans there. I haven't looked into Reflection at all yet.

@tontonsb tontonsb requested a review from dstogov as a code owner December 28, 2024 20:11
@tontonsb tontonsb changed the title Add syntax for optional interfaces [RFC] Optional interfaces Dec 28, 2024
@tontonsb tontonsb marked this pull request as draft December 28, 2024 20:54
Copy link
Member

@nielsdos nielsdos left a comment

Choose a reason for hiding this comment

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

As you're asking for opcache help, this should resolve some questions and fix the tests (hopefully)

if (iface) {
UPDATE_IS_CACHEABLE(iface);
}
}
}
ce->num_interfaces = num_implementable_interfaces;
Copy link
Member

@nielsdos nielsdos Dec 29, 2024

Choose a reason for hiding this comment

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

This line is the reason your tests are segfaulting, this could be an immutable class entry. You may only modify non-immutable and non cached ce's. Move this line to after the lazy class loading code in the zend_try down below.
e.g. right before if (CG(unlinked_uses)) { (probably).

Copy link
Member

@nielsdos nielsdos Dec 29, 2024

Choose a reason for hiding this comment

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

I also wonder what caching semantics will play in the following scenario:

  1. You do a request and an optional interface is not available, this class is cached without that interface.
  2. A future request comes in, and reuses the cache, but since this may be via a different code path it's possible that the interface is now available. Yet because the class was cached, this won't implement the interface now even though it's available at this point in time. I'm not entirely sure if this is a real problem but it should be checked. I also don't know what assumptions in the engine this may break.

@@ -1048,8 +1048,9 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce)
for (i = 0; i < ce->num_interfaces; i++) {
zend_accel_store_interned_string(ce->interface_names[i].name);
zend_accel_store_interned_string(ce->interface_names[i].lc_name);
// TODO: should we store .is_optional here? How?
Copy link
Member

Choose a reason for hiding this comment

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

No special handling is needed, the bool is copied by zend_shared_memdup_free down below.

@@ -803,6 +803,7 @@ static void zend_file_cache_serialize_class(zval *zv,
for (i = 0; i < ce->num_interfaces; i++) {
SERIALIZE_STR(interface_names[i].name);
SERIALIZE_STR(interface_names[i].lc_name);
// SERIALIZE_BOOL(interface_names[i].is_optional) ?
Copy link
Member

Choose a reason for hiding this comment

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

No special handling is needed, this is implicitly handled. The pointers need special handling to change their offsets, but the bool is simply a primitive.

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.

2 participants