Skip to content

Commit a6b1741

Browse files
committed
Add support for internal enums via stub generation
This uses persistently malloc()'ed objects, which get duplicated to an request-unique object. Those request-unique objects are supposed to be immutable for the lifetime of the request, i.e. they can be just efree()'d without issues.
1 parent 7bc0dd2 commit a6b1741

17 files changed

+567
-46
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
--TEST--
2+
Internal enums are persistent
3+
--EXTENSIONS--
4+
zend_text
5+
--FILE--
6+
<?php
7+
8+
var_dump(ZendTestStringBacked::FIRST);
9+
var_dump(ZendTestStringBacked::FIRST == ZendTestStringBacked::getFirst());
10+
var_dump(ZendTestStringBacked::FIRST != ZendTestUnbacked::FIRST);
11+
var_dump(ZendTestStringBacked::FIRST->value);
12+
var_dump(ZendTestIntBacked::FIRST->value);
13+
14+
var_dump(ZendTestStringBacked::FIRST instanceof UnitEnum, ZendTestStringBacked::FIRST instanceof BackedEnum);
15+
var_dump(ZendTestUnbacked::FIRST instanceof UnitEnum, ZendTestUnbacked::FIRST instanceof BackedEnum);
16+
17+
var_dump(ZendTestUnbacked::cases());
18+
var_dump(ZendTestStringBacked::cases());
19+
var_dump(ZendTestIntBacked::from(42));
20+
var_dump(ZendTestStringBacked::tryFrom("foo"));
21+
var_dump(ZendTestStringBacked::tryFrom("a"));
22+
23+
?>
24+
--EXPECT--
25+
enum(ZendTestStringBacked::FIRST)
26+
bool(true)
27+
bool(true)
28+
string(1) "a"
29+
int(42)
30+
bool(true)
31+
bool(true)
32+
bool(true)
33+
bool(false)
34+
array(2) {
35+
[0]=>
36+
enum(ZendTestUnbacked::FIRST)
37+
[1]=>
38+
enum(ZendTestUnbacked::SECOND)
39+
}
40+
array(2) {
41+
[0]=>
42+
enum(ZendTestStringBacked::FIRST)
43+
[1]=>
44+
enum(ZendTestStringBacked::SECOND)
45+
}
46+
enum(ZendTestIntBacked::FIRST)
47+
NULL
48+
enum(ZendTestStringBacked::FIRST)

Zend/zend_API.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,8 @@ ZEND_API zend_result zend_get_default_from_internal_arg_info(
715715

716716
END_EXTERN_C()
717717

718+
#include "zend_enum.h"
719+
718720
#if ZEND_DEBUG
719721
#define CHECK_ZVAL_STRING(str) \
720722
ZEND_ASSERT(ZSTR_VAL(str)[ZSTR_LEN(str)] == '\0' && "String is not null-terminated");

Zend/zend_enum.c

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
+----------------------------------------------------------------------+
1717
*/
1818

19+
#define GENERATE_ZEND_ENUM_REGISTRATIONS
20+
1921
#include "zend.h"
2022
#include "zend_API.h"
2123
#include "zend_compile.h"
22-
#include "zend_enum_arginfo.h"
2324
#include "zend_interfaces.h"
25+
#include "zend_inheritance.h"
2426

2527
#define ZEND_ENUM_PROPERTY_ERROR() \
2628
zend_throw_error(NULL, "Enum properties are immutable")
@@ -37,19 +39,22 @@ ZEND_API zend_class_entry *zend_ce_backed_enum;
3739

3840
static zend_object_handlers enum_handlers;
3941

40-
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv)
42+
static void enum_obj_init(zend_object *obj, zend_string *case_name, zval *backing_value_zv)
4143
{
42-
zend_object *zobj = zend_objects_new(ce);
43-
ZVAL_OBJ(result, zobj);
44-
45-
ZVAL_STR_COPY(OBJ_PROP_NUM(zobj, 0), case_name);
44+
ZVAL_STR_COPY(OBJ_PROP_NUM(obj, 0), case_name);
4645
if (backing_value_zv != NULL) {
47-
ZVAL_COPY(OBJ_PROP_NUM(zobj, 1), backing_value_zv);
46+
ZVAL_COPY(OBJ_PROP_NUM(obj, 1), backing_value_zv);
4847
}
4948

50-
zobj->handlers = &enum_handlers;
49+
obj->handlers = &enum_handlers;
50+
}
5151

52-
return zobj;
52+
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv)
53+
{
54+
zend_object *obj = zend_objects_new(ce);
55+
ZVAL_OBJ(result, obj);
56+
enum_obj_init(obj, case_name, backing_value_zv);
57+
return obj;
5358
}
5459

