-
Notifications
You must be signed in to change notification settings - Fork 7.8k
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
[RFC] Optional interfaces #17288
Conversation
There was a problem hiding this 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)
The PR misses tests. The implementation looks simple. I don't see problems. |
I had just read the rfc, since PHP8.3 we have the Override attribute if an interface is optional how will this attribute be managed? |
@nielsdos @dstogov I'm trying to understand OPCache and it seems to me that it stores I'm trying to write a test for that. The test is passing, but I'm not sure if OPCache is working there at all. If I add a
So it seems that OPCache is not actually available to that script.? However, I was able to run into the same issue by adding So maybe I'm just doing something wrong with the OPCache testing? Other than that I'm now trying to solve an issue with the automagic |
The problem is that opcache is a dynamically loaded extension, and it won't get loaded unless you pass the right parameters to the |
Thank you very much @nielsdos, now it's working and I've added a check on opcache stats to make sure the cache is used. But... does this imply that the other opcache tests that use |
@tontonsb Huh, looks like a mistake indeed then, because the CLI server uses
I haven't check which ones of these need opcache enabled either, but some probably do.. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see various failures with ./configure --enable-address-sanitizer
& run-test.php --asan
. You can also use run-tests.php -m
for Valgrind. The tests that fail for me are:
Testing interface declaration using the original and alias class name [Zend/tests/class_alias/class_alias_009.phpt]
Enum cannot manually implement BackedEnum [Zend/tests/enum/no-enum-implements-backed-enum.phpt]
ZE2 A class can only implement interfaces [tests/classes/interface_class.phpt]
Enum cannot manually implement UnitEnum [Zend/tests/enum/no-enum-implements-unit-enum.phpt]
Trying to inherit a class in an interface [Zend/tests/inter_05.phpt]
Automatic Stringable implementation participates in variance [Zend/tests/type_declarations/variance/stringable.phpt]
Trying to implement a trait [Zend/tests/traits/error_008.phpt]
Enum no manual cases method [Zend/tests/enum/no-cases.phpt]
implementing a class [Zend/tests/objects/objects_012.phpt]
Enum no manual from method [Zend/tests/enum/no-from.phpt]
implementing the same interface twice [Zend/tests/objects/objects_013.phpt]
Optional interfaces are rechecked on subsequent requests [ext/opcache/tests/optional_interfaces.phpt]
extending the same interface twice [Zend/tests/objects/objects_014.phpt]
GH-7792 (Refer to enum as enum instead of class) [Zend/tests/gh7792_4.phpt]
Adding stringable is not broken with Optional interfaces [Zend/tests/optional_interfaces/stringable.phpt]
I can also have a closer look if you wish.
efree(ce->interface_names); | ||
} | ||
|
||
ce->num_interfaces = num_implementable_interfaces; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that num_interfaces
may diverge from the unliked and linked class, we may need some additional checks when iterating interfaces. E.g. zend_accel_inheritance_cache_find()
, which assumes entry->traits_and_interfaces
and traits_and_interfaces
has the same number of entries.
We need to free interfaces names before we overwrite their counter.
Co-authored-by: Ilija Tovilo <[email protected]>
0ef5fd3
to
d216c35
Compare
Thanks for the thorough review an suggestions @iluuu1994 I've solved the small issues, but I'm not sure what to do about those Besides that I've ran into an issue that the gh9447 test fails — |
When we have a fetch type, the name is "NOT_FQ". In practice, this just omits the leading \.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks reasonable for now. The remaining details can be fixed if and once the RFC passes.
--TEST-- | ||
Only the existing interfaces pass the type checks | ||
--INI-- | ||
zend.exception_ignore_args = On |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: We don't use spaces in the INI section.
@@ -1034,6 +1034,10 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); | |||
#define ZEND_NAME_FQ 0 | |||
#define ZEND_NAME_NOT_FQ 1 | |||
#define ZEND_NAME_RELATIVE 2 | |||
#define ZEND_NAME_QUALIFIED_MASK 0x03 | |||
#define NAME_QUAL(type) (type & ZEND_NAME_QUALIFIED_MASK) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should have some sort of Zend-prefix. At least Z_NAME_EQUAL
. We don't have many non-prefixed macros.
ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION | | ||
(ce->interface_names[i].is_optional ? ZEND_FETCH_CLASS_SILENT : 0)); | ||
|
||
// Optional interfaces are skipped if they don't exist. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please prefer C-style /* */
comments.
if (!iface) { | ||
check_unrecoverable_load_failure(ce); | ||
free_alloca(traits_and_interfaces, use_heap); | ||
return NULL; | ||
} | ||
traits_and_interfaces[ce->num_traits + i] = iface; | ||
traits_and_interfaces[ce->num_traits + num_implementable_interfaces++] = iface; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned previously, I think we should at least set the remaining slots in traits_and_interfaces
to NULL
if they are unused. This will prevent use-of-uninitialized-memory issues in zend_accel_inheritance_cache_find()
and similar places.
@@ -180,14 +180,16 @@ void zend_enum_add_interfaces(zend_class_entry *ce) | |||
|
|||
ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)); | |||
|
|||
ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); | |||
ce->interface_names = erealloc(ce->interface_names, sizeof(zend_interface_name) * ce->num_interfaces); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a change that should definitely be in UPGRADING.INTERNALS — I know of several extensions/prototypes that I have written where this would start silently causing issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It definitely should be mentioned in the UPGRADING.INTERNALS, but the correct solution would nevertheless be the following:
ce->interface_names = erealloc(ce->interface_names, sizeof(zend_interface_name) * ce->num_interfaces); | |
ce->interface_names = safe_erealloc(ce->interface_names, ce->num_interfaces, sizeof(*ce->interface_names), 0); |
Thus not hardcoding the struct name in the first place.
Closing, as the RFC failed. @tontonsb Good work nonetheless, keep at it! |
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:
interface_name_list
andinterface_name
are added to parser.interface_name_list
is used instead ofclass_name_list
forimplements_list
andinterface_extends_list
. Theinterface_name
is a name that can be prefixed by a?
in which case it will set aZEND_CLASS_NAME_OPTIONAL
flag on theattr
of that token.zend_interface_name
is defined and used forinterface_names
. The difference fromzend_class_name
is that thezend_interface_name
also holds a booleanis_optional
which is set according to theZEND_CLASS_NAME_OPTIONAL
flag.ZEND_FETCH_CLASS_SILENT
flag onzend_fetch_class_by_name
if the interface nameis_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.