diff --git a/Zend/Optimizer/pass1.c b/Zend/Optimizer/pass1.c index 1f0dbea5c65a0..d5ea8d13afadc 100644 --- a/Zend/Optimizer/pass1.c +++ b/Zend/Optimizer/pass1.c @@ -210,7 +210,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) break; } } - if (Z_TYPE(c) == IS_CONSTANT_AST) { + if (Z_TYPE(c) == IS_CONSTANT_AST || Z_TYPE(c) == IS_OBJECT) { break; } literal_dtor(&ZEND_OP2_LITERAL(opline)); @@ -274,6 +274,8 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) || Z_TYPE(t) == IS_CONSTANT_AST) { break; } + } else if (Z_TYPE_P(c) == IS_OBJECT) { + break; } else { ZVAL_COPY_OR_DUP(&t, c); } diff --git a/Zend/tests/enum/internal-enum-constant-assignment.phpt b/Zend/tests/enum/internal-enum-constant-assignment.phpt new file mode 100644 index 0000000000000..1b777193448c5 --- /dev/null +++ b/Zend/tests/enum/internal-enum-constant-assignment.phpt @@ -0,0 +1,16 @@ +--TEST-- +Internal enum reassignment to constants preserves identity +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECT-- +enum(ZendTestStringBacked::FIRST) +bool(true) diff --git a/Zend/tests/enum/internal-enum-persistent.phpt b/Zend/tests/enum/internal-enum-persistent.phpt new file mode 100644 index 0000000000000..b999d1503841c --- /dev/null +++ b/Zend/tests/enum/internal-enum-persistent.phpt @@ -0,0 +1,50 @@ +--TEST-- +Internal enums are persistent +--EXTENSIONS-- +zend_test +--FILE-- +value); +var_dump(ZendTestIntBacked::FIRST->value); + +var_dump(ZendTestStringBacked::FIRST instanceof UnitEnum, ZendTestStringBacked::FIRST instanceof BackedEnum); +var_dump(ZendTestUnbacked::FIRST instanceof UnitEnum, ZendTestUnbacked::FIRST instanceof BackedEnum); + +var_dump(ZendTestUnbacked::cases()); +var_dump(ZendTestStringBacked::cases()); +var_dump(ZendTestIntBacked::from(42)); +var_dump(ZendTestStringBacked::tryFrom("foo")); +var_dump(ZendTestStringBacked::tryFrom("a")); + +?> +--EXPECT-- +enum(ZendTestStringBacked::FIRST) +bool(true) +bool(true) +bool(true) +string(1) "a" +int(42) +bool(true) +bool(true) +bool(true) +bool(false) +array(2) { + [0]=> + enum(ZendTestUnbacked::FIRST) + [1]=> + enum(ZendTestUnbacked::SECOND) +} +array(2) { + [0]=> + enum(ZendTestStringBacked::FIRST) + [1]=> + enum(ZendTestStringBacked::SECOND) +} +enum(ZendTestIntBacked::FIRST) +NULL +enum(ZendTestStringBacked::FIRST) diff --git a/Zend/tests/enum/internal-enum-read-from-reflection.phpt b/Zend/tests/enum/internal-enum-read-from-reflection.phpt new file mode 100644 index 0000000000000..bcb359809e60f --- /dev/null +++ b/Zend/tests/enum/internal-enum-read-from-reflection.phpt @@ -0,0 +1,32 @@ +--TEST-- +Internal enums can be safely read from reflection +--EXTENSIONS-- +zend_test +--FILE-- +getCases()); +var_dump($r->getConstant("FIRST") === ZendTestStringBacked::FIRST); +var_dump($r->getConstants()["FIRST"] === ZendTestStringBacked::FIRST); + +?> +--EXPECTF-- +array(2) { + [0]=> + object(ReflectionEnumBackedCase)#%d (2) { + ["name"]=> + string(5) "FIRST" + ["class"]=> + string(20) "ZendTestStringBacked" + } + [1]=> + object(ReflectionEnumBackedCase)#%d (2) { + ["name"]=> + string(6) "SECOND" + ["class"]=> + string(20) "ZendTestStringBacked" + } +} +bool(true) +bool(true) diff --git a/Zend/tests/enum/internal-enum-serialization.phpt b/Zend/tests/enum/internal-enum-serialization.phpt new file mode 100644 index 0000000000000..d0a011564c4b0 --- /dev/null +++ b/Zend/tests/enum/internal-enum-serialization.phpt @@ -0,0 +1,19 @@ +--TEST-- +Internal enums serialization +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECT-- +enum(ZendTestStringBacked::FIRST) +string(34) "E:26:"ZendTestStringBacked:FIRST";" +enum(ZendTestStringBacked::FIRST) +bool(true) diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 76af2a51765a1..3fd691c04fcbb 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -40,6 +40,8 @@ typedef struct _zend_function_entry { uint32_t flags; } zend_function_entry; +#include "zend_enum.h" + typedef struct _zend_fcall_info { size_t size; zval function_name; diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 437fa2f815071..0c63ffc22e16c 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -19,8 +19,10 @@ #include "zend.h" #include "zend_API.h" #include "zend_compile.h" -#include "zend_enum_arginfo.h" #include "zend_interfaces.h" +#include "zend_inheritance.h" +#include "zend_constants.h" +#include "zend_enum_arginfo.h" #define ZEND_ENUM_PROPERTY_ERROR() \ zend_throw_error(NULL, "Enum properties are immutable") @@ -37,19 +39,22 @@ ZEND_API zend_class_entry *zend_ce_backed_enum; static zend_object_handlers enum_handlers; -zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv) +static void enum_obj_init(zend_object *obj, zend_string *case_name, zval *backing_value_zv) { - zend_object *zobj = zend_objects_new(ce); - ZVAL_OBJ(result, zobj); - - ZVAL_STR_COPY(OBJ_PROP_NUM(zobj, 0), case_name); + ZVAL_STR_COPY(OBJ_PROP_NUM(obj, 0), case_name); if (backing_value_zv != NULL) { - ZVAL_COPY(OBJ_PROP_NUM(zobj, 1), backing_value_zv); + ZVAL_COPY(OBJ_PROP_NUM(obj, 1), backing_value_zv); } - zobj->handlers = &enum_handlers; + obj->handlers = &enum_handlers; +} - return zobj; +zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv) +{ + zend_object *obj = zend_objects_new(ce); + ZVAL_OBJ(result, obj); + enum_obj_init(obj, case_name, backing_value_zv); + return obj; } static void zend_verify_enum_properties(zend_class_entry *ce) @@ -371,3 +376,128 @@ void zend_enum_register_props(zend_class_entry *ce) zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &value_default_value, ZEND_ACC_PUBLIC, NULL, value_type); } } + +static const zend_function_entry simple_internal_methods[] = { + ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_FE_END +}; + +static const zend_function_entry backed_internal_methods[] = { + ZEND_NAMED_ME(from, zend_enum_from_func, arginfo_class_BackedEnum_from, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_NAMED_ME(tryFrom, zend_enum_try_from_func, arginfo_class_BackedEnum_tryFrom, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_FE_END +}; + +ZEND_API zend_class_entry *zend_register_internal_enum(const char *name) +{ + return zend_register_internal_backed_enum_ex(name, IS_UNDEF, NULL); +} + +ZEND_API zend_class_entry *zend_register_internal_enum_ex(const char *name, const zend_function_entry *functions) +{ + return zend_register_internal_backed_enum_ex(name, IS_UNDEF, functions); +} + +ZEND_API zend_class_entry *zend_register_internal_backed_enum(const char *name, uint32_t backing_type) +{ + return zend_register_internal_backed_enum_ex(name, backing_type, NULL); +} + +ZEND_API zend_class_entry *zend_register_internal_backed_enum_ex(const char *name, uint32_t backing_type, const zend_function_entry *functions) +{ + ZEND_ASSERT(backing_type == IS_UNDEF || backing_type == IS_STRING || backing_type == IS_LONG); + + zend_class_entry ce_val, *ce; + INIT_CLASS_ENTRY_EX(ce_val, name, strlen(name), functions); + ce = zend_register_internal_class(&ce_val); + + ce->ce_flags |= ZEND_ACC_ENUM; + ce->enum_backing_type = backing_type; + ce->backed_enum_table = malloc(sizeof(HashTable)); + zend_hash_init(ce->backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 1); + zend_enum_register_props(ce); + + zend_register_functions(ce, simple_internal_methods, &ce->function_table, EG(current_module)->type); + zend_do_implement_interface(ce, zend_ce_unit_enum); + if (backing_type != IS_UNDEF) { + zend_register_functions(ce, backed_internal_methods, &ce->function_table, EG(current_module)->type); + zend_do_implement_interface(ce, zend_ce_backed_enum); + } + + return ce; +} + +static zend_object *add_internal_enum_case(zend_class_entry *ce, zend_string *name, zval *value) +{ + zend_object *case_obj = zend_objects_new_persistent(ce); + enum_obj_init(case_obj, name, value); + + zval casezv; + ZVAL_OBJ(&casezv, case_obj); + zend_class_constant *c = zend_declare_class_constant_ex(ce, name, &casezv, ZEND_ACC_PUBLIC, NULL); + ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE; + + zend_string_release(name); + if (value) { + zval_internal_ptr_dtor(value); + } + + return case_obj; +} + +ZEND_API zend_object *zend_add_enum_case_ex(zend_class_entry *ce, zend_string *name) +{ + ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF); + + name = zend_new_interned_string(name); + + zend_object *case_obj = add_internal_enum_case(ce, name, NULL); + + zend_string_release(name); + + return case_obj; +} + +ZEND_API zend_object *zend_add_enum_case_long_ex(zend_class_entry *ce, zend_string *name, zend_long value) +{ + ZEND_ASSERT(ce->enum_backing_type == IS_LONG); + + name = zend_new_interned_string(name); + + zval namezv; + ZVAL_STR_COPY(&namezv, name); + if (!zend_hash_index_add(ce->backed_enum_table, value, &namezv)) { + ZEND_ASSERT(0 && "Cannot add two enum values with the same backing value"); + } + + zval zv; + ZVAL_LONG(&zv, value); + zend_object *case_obj = add_internal_enum_case(ce, name, &zv); + + zend_string_release(name); + + return case_obj; +} + +ZEND_API zend_object *zend_add_enum_case_str_ex(zend_class_entry *ce, zend_string *name, zend_string *str) +{ + ZEND_ASSERT(ce->enum_backing_type == IS_STRING); + + name = zend_new_interned_string(name); + str = zend_new_interned_string(str); + + zval namezv; + ZVAL_STR_COPY(&namezv, name); + if (!zend_hash_add(ce->backed_enum_table, str, &namezv)) { + ZEND_ASSERT(0 && "Cannot add two enum values with the same backing value"); + } + + zval zv; + ZVAL_STR(&zv, str); + zend_object *case_obj = add_internal_enum_case(ce, name, &zv); + + zend_string_release(name); + zend_string_release(str); + + return case_obj; +} diff --git a/Zend/zend_enum.h b/Zend/zend_enum.h index c0abe7311f68a..46e5bef7d108b 100644 --- a/Zend/zend_enum.h +++ b/Zend/zend_enum.h @@ -20,6 +20,7 @@ #define ZEND_ENUM_H #include "zend.h" +#include "zend_API.h" #include "zend_types.h" BEGIN_EXTERN_C() @@ -47,6 +48,22 @@ static zend_always_inline zval *zend_enum_fetch_case_value(zend_object *zobj) return OBJ_PROP_NUM(zobj, 1); } +ZEND_API zend_class_entry *zend_register_internal_enum(const char *name); +ZEND_API zend_class_entry *zend_register_internal_enum_ex(const char *name, const zend_function_entry *functions); +ZEND_API zend_class_entry *zend_register_internal_backed_enum(const char *name, uint32_t backing_type); +ZEND_API zend_class_entry *zend_register_internal_backed_enum_ex(const char *name, uint32_t backing_type, const zend_function_entry *functions); + +#define zend_add_enum_case(ce, name) zend_add_enum_case_ex(ce, zend_string_init(name, sizeof(name) - 1, 1)) +ZEND_API zend_object *zend_add_enum_case_ex(zend_class_entry *ce, zend_string *name); + +#define zend_add_enum_case_long(ce, name, long) zend_add_enum_case_long_ex(ce, zend_string_init(name, sizeof(name) - 1, 1), long) +ZEND_API zend_object *zend_add_enum_case_long_ex(zend_class_entry *ce, zend_string *name, zend_long value); + +#define zend_add_enum_case_string(ce, name, str) zend_add_enum_case_string_ex(ce, zend_string_init(name, sizeof(name) - 1, 1), str) +#define zend_add_enum_case_str(ce, name, str) zend_add_enum_case_str_ex(ce, zend_string_init(name, sizeof(name) - 1, 1), str) +#define zend_add_enum_case_string_ex(ce, name, str) zend_add_enum_case_str_ex(ce, name, zend_string_init(str, sizeof(str) - 1, 1)) +ZEND_API zend_object *zend_add_enum_case_str_ex(zend_class_entry *ce, zend_string *name, zend_string *str); + END_EXTERN_C() #endif /* ZEND_ENUM_H */ diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index a551c5393956e..cdb71c4505d0b 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -185,6 +185,7 @@ void init_executor(void) /* {{{ */ EG(persistent_constants_count) = EG(zend_constants)->nNumUsed; EG(persistent_functions_count) = EG(function_table)->nNumUsed; EG(persistent_classes_count) = EG(class_table)->nNumUsed; + EG(persistent_objects) = (zend_object *) ecalloc(sizeof(zend_object), zend_objects_persistent_handle_counter); EG(get_gc_buffer).start = EG(get_gc_buffer).end = EG(get_gc_buffer).cur = NULL; @@ -431,6 +432,7 @@ void shutdown_executor(void) /* {{{ */ zend_stack_destroy(&EG(user_error_handlers_error_reporting)); zend_stack_destroy(&EG(user_error_handlers)); zend_stack_destroy(&EG(user_exception_handlers)); + efree(EG(persistent_objects)); zend_objects_store_destroy(&EG(objects_store)); if (EG(in_autoload)) { zend_hash_destroy(EG(in_autoload)); diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 4315de5845bbf..6b46d88863621 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -79,13 +79,13 @@ #endif /* GC_INFO layout */ -#define GC_ADDRESS 0x0fffffu -#define GC_COLOR 0x300000u +#define GC_ADDRESS 0x07ffffu +#define GC_COLOR 0x180000u #define GC_BLACK 0x000000u /* must be zero */ -#define GC_WHITE 0x100000u -#define GC_GREY 0x200000u -#define GC_PURPLE 0x300000u +#define GC_WHITE 0x080000u +#define GC_GREY 0x100000u +#define GC_PURPLE 0x180000u /* Debug tracing */ #if ZEND_GC_DEBUG > 1 diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index d1ac6dbacfdbb..f406cec081fcd 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -181,6 +181,7 @@ struct _zend_executor_globals { uint32_t persistent_constants_count; uint32_t persistent_functions_count; uint32_t persistent_classes_count; + zend_object *persistent_objects; HashTable *in_autoload; bool full_tables_cleanup; diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 7e3fa03912a87..11ed585e5fa85 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -26,12 +26,19 @@ #include "zend_exceptions.h" #include "zend_weakrefs.h" -static zend_always_inline void _zend_object_std_init(zend_object *object, zend_class_entry *ce) +ZEND_API uint32_t zend_objects_persistent_handle_counter = 0; + +static zend_always_inline void _zend_object_minimal_init(zend_object *object, zend_class_entry *ce) { GC_SET_REFCOUNT(object, 1); GC_TYPE_INFO(object) = GC_OBJECT; object->ce = ce; object->properties = NULL; +} + +static zend_always_inline void _zend_object_std_init(zend_object *object, zend_class_entry *ce) +{ + _zend_object_minimal_init(object, ce); zend_objects_store_put(object); if (UNEXPECTED(ce->ce_flags & ZEND_ACC_USE_GUARDS)) { ZVAL_UNDEF(object->properties_table + object->ce->default_properties_count); @@ -282,3 +289,27 @@ ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object) return new_object; } + +ZEND_API zend_object *zend_objects_new_persistent(zend_class_entry *ce) +{ + size_t alloc_size = sizeof(zend_object) + zend_object_properties_size(ce); + zend_object *obj = malloc(alloc_size); + _zend_object_minimal_init(obj, ce); + GC_TYPE_INFO(obj) |= IS_OBJ_PERSISTENT << GC_FLAGS_SHIFT; + obj->handle = zend_objects_persistent_handle_counter; + zend_objects_persistent_handle_counter += (alloc_size - 1) / sizeof(zend_object) + 1; + return obj; +} + +ZEND_API zend_object* ZEND_FASTCALL zend_objects_persistent_copy(zend_object *object) +{ + ZEND_ASSERT(GC_FLAGS(object) & IS_OBJ_PERSISTENT); + + zend_object *target = EG(persistent_objects) + object->handle; + if (UNEXPECTED(GC_REFCOUNT(target) == 0)) { // non-initialized + memcpy(target, object, sizeof(zend_object) + zend_object_properties_size(object->ce)); + } + GC_TYPE_INFO(target) &= ~(IS_OBJ_PERSISTENT << GC_FLAGS_SHIFT); + GC_ADDREF(target); + return target; +} diff --git a/Zend/zend_objects.h b/Zend/zend_objects.h index 91d388154dd13..7b6af47162d89 100644 --- a/Zend/zend_objects.h +++ b/Zend/zend_objects.h @@ -23,10 +23,15 @@ #include "zend.h" BEGIN_EXTERN_C() +extern ZEND_API uint32_t zend_objects_persistent_handle_counter; + ZEND_API void ZEND_FASTCALL zend_object_std_init(zend_object *object, zend_class_entry *ce); ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce); ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object); +ZEND_API zend_object *zend_objects_new_persistent(zend_class_entry *ce); +ZEND_API zend_object* ZEND_FASTCALL zend_objects_persistent_copy(zend_object *object); + ZEND_API void zend_object_std_dtor(zend_object *object); ZEND_API void zend_objects_destroy_object(zend_object *object); ZEND_API zend_object *zend_objects_clone_obj(zend_object *object); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0386a43e09cac..c2ba51728f99c 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -439,6 +439,9 @@ ZEND_API void destroy_zend_class(zval *zv) } break; case ZEND_INTERNAL_CLASS: + if (ce->backed_enum_table) { + zend_hash_release(ce->backed_enum_table); + } if (ce->default_properties_table) { zval *p = ce->default_properties_table; zval *end = p + ce->default_properties_count; diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 963362222d0eb..d9b3782ad41d9 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -614,10 +614,10 @@ static zend_always_inline zend_uchar zval_get_type(const zval* pz) { #define GC_TRY_DELREF(p) zend_gc_try_delref(&(p)->gc) #define GC_TYPE_MASK 0x0000000f -#define GC_FLAGS_MASK 0x000003f0 -#define GC_INFO_MASK 0xfffffc00 +#define GC_FLAGS_MASK 0x000007f0 +#define GC_INFO_MASK 0xfffff800 #define GC_FLAGS_SHIFT 0 -#define GC_INFO_SHIFT 10 +#define GC_INFO_SHIFT 11 static zend_always_inline zend_uchar zval_gc_type(uint32_t gc_type_info) { return (gc_type_info & GC_TYPE_MASK); @@ -704,9 +704,10 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define IS_ARRAY_PERSISTENT GC_PERSISTENT /* object flags (zval.value->gc.u.flags) */ -#define IS_OBJ_WEAKLY_REFERENCED GC_PERSISTENT +#define IS_OBJ_PERSISTENT GC_PERSISTENT /* allocated using malloc */ #define IS_OBJ_DESTRUCTOR_CALLED (1<<8) #define IS_OBJ_FREE_CALLED (1<<9) +#define IS_OBJ_WEAKLY_REFERENCED (1<<10) #define OBJ_FLAGS(obj) GC_FLAGS(obj) diff --git a/Zend/zend_variables.c b/Zend/zend_variables.c index 810866a1be23c..8fd076a1cb6da 100644 --- a/Zend/zend_variables.c +++ b/Zend/zend_variables.c @@ -98,8 +98,12 @@ ZEND_API void zval_internal_ptr_dtor(zval *zval_ptr) /* {{{ */ ZEND_ASSERT(!ZSTR_IS_INTERNED(str)); ZEND_ASSERT((GC_FLAGS(str) & IS_STR_PERSISTENT)); free(str); + } else if (Z_TYPE_P(zval_ptr) == IS_OBJECT) { + zend_object *obj = (zend_object *) ref; + ZEND_ASSERT((GC_FLAGS(ref) & IS_OBJ_PERSISTENT)); + free(obj); } else { - zend_error_noreturn(E_CORE_ERROR, "Internal zval's can't be arrays, objects, resources or reference"); + zend_error_noreturn(E_CORE_ERROR, "Internal zval's can't be arrays, resources or reference"); } } } @@ -129,5 +133,7 @@ ZEND_API void ZEND_FASTCALL zval_copy_ctor_func(zval *zvalue) ZEND_ASSERT(!ZSTR_IS_INTERNED(Z_STR_P(zvalue))); CHECK_ZVAL_STRING(Z_STR_P(zvalue)); ZVAL_NEW_STR(zvalue, zend_string_dup(Z_STR_P(zvalue), 0)); + } else if (EXPECTED(Z_TYPE_P(zvalue) == IS_OBJECT)) { + ZVAL_OBJ(zvalue, zend_objects_persistent_copy(Z_OBJ_P(zvalue))); } } diff --git a/build/gen_stub.php b/build/gen_stub.php index 88ece28deb721..3124bd0595bb8 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -8,6 +8,7 @@ use PhpParser\Node\Name; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Enum_; use PhpParser\Node\Stmt\Interface_; use PhpParser\PrettyPrinter\Standard; use PhpParser\PrettyPrinterAbstract; @@ -482,6 +483,33 @@ public function __toString() } } +interface ConstName { + public function __toString(): string; +} + +class ClassConstantName implements ConstName { + /** @var Name */ + public $class; + /** @var string */ + public $const; + + public function __construct(Name $class, string $const) + { + $this->class = $class; + $this->const = $const; + } + + public function getEnumObjectName() + { + return "enum_" . implode('_', $this->class->parts) . "_{$this->const}"; + } + + public function __toString(): string + { + return $this->class->toString() . "::" . $this->const; + } +} + interface FunctionOrMethodName { public function getDeclaration(): string; public function getArgInfoName(): string; @@ -995,6 +1023,99 @@ private function appendMethodSynopsisTypeToElement(DOMDocument $doc, DOMElement } } +class ConstInfo +{ + /** @var ConstName */ + public $name; + /** @var int */ + public $flags; + /** @var Expr|null */ + public $value; + /** @var string|null */ + public $cond; + + const MODIFIER_CASE = 0x1000; + + public function __construct(ConstName $name, int $flags, ?Expr $value, ?string $cond) + { + $this->name = $name; + $this->flags = $flags; + $this->value = $value; + $this->cond = $cond; + } + + public function evaluateValue(&$valueConstant = false) { + if ($this->value === null) { + if (!($this->flags & self::MODIFIER_CASE)) { + throw new Exception("Constant $this->name has no value"); + } + + return null; + } + $evaluator = new ConstExprEvaluator( + function (Expr $expr) use (&$valueConstant) { + if ($expr instanceof Expr\ConstFetch && !($this->flags & self::MODIFIER_CASE)) { + $valueConstant = true; + return null; + } + + throw new Exception("Constant $this->name has an unsupported value"); + } + ); + return $evaluator->evaluateDirectly($this->value); + } + + public function getDeclaration(): string { + $code = ""; + + if ($this->flags & self::MODIFIER_CASE) { + $code .= "static zend_object *{$this->name->getEnumObjectName()};\n"; + } + + return $code; + } + + public function getRegistration(): string { + $code = ""; + + $constName = $this->name->const; + + $valueConstant = false; + $value = $this->evaluateValue($valueConstant); + $type = $value === null ? "undefined" : getType($value); + + if ($valueConstant) { + echo "Skipping code generation for constant $this->name, because it has a constant default value\n"; + return ""; + } + + if ($this->flags & self::MODIFIER_CASE) { + $suffix = ""; + $additionalParams = ""; + switch ($type) { + case "integer": + $suffix = "_long"; + $additionalParams = ", $value"; + break; + + case "string": + $suffix = "_string"; + $additionalParams = ', "' . addcslashes($value, '\\"') . '"'; + break; + } + if ($this->cond) { + $code .= "#if {$this->cond}\n"; + } + $code .= "\t{$this->name->getEnumObjectName()} = zend_add_enum_case$suffix(class_entry, \"{$constName}\"$additionalParams);\n"; + if ($this->cond) { + $code .= "#endif\n"; + } + } + + return $code; + } +} + class PropertyInfo { /** @var PropertyName */ @@ -1180,6 +1301,8 @@ class ClassInfo { /** @var string */ public $type; /** @var string|null */ + public $backingType; + /** @var string|null */ public $alias; /** @var bool */ public $isDeprecated; @@ -1193,12 +1316,15 @@ class ClassInfo { public $propertyInfos; /** @var FuncInfo[] */ public $funcInfos; + /** @var ConstInfo[] */ + public $constInfos; /** * @param Name[] $extends * @param Name[] $implements * @param PropertyInfo[] $propertyInfos * @param FuncInfo[] $funcInfos + * @param ConstInfo[] $constInfos */ public function __construct( Name $name, @@ -1210,7 +1336,8 @@ public function __construct( array $extends, array $implements, array $propertyInfos, - array $funcInfos + array $funcInfos, + array $constInfos ) { $this->name = $name; $this->flags = $flags; @@ -1222,6 +1349,24 @@ public function __construct( $this->implements = $implements; $this->propertyInfos = $propertyInfos; $this->funcInfos = $funcInfos; + $this->constInfos = $constInfos; + + if ($this->type == "enum") { + $this->backingType = "UNDEF"; + foreach ($this->constInfos as $const) { + if ($const->flags & ConstInfo::MODIFIER_CASE) { + switch (gettype($const->evaluateValue())) { + case "integer": + $this->backingType = "LONG"; + break; + case "string": + $this->backingType = "STRING"; + break; + } + break; + } + } + } } public function getRegistration(): string @@ -1239,20 +1384,25 @@ public function getRegistration(): string $code = "static zend_class_entry *register_class_$escapedName(" . (empty($params) ? "void" : implode(", ", $params)) . ")\n"; $code .= "{\n"; - $code .= "\tzend_class_entry ce, *class_entry;\n\n"; - if (count($this->name->parts) > 1) { - $className = $this->name->getLast(); - $namespace = addslashes((string) $this->name->slice(0, -1)); - - $code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n"; + if ($this->type == "enum") { + $escapedStringName = addslashes((string) $this->name); + $code .= "\tzend_class_entry *class_entry = zend_register_internal" . ($this->backingType == "UNDEF" ? "" : "_backed") . "_enum_ex(\"$escapedStringName\", " . ($this->backingType == "UNDEF" ? "" : "IS_$this->backingType, ") . "class_{$escapedName}_methods);\n"; } else { - $code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n"; - } + $code .= "\tzend_class_entry ce, *class_entry;\n\n"; + if (count($this->name->parts) > 1) { + $className = $this->name->getLast(); + $namespace = addslashes((string) $this->name->slice(0, -1)); - if ($this->type === "class" || $this->type === "trait") { - $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n"; - } else { - $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n"; + $code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n"; + } else { + $code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n"; + } + + if ($this->type === "class" || $this->type === "trait") { + $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n"; + } else { + $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n"; + } } if ($this->getFlagsAsString()) { $code .= "\tclass_entry->ce_flags |= " . $this->getFlagsAsString() . ";\n"; @@ -1273,6 +1423,10 @@ function (Name $item) { $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "_", $this->alias) . "\", class_entry);\n"; } + foreach ($this->constInfos as $const) { + $code .= $const->getRegistration(); + } + foreach ($this->propertyInfos as $property) { $code .= $property->getDeclaration(); } @@ -1604,8 +1758,9 @@ function parseProperty( /** * @param PropertyInfo[] $properties * @param FuncInfo[] $methods + * @param ConstInfo[] $constants */ -function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $methods): ClassInfo { +function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $methods, array $constants): ClassInfo { $flags = $class instanceof Class_ ? $class->flags : 0; $comment = $class->getDocComment(); $alias = null; @@ -1635,19 +1790,22 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $implements = $class->implements; } elseif ($class instanceof Interface_) { $extends = $class->extends; + } elseif ($class instanceof Enum_) { + $implements = $class->implements; } return new ClassInfo( $name, $flags, - $class instanceof Class_ ? "class" : ($class instanceof Interface_ ? "interface" : "trait"), + $class instanceof Class_ ? "class" : ($class instanceof Interface_ ? "interface" : ($class instanceof Enum_ ? "enum" : "trait")), $alias, $isDeprecated, $isStrictProperties, $extends, $implements, $properties, - $methods + $methods, + $constants ); } @@ -1725,13 +1883,14 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac $className = $stmt->namespacedName; $propertyInfos = []; $methodInfos = []; + $constantInfos = []; foreach ($stmt->stmts as $classStmt) { $cond = handlePreprocessorConditions($conds, $classStmt); if ($classStmt instanceof Stmt\Nop) { continue; } - if (!$classStmt instanceof Stmt\ClassMethod && !$classStmt instanceof Stmt\Property) { + if (!$classStmt instanceof Stmt\ClassMethod && !$classStmt instanceof Stmt\Property && !$classStmt instanceof Stmt\EnumCase) { throw new Exception("Not implemented {$classStmt->getType()}"); } @@ -1740,12 +1899,15 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac $classFlags = $stmt->flags; } - $flags = $classStmt->flags; + $flags = 0; + if (!$classStmt instanceof Stmt\EnumCase) { + $flags = $classStmt->flags; + } if ($stmt instanceof Stmt\Interface_) { $flags |= Class_::MODIFIER_ABSTRACT; } - if (!($flags & Class_::VISIBILITY_MODIFIER_MASK)) { + if (!($flags & Class_::VISIBILITY_MODIFIER_MASK) && !$classStmt instanceof Stmt\EnumCase) { throw new Exception("Visibility modifier is required"); } @@ -1768,10 +1930,17 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac $classStmt, $cond ); + } else if ($classStmt instanceof Stmt\EnumCase) { + $constantInfos[] = new ConstInfo( + new ClassConstantName($className, $classStmt->name->toString()), + ConstInfo::MODIFIER_CASE, + $classStmt->expr, + $cond + ); } } - $fileInfo->classInfos[] = parseClass($className, $stmt, $propertyInfos, $methodInfos); + $fileInfo->classInfos[] = parseClass($className, $stmt, $propertyInfos, $methodInfos, $constantInfos); continue; } @@ -2013,6 +2182,13 @@ function generateClassEntryCode(FileInfo $fileInfo): string { $code = ""; foreach ($fileInfo->classInfos as $class) { + $declarationCode = ""; + foreach ($class->constInfos as $constant) { + $declarationCode .= $constant->getDeclaration(); + } + if ($declarationCode) { + $code .= "\n$declarationCode"; + } $code .= "\n" . $class->getRegistration(); } @@ -2277,7 +2453,7 @@ function initPhpParser() { } $isInitialized = true; - $version = "4.9.0"; + $version = "4.11.0"; $phpParserDir = __DIR__ . "/PHP-Parser-$version"; if (!is_dir($phpParserDir)) { installPhpParser($version, $phpParserDir); diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 7b53c96ff116b..dbf19b421a958 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -1388,7 +1388,7 @@ object ":" uiv ":" ["] { } } ZEND_ASSERT(Z_TYPE_P(value) == IS_OBJECT); - ZVAL_COPY(rval, value); + ZVAL_COPY_OR_DUP(rval, value); return 1; diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 0fc76b149f233..8b7c976757560 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -294,6 +294,18 @@ void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t ta } } +static ZEND_METHOD(ZendTestUnbacked, showName) +{ + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_COPY(zend_enum_fetch_case_name(Z_OBJ_P(getThis()))); +} + +static ZEND_METHOD(ZendTestStringBacked, getFirst) +{ + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_OBJ_COPY(zend_objects_persistent_copy(enum_ZendTestStringBacked_FIRST)); +} + static ZEND_METHOD(_ZendTestClass, __toString) { ZEND_PARSE_PARAMETERS_NONE(); @@ -384,6 +396,10 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_ns2_foo_class = register_class_ZendTestNS2_Foo(); zend_test_ns2_ns_foo_class = register_class_ZendTestNS2_ZendSubNS_Foo(); + register_class_ZendTestIntBacked(); + register_class_ZendTestStringBacked(); + register_class_ZendTestUnbacked(); + // Loading via dl() not supported with the observer API if (type != MODULE_TEMPORARY) { REGISTER_INI_ENTRIES(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 3571556d238ca..c68f34f53b2e1 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -44,6 +44,25 @@ final class ZendTestAttribute { } +enum ZendTestUnbacked { + case FIRST; + case SECOND; + + public function showName(): string {} +} + +enum ZendTestStringBacked: string { + case FIRST = "a"; + case SECOND = "bc"; + + public static function getFirst(): ZendTestStringBacked {} +} + +enum ZendTestIntBacked: int { + case FIRST = 42; + case SECOND = -42; +} + function zend_test_array_return(): array {} function zend_test_nullable_array_return(): ?array {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index d4d77e125dd8d..a87354dca0f54 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 70374ed7b55604eb98e85148d7ff19e79258ce92 */ + * Stub hash: 7a76a82242a168597397e7ae7f3533d648a6fefc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -71,6 +71,11 @@ ZEND_END_ARG_INFO() #define arginfo_class__ZendTestTrait_testMethod arginfo_ZendTestNS2_ZendSubNS_namespaced_func +#define arginfo_class_ZendTestUnbacked_showName arginfo_class__ZendTestClass___toString + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ZendTestStringBacked_getFirst, 0, 0, ZendTestStringBacked, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ZendTestNS_Foo_method arginfo_zend_test_void_return #define arginfo_class_ZendTestNS2_Foo_method arginfo_zend_test_void_return @@ -98,6 +103,8 @@ static ZEND_METHOD(_ZendTestClass, returnsStatic); static ZEND_METHOD(_ZendTestClass, returnsThrowable); static ZEND_METHOD(_ZendTestChildClass, returnsThrowable); static ZEND_METHOD(_ZendTestTrait, testMethod); +static ZEND_METHOD(ZendTestUnbacked, showName); +static ZEND_METHOD(ZendTestStringBacked, getFirst); static ZEND_METHOD(ZendTestNS_Foo, method); static ZEND_METHOD(ZendTestNS2_Foo, method); static ZEND_METHOD(ZendTestNS2_ZendSubNS_Foo, method); @@ -153,6 +160,23 @@ static const zend_function_entry class_ZendTestAttribute_methods[] = { }; +static const zend_function_entry class_ZendTestUnbacked_methods[] = { + ZEND_ME(ZendTestUnbacked, showName, arginfo_class_ZendTestUnbacked_showName, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_ZendTestStringBacked_methods[] = { + ZEND_ME(ZendTestStringBacked, getFirst, arginfo_class_ZendTestStringBacked_getFirst, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_ZendTestIntBacked_methods[] = { + ZEND_FE_END +}; + + static const zend_function_entry class_ZendTestNS_Foo_methods[] = { ZEND_ME(ZendTestNS_Foo, method, arginfo_class_ZendTestNS_Foo_method, ZEND_ACC_PUBLIC) ZEND_FE_END @@ -268,6 +292,42 @@ static zend_class_entry *register_class_ZendTestAttribute(void) return class_entry; } +static zend_object *enum_ZendTestUnbacked_FIRST; +static zend_object *enum_ZendTestUnbacked_SECOND; + +static zend_class_entry *register_class_ZendTestUnbacked(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum_ex("ZendTestUnbacked", class_ZendTestUnbacked_methods); + enum_ZendTestUnbacked_FIRST = zend_add_enum_case(class_entry, "FIRST"); + enum_ZendTestUnbacked_SECOND = zend_add_enum_case(class_entry, "SECOND"); + + return class_entry; +} + +static zend_object *enum_ZendTestStringBacked_FIRST; +static zend_object *enum_ZendTestStringBacked_SECOND; + +static zend_class_entry *register_class_ZendTestStringBacked(void) +{ + zend_class_entry *class_entry = zend_register_internal_backed_enum_ex("ZendTestStringBacked", IS_STRING, class_ZendTestStringBacked_methods); + enum_ZendTestStringBacked_FIRST = zend_add_enum_case_string(class_entry, "FIRST", "a"); + enum_ZendTestStringBacked_SECOND = zend_add_enum_case_string(class_entry, "SECOND", "bc"); + + return class_entry; +} + +static zend_object *enum_ZendTestIntBacked_FIRST; +static zend_object *enum_ZendTestIntBacked_SECOND; + +static zend_class_entry *register_class_ZendTestIntBacked(void) +{ + zend_class_entry *class_entry = zend_register_internal_backed_enum_ex("ZendTestIntBacked", IS_LONG, class_ZendTestIntBacked_methods); + enum_ZendTestIntBacked_FIRST = zend_add_enum_case_long(class_entry, "FIRST", 42); + enum_ZendTestIntBacked_SECOND = zend_add_enum_case_long(class_entry, "SECOND", -42); + + return class_entry; +} + static zend_class_entry *register_class_ZendTestNS_Foo(void) { zend_class_entry ce, *class_entry;