5560
static void zend_verify_enum_properties(zend_class_entry *ce)
@@ -216,7 +221,7 @@ void zend_enum_add_interfaces(zend_class_entry *ce)
216221
}
217222
}
218223

219-
static ZEND_NAMED_FUNCTION(zend_enum_cases_func)
224+
ZEND_API ZEND_NAMED_FUNCTION(zend_enum_cases_func)
220225
{
221226
zend_class_entry *ce = execute_data->func->common.scope;
222227
zend_class_constant *c;
@@ -291,12 +296,12 @@ static void zend_enum_from_base(INTERNAL_FUNCTION_PARAMETERS, bool try)
291296
ZVAL_COPY(return_value, case_zv);
292297
}
293298

294-
static ZEND_NAMED_FUNCTION(zend_enum_from_func)
299+
ZEND_API ZEND_NAMED_FUNCTION(zend_enum_from_func)
295300
{
296301
zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
297302
}
298303

299-
static ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
304+
ZEND_API ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
300305
{
301306
zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
302307
}
@@ -371,3 +376,92 @@ void zend_enum_register_props(zend_class_entry *ce)
371376
zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &value_default_value, ZEND_ACC_PUBLIC, NULL, value_type);
372377
}
373378
}
379+
380+
static const zend_function_entry simple_internal_methods[] = { ZEND_ENUM_ME_END };
381+
static const zend_function_entry backed_internal_methods[] = { ZEND_ENUM_BACKED_ME_END };
382+
383+
ZEND_API zend_class_entry *zend_register_internal_enum(const char *name)
384+
{
385+
return zend_register_internal_backed_enum_ex(name, IS_UNDEF, simple_internal_methods);
386+
}
387+
388+
ZEND_API zend_class_entry *zend_register_internal_enum_ex(const char *name, const zend_function_entry *functions)
389+
{
390+
return zend_register_internal_backed_enum_ex(name, IS_UNDEF, functions);
391+
}
392+
393+
ZEND_API zend_class_entry *zend_register_internal_backed_enum(const char *name, uint32_t backing_type)
394+
{
395+
return zend_register_internal_backed_enum_ex(name, backing_type, backed_internal_methods);
396+
}
397+
398+
ZEND_API zend_class_entry *zend_register_internal_backed_enum_ex(const char *name, uint32_t backing_type, const zend_function_entry *functions)
399+
{
400+
ZEND_ASSERT(backing_type == IS_UNDEF || backing_type == IS_STRING || backing_type == IS_LONG);
401+
402+
zend_class_entry ce_val, *ce;
403+
INIT_CLASS_ENTRY_EX(ce_val, name, strlen(name), functions);
404+
ce = zend_register_internal_class(&ce_val);
405+
406+
ce->ce_flags |= ZEND_ACC_ENUM;
407+
ce->enum_backing_type = backing_type;
408+
ce->backed_enum_table = malloc(sizeof(HashTable));
409+
zend_hash_init(ce->backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 1);
410+
zend_enum_register_props(ce);
411+
zend_do_implement_interface(ce, zend_ce_unit_enum);
412+
if (backing_type != IS_UNDEF) {
413+
zend_do_implement_interface(ce, zend_ce_backed_enum);
414+
}
415+
416+
return ce;
417+
}
418+
419+
static zend_object *add_internal_enum_case(zend_class_entry *ce, zend_string *name, zval *value)
420+
{
421+
zend_object *case_obj = zend_objects_new_persistent(ce);
422+
enum_obj_init(case_obj, name, value);
423+
424+
zval casezv;
425+
ZVAL_OBJ(&casezv, case_obj);
426+
zend_class_constant *c = zend_declare_class_constant_ex(ce, name, &casezv, ZEND_ACC_PUBLIC, NULL);
427+
ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE;
428+
429+
return case_obj;
430+
}
431+
432+
ZEND_API zend_object *zend_add_enum_case_ex(zend_class_entry *ce, zend_string *name)
433+
{
434+
ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF);
435+
436+
return add_internal_enum_case(ce, name, NULL);
437+
}
438+
439+
ZEND_API zend_object *zend_add_enum_case_long_ex(zend_class_entry *ce, zend_string *name, zend_long value)
440+
{
441+
ZEND_ASSERT(ce->enum_backing_type == IS_LONG);
442+
443+
zval namezv;
444+
ZVAL_STR_COPY(&namezv, name);
445+
if (!zend_hash_index_add(ce->backed_enum_table, value, &namezv)) {
446+
ZEND_ASSERT(0 && "Cannot add two enum values with the same backing value");
447+
}
448+
449+
zval zv;
450+
ZVAL_LONG(&zv, value);
451+
return add_internal_enum_case(ce, name, &zv);
452+
}
453+
454+
ZEND_API zend_object *zend_add_enum_case_str_ex(zend_class_entry *ce, zend_string *name, zend_string *str)
455+
{
456+
ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
457+
458+
zval namezv;
459+
ZVAL_STR_COPY(&namezv, name);
460+
if (!zend_hash_add(ce->backed_enum_table, str, &namezv)) {
461+
ZEND_ASSERT(0 && "Cannot add two enum values with the same backing value");
462+
}
463+
464+
zval zv;
465+
ZVAL_STR(&zv, str);
466+
return add_internal_enum_case(ce, name, &zv);
467+
}

Zend/zend_enum.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
#define ZEND_ENUM_H
2121

2222
#include "zend.h"
23+
#include "zend_API.h"
2324
#include "zend_types.h"
25+
#include "zend_enum_arginfo.h"
2426

2527
BEGIN_EXTERN_C()
2628

@@ -47,6 +49,35 @@ static zend_always_inline zval *zend_enum_fetch_case_value(zend_object *zobj)
4749
return OBJ_PROP_NUM(zobj, 1);
4850
}
4951

52+
ZEND_API ZEND_NAMED_FUNCTION(zend_enum_cases_func);
53+
ZEND_API ZEND_NAMED_FUNCTION(zend_enum_from_func);
54+
ZEND_API ZEND_NAMED_FUNCTION(zend_enum_try_from_func);
55+
56+
#define ZEND_ENUM_ME_END \
57+
ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) \
58+
ZEND_FE_END
59+
60+
#define ZEND_ENUM_BACKED_ME_END \
61+
ZEND_NAMED_ME(from, zend_enum_from_func, arginfo_class_BackedEnum_from, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) \
62+
ZEND_NAMED_ME(tryFrom, zend_enum_try_from_func, arginfo_class_BackedEnum_tryFrom, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) \
63+
ZEND_ENUM_ME_END
64+
65+
ZEND_API zend_class_entry *zend_register_internal_enum(const char *name);
66+
ZEND_API zend_class_entry *zend_register_internal_enum_ex(const char *name, const zend_function_entry *functions);
67+
ZEND_API zend_class_entry *zend_register_internal_backed_enum(const char *name, uint32_t backing_type);
68+
ZEND_API zend_class_entry *zend_register_internal_backed_enum_ex(const char *name, uint32_t backing_type, const zend_function_entry *functions);
69+
70+
#define zend_add_enum_case(ce, name) zend_add_enum_case_ex(ce, zend_string_init(name, sizeof(name) - 1, 1))
71+
ZEND_API zend_object *zend_add_enum_case_ex(zend_class_entry *ce, zend_string *name);
72+
73+
#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)
74+
ZEND_API zend_object *zend_add_enum_case_long_ex(zend_class_entry *ce, zend_string *name, zend_long value);
75+
76+
#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)
77+
#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)
78+
#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))
79+
ZEND_API zend_object *zend_add_enum_case_str_ex(zend_class_entry *ce, zend_string *name, zend_string *str);
80+
5081
END_EXTERN_C()
5182

5283
#endif /* ZEND_ENUM_H */

Zend/zend_enum.stub.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<?php
22

3-
/** @generate-class-entries */
3+
/**
4+
* @generate-class-entries
5+
* @generate-functions-guard GENERATE_ZEND_ENUM_REGISTRATIONS
6+
*/
47

58
interface UnitEnum
69
{

Zend/zend_enum_arginfo.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 7092f1d4ba651f077cff37050899f090f00abf22 */
2+
* Stub hash: 25c0672f861c05d8edfedcb58e0deb351a40c4db */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_UnitEnum_cases, 0, 0, IS_ARRAY, 0)
55
ZEND_END_ARG_INFO()
@@ -27,6 +27,8 @@ static const zend_function_entry class_BackedEnum_methods[] = {
2727
ZEND_FE_END
2828
};
2929

30+
#ifdef GENERATE_ZEND_ENUM_REGISTRATIONS
31+
3032
static zend_class_entry *register_class_UnitEnum(void)
3133
{
3234
zend_class_entry ce, *class_entry;
@@ -47,3 +49,5 @@ static zend_class_entry *register_class_BackedEnum(zend_class_entry *class_entry
4749

4850
return class_entry;
4951
}
52+
53+
#endif

Zend/zend_execute_API.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ void init_executor(void) /* {{{ */
185185
EG(persistent_constants_count) = EG(zend_constants)->nNumUsed;
186186
EG(persistent_functions_count) = EG(function_table)->nNumUsed;
187187
EG(persistent_classes_count) = EG(class_table)->nNumUsed;
188+
EG(persistent_objects) = (zend_object *) ecalloc(sizeof(zend_object), zend_objects_persistent_handle_counter);
188189

189190
EG(get_gc_buffer).start = EG(get_gc_buffer).end = EG(get_gc_buffer).cur = NULL;
190191

@@ -431,6 +432,7 @@ void shutdown_executor(void) /* {{{ */
431432
zend_stack_destroy(&EG(user_error_handlers_error_reporting));
432433
zend_stack_destroy(&EG(user_error_handlers));
433434
zend_stack_destroy(&EG(user_exception_handlers));
435+
efree(EG(persistent_objects));
434436
zend_objects_store_destroy(&EG(objects_store));
435437
if (EG(in_autoload)) {
436438
zend_hash_destroy(EG(in_autoload));

Zend/zend_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ struct _zend_executor_globals {
181181
uint32_t persistent_constants_count;
182182
uint32_t persistent_functions_count;
183183
uint32_t persistent_classes_count;
184+
zend_object *persistent_objects;
184185

185186
HashTable *in_autoload;
186187
bool full_tables_cleanup;

Zend/zend_objects.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,19 @@
2626
#include "zend_exceptions.h"
2727
#include "zend_weakrefs.h"
2828

29-
static zend_always_inline void _zend_object_std_init(zend_object *object, zend_class_entry *ce)
29+
ZEND_API uint32_t zend_objects_persistent_handle_counter = 0;
30+
31+
static zend_always_inline void _zend_object_minimal_init(zend_object *object, zend_class_entry *ce)
3032
{
3133
GC_SET_REFCOUNT(object, 1);
3234
GC_TYPE_INFO(object) = GC_OBJECT;
3335
object->ce = ce;
3436
object->properties = NULL;
37+
}
38+
39+
static zend_always_inline void _zend_object_std_init(zend_object *object, zend_class_entry *ce)
40+
{
41+
_zend_object_minimal_init(object, ce);
3542
zend_objects_store_put(object);
3643
if (UNEXPECTED(ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
3744
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)
282289

283290
return new_object;
284291
}
292+
293+
ZEND_API zend_object *zend_objects_new_persistent(zend_class_entry *ce)
294+
{
295+
size_t alloc_size = sizeof(zend_object) + zend_object_properties_size(ce);
296+
zend_object *obj = malloc(alloc_size);
297+
_zend_object_minimal_init(obj, ce);
298+
GC_TYPE_INFO(obj) |= IS_OBJ_PERSISTENT << GC_FLAGS_SHIFT;
299+
obj->handle = zend_objects_persistent_handle_counter;
300+
zend_objects_persistent_handle_counter += (alloc_size - 1) / sizeof(zend_object) + 1;
301+
return obj;
302+
}
303+
304+
ZEND_API zend_object* ZEND_FASTCALL zend_objects_persistent_copy(zend_object *object)
305+
{
306+
ZEND_ASSERT(GC_FLAGS(object) & IS_OBJ_PERSISTENT);
307+
308+
zend_object *target = EG(persistent_objects) + object->handle;
309+
if (UNEXPECTED(GC_REFCOUNT(target) == 0)) { // non-initialized
310+
memcpy(target, object, sizeof(zend_object) + zend_object_properties_size(object->ce));
311+
}
312+
GC_TYPE_INFO(target) &= ~(IS_OBJ_PERSISTENT << GC_FLAGS_SHIFT);
313+
GC_ADDREF(target);
314+
return target;
315+
}

Zend/zend_objects.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@
2323
#include "zend.h"
2424

2525
BEGIN_EXTERN_C()
26+
extern ZEND_API uint32_t zend_objects_persistent_handle_counter;
27+
2628
ZEND_API void ZEND_FASTCALL zend_object_std_init(zend_object *object, zend_class_entry *ce);
2729
ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce);
2830
ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object);
2931

32+
ZEND_API zend_object *zend_objects_new_persistent(zend_class_entry *ce);
33+
ZEND_API zend_object* ZEND_FASTCALL zend_objects_persistent_copy(zend_object *object);
34+
3035
ZEND_API void zend_object_std_dtor(zend_object *object);
3136
ZEND_API void zend_objects_destroy_object(zend_object *object);
3237
ZEND_API zend_object *zend_objects_clone_obj(zend_object *object);

Zend/zend_opcode.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,9 @@ ZEND_API void destroy_zend_class(zval *zv)
439439
}
440440
break;
441441
case ZEND_INTERNAL_CLASS:
442+
if (ce->backed_enum_table) {
443+
zend_hash_release(ce->backed_enum_table);
444+
}
442445
if (ce->default_properties_table) {
443446
zval *p = ce->default_properties_table;
444447
zval *end = p + ce->default_properties_count;

0 commit comments

Comments
 (0)