|
30 | 30 | #include "zend_attributes.h" |
31 | 31 | #include "zend_enum.h" |
32 | 32 | #include "zend_interfaces.h" |
| 33 | +#include "zend_inheritance.h" |
33 | 34 | #include "zend_weakrefs.h" |
34 | 35 | #include "Zend/Optimizer/zend_optimizer.h" |
35 | 36 | #include "Zend/zend_alloc.h" |
@@ -61,6 +62,7 @@ static zend_class_entry *zend_test_attribute; |
61 | 62 | static zend_class_entry *zend_test_repeatable_attribute; |
62 | 63 | static zend_class_entry *zend_test_parameter_attribute; |
63 | 64 | static zend_class_entry *zend_test_property_attribute; |
| 65 | +static zend_class_entry *zend_test_attribute_add_interface; |
64 | 66 | static zend_class_entry *zend_test_attribute_with_arguments; |
65 | 67 | static zend_class_entry *zend_test_class_with_method_with_parameter_attribute; |
66 | 68 | static zend_class_entry *zend_test_child_class_with_method_with_parameter_attribute; |
@@ -913,6 +915,95 @@ void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t ta |
913 | 915 | } |
914 | 916 | } |
915 | 917 |
|
| 918 | +/** |
| 919 | + * Recursive handler to check if a class implements the custom casting |
| 920 | + * interface, either directly or via inheritance. |
| 921 | + * |
| 922 | + * Technically this shouldn't be needed since attribute validators run before |
| 923 | + * interfaces are added and classes are linked, but better safe than sorry |
| 924 | + */ |
| 925 | +static bool check_class_has_interface(zend_class_entry *scope) { |
| 926 | + // Might be *linked* (so already have class entries) but not *resolved*, |
| 927 | + // since that waits until the inherited parent class is resolved |
| 928 | + if (scope->ce_flags & ZEND_ACC_LINKED) { |
| 929 | + for (uint32_t iii = 0; iii < scope->num_interfaces; iii++) { |
| 930 | + if (scope->interfaces[iii] == zend_test_interface) { |
| 931 | + return true; |
| 932 | + } |
| 933 | + } |
| 934 | + } else { |
| 935 | + for (uint32_t iii = 0; iii < scope->num_interfaces; iii++) { |
| 936 | + if (zend_string_equals_literal( |
| 937 | + scope->interface_names[iii].lc_name, |
| 938 | + "_zendtestinterface" |
| 939 | + )) { |
| 940 | + // Interface was added manually be the developer |
| 941 | + return true; |
| 942 | + } |
| 943 | + } |
| 944 | + } |
| 945 | + zend_class_entry *parent = NULL; |
| 946 | + if (scope->ce_flags & ZEND_ACC_LINKED) { |
| 947 | + if (scope->parent == NULL) { |
| 948 | + return false; |
| 949 | + } |
| 950 | + parent = scope->parent; |
| 951 | + } else if (scope->parent_name == NULL) { |
| 952 | + return false; |
| 953 | + } else { |
| 954 | + parent = zend_lookup_class_ex( |
| 955 | + scope->parent_name, |
| 956 | + NULL, |
| 957 | + ZEND_FETCH_CLASS_ALLOW_UNLINKED |
| 958 | + ); |
| 959 | + } |
| 960 | + if (parent == NULL) { |
| 961 | + // Invalid class to extend? Leave that up to normal PHP to deal with |
| 962 | + return false; |
| 963 | + } |
| 964 | + return check_class_has_interface(parent); |
| 965 | +} |
| 966 | + |
| 967 | +void zend_attribute_validate_add_interface(zend_attribute *attr, uint32_t target, zend_class_entry *scope) |
| 968 | +{ |
| 969 | + if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { |
| 970 | + return; |
| 971 | + } |
| 972 | + if (scope->ce_flags & (ZEND_ACC_ENUM|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT)) { |
| 973 | + zend_error_noreturn(E_ERROR, "Only classes can be marked with #[ZendTestAttributeAddsInterface]"); |
| 974 | + } |
| 975 | + if (check_class_has_interface(scope)) { |
| 976 | + return; |
| 977 | + } |
| 978 | + if (scope->ce_flags & ZEND_ACC_LINKED) { |
| 979 | + // There is already a method to add interfaces |
| 980 | + zend_do_implement_interface(scope, zend_test_interface); |
| 981 | + return; |
| 982 | + } |
| 983 | + |
| 984 | + // Add the interface automatically to the list |
| 985 | + const uint32_t interfaceIdx = scope->num_interfaces; |
| 986 | + scope->num_interfaces++; |
| 987 | + |
| 988 | + zend_class_name *newInterfaceSet = safe_erealloc( |
| 989 | + scope->interface_names, |
| 990 | + scope->num_interfaces, |
| 991 | + sizeof(*newInterfaceSet), |
| 992 | + 0 |
| 993 | + ); |
| 994 | + newInterfaceSet[interfaceIdx].name = zend_string_init( |
| 995 | + "_ZendTestInterface", |
| 996 | + strlen("_ZendTestInterface"), |
| 997 | + 0 |
| 998 | + ); |
| 999 | + newInterfaceSet[interfaceIdx].lc_name = zend_string_init( |
| 1000 | + "_zendtestinterface", |
| 1001 | + strlen("_zendtestinterface"), |
| 1002 | + 0 |
| 1003 | + ); |
| 1004 | + scope->interface_names = newInterfaceSet; |
| 1005 | +} |
| 1006 | + |
916 | 1007 | static ZEND_METHOD(_ZendTestClass, __toString) |
917 | 1008 | { |
918 | 1009 | ZEND_PARSE_PARAMETERS_NONE(); |
@@ -1296,6 +1387,12 @@ PHP_MINIT_FUNCTION(zend_test) |
1296 | 1387 | zend_test_property_attribute = register_class_ZendTestPropertyAttribute(); |
1297 | 1388 | zend_mark_internal_attribute(zend_test_property_attribute); |
1298 | 1389 |
|
| 1390 | + zend_test_attribute_add_interface = register_class_ZendTestAttributeAddsInterface(); |
| 1391 | + { |
| 1392 | + zend_internal_attribute *attr = zend_mark_internal_attribute(zend_test_attribute_add_interface); |
| 1393 | + attr->validator = zend_attribute_validate_add_interface; |
| 1394 | + } |
| 1395 | + |
1299 | 1396 | zend_test_attribute_with_arguments = register_class_ZendTestAttributeWithArguments(); |
1300 | 1397 | zend_mark_internal_attribute(zend_test_attribute_with_arguments); |
1301 | 1398 |
|
|
0 commit comments