1 /*
2 +----------------------------------------------------------------------+
3 | Zend Engine |
4 +----------------------------------------------------------------------+
5 | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 2.00 of the Zend license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.zend.com/license/2_00.txt. |
11 | If you did not receive a copy of the Zend license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@zend.com so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Ilija Tovilo <ilutov@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 #include "zend.h"
20 #include "zend_API.h"
21 #include "zend_compile.h"
22 #include "zend_enum_arginfo.h"
23 #include "zend_interfaces.h"
24 #include "zend_enum.h"
25 #include "zend_extensions.h"
26 #include "zend_observer.h"
27
28 #define ZEND_ENUM_DISALLOW_MAGIC_METHOD(propertyName, methodName) \
29 do { \
30 if (ce->propertyName) { \
31 zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include magic method %s", ZSTR_VAL(ce->name), methodName); \
32 } \
33 } while (0);
34
35 ZEND_API zend_class_entry *zend_ce_unit_enum;
36 ZEND_API zend_class_entry *zend_ce_backed_enum;
37
38 static zend_object_handlers enum_handlers;
39
zend_enum_new(zval * result,zend_class_entry * ce,zend_string * case_name,zval * backing_value_zv)40 zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv)
41 {
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);
46 if (backing_value_zv != NULL) {
47 ZVAL_COPY(OBJ_PROP_NUM(zobj, 1), backing_value_zv);
48 }
49
50 zobj->handlers = &enum_handlers;
51
52 return zobj;
53 }
54
zend_verify_enum_properties(zend_class_entry * ce)55 static void zend_verify_enum_properties(zend_class_entry *ce)
56 {
57 zend_property_info *property_info;
58
59 ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property_info) {
60 if (zend_string_equals_literal(property_info->name, "name")) {
61 continue;
62 }
63 if (
64 ce->enum_backing_type != IS_UNDEF
65 && zend_string_equals_literal(property_info->name, "value")
66 ) {
67 continue;
68 }
69 // FIXME: File/line number for traits?
70 zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include properties",
71 ZSTR_VAL(ce->name));
72 } ZEND_HASH_FOREACH_END();
73 }
74
zend_verify_enum_magic_methods(zend_class_entry * ce)75 static void zend_verify_enum_magic_methods(zend_class_entry *ce)
76 {
77 // Only __get, __call and __invoke are allowed
78
79 ZEND_ENUM_DISALLOW_MAGIC_METHOD(constructor, "__construct");
80 ZEND_ENUM_DISALLOW_MAGIC_METHOD(destructor, "__destruct");
81 ZEND_ENUM_DISALLOW_MAGIC_METHOD(clone, "__clone");
82 ZEND_ENUM_DISALLOW_MAGIC_METHOD(__get, "__get");
83 ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, "__set");
84 ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, "__unset");
85 ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, "__isset");
86 ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString");
87 ZEND_ENUM_DISALLOW_MAGIC_METHOD(__debugInfo, "__debugInfo");
88 ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, "__serialize");
89 ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, "__unserialize");
90
91 const char *forbidden_methods[] = {
92 "__sleep",
93 "__wakeup",
94 "__set_state",
95 };
96
97 uint32_t forbidden_methods_length = sizeof(forbidden_methods) / sizeof(forbidden_methods[0]);
98 for (uint32_t i = 0; i < forbidden_methods_length; ++i) {
99 const char *forbidden_method = forbidden_methods[i];
100
101 if (zend_hash_str_exists(&ce->function_table, forbidden_method, strlen(forbidden_method))) {
102 zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include magic method %s", ZSTR_VAL(ce->name), forbidden_method);
103 }
104 }
105 }
106
zend_verify_enum_interfaces(zend_class_entry * ce)107 static void zend_verify_enum_interfaces(zend_class_entry *ce)
108 {
109 if (zend_class_implements_interface(ce, zend_ce_serializable)) {
110 zend_error_noreturn(E_COMPILE_ERROR,
111 "Enum %s cannot implement the Serializable interface", ZSTR_VAL(ce->name));
112 }
113 }
114
zend_verify_enum(zend_class_entry * ce)115 void zend_verify_enum(zend_class_entry *ce)
116 {
117 zend_verify_enum_properties(ce);
118 zend_verify_enum_magic_methods(ce);
119 zend_verify_enum_interfaces(ce);
120 }
121
zend_implement_unit_enum(zend_class_entry * interface,zend_class_entry * class_type)122 static int zend_implement_unit_enum(zend_class_entry *interface, zend_class_entry *class_type)
123 {
124 if (class_type->ce_flags & ZEND_ACC_ENUM) {
125 return SUCCESS;
126 }
127
128 zend_error_noreturn(E_ERROR, "Non-enum class %s cannot implement interface %s",
129 ZSTR_VAL(class_type->name),
130 ZSTR_VAL(interface->name));
131
132 return FAILURE;
133 }
134
zend_implement_backed_enum(zend_class_entry * interface,zend_class_entry * class_type)135 static int zend_implement_backed_enum(zend_class_entry *interface, zend_class_entry *class_type)
136 {
137 if (!(class_type->ce_flags & ZEND_ACC_ENUM)) {
138 zend_error_noreturn(E_ERROR, "Non-enum class %s cannot implement interface %s",
139 ZSTR_VAL(class_type->name),
140 ZSTR_VAL(interface->name));
141 return FAILURE;
142 }
143
144 if (class_type->enum_backing_type == IS_UNDEF) {
145 zend_error_noreturn(E_ERROR, "Non-backed enum %s cannot implement interface %s",
146 ZSTR_VAL(class_type->name),
147 ZSTR_VAL(interface->name));
148 return FAILURE;
149 }
150
151 return SUCCESS;
152 }
153
zend_register_enum_ce(void)154 void zend_register_enum_ce(void)
155 {
156 zend_ce_unit_enum = register_class_UnitEnum();
157 zend_ce_unit_enum->interface_gets_implemented = zend_implement_unit_enum;
158
159 zend_ce_backed_enum = register_class_BackedEnum(zend_ce_unit_enum);
160 zend_ce_backed_enum->interface_gets_implemented = zend_implement_backed_enum;
161
162 memcpy(&enum_handlers, &std_object_handlers, sizeof(zend_object_handlers));
163 enum_handlers.clone_obj = NULL;
164 enum_handlers.compare = zend_objects_not_comparable;
165 }
166
zend_enum_add_interfaces(zend_class_entry * ce)167 void zend_enum_add_interfaces(zend_class_entry *ce)
168 {
169 uint32_t num_interfaces_before = ce->num_interfaces;
170
171 ce->num_interfaces++;
172 if (ce->enum_backing_type != IS_UNDEF) {
173 ce->num_interfaces++;
174 }
175
176 ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES));
177
178 ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces);
179
180 ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_unit_enum->name);
181 ce->interface_names[num_interfaces_before].lc_name = zend_string_init("unitenum", sizeof("unitenum") - 1, 0);
182
183 if (ce->enum_backing_type != IS_UNDEF) {
184 ce->interface_names[num_interfaces_before + 1].name = zend_string_copy(zend_ce_backed_enum->name);
185 ce->interface_names[num_interfaces_before + 1].lc_name = zend_string_init("backedenum", sizeof("backedenum") - 1, 0);
186 }
187 }
188
zend_enum_build_backed_enum_table(zend_class_entry * ce)189 zend_result zend_enum_build_backed_enum_table(zend_class_entry *ce)
190 {
191 ZEND_ASSERT(ce->ce_flags & ZEND_ACC_ENUM);
192 ZEND_ASSERT(ce->type == ZEND_USER_CLASS);
193
194 uint32_t backing_type = ce->enum_backing_type;
195 ZEND_ASSERT(backing_type != IS_UNDEF);
196
197 HashTable *backed_enum_table = emalloc(sizeof(HashTable));
198 zend_hash_init(backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 0);
199 zend_class_set_backed_enum_table(ce, backed_enum_table);
200
201 zend_string *enum_class_name = ce->name;
202
203 zend_string *name;
204 zval *val;
205 ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(CE_CONSTANTS_TABLE(ce), name, val) {
206 zend_class_constant *c = Z_PTR_P(val);
207 if ((ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE) == 0) {
208 continue;
209 }
210
211 zval *c_value = &c->value;
212 zval *case_name = zend_enum_fetch_case_name(Z_OBJ_P(c_value));
213 zval *case_value = zend_enum_fetch_case_value(Z_OBJ_P(c_value));
214
215 if (ce->enum_backing_type != Z_TYPE_P(case_value)) {
216 zend_type_error("Enum case type %s does not match enum backing type %s",
217 zend_get_type_by_const(Z_TYPE_P(case_value)),
218 zend_get_type_by_const(ce->enum_backing_type));
219 goto failure;
220 }
221
222 if (ce->enum_backing_type == IS_LONG) {
223 zend_long long_key = Z_LVAL_P(case_value);
224 zval *existing_case_name = zend_hash_index_find(backed_enum_table, long_key);
225 if (existing_case_name) {
226 zend_throw_error(NULL, "Duplicate value in enum %s for cases %s and %s",
227 ZSTR_VAL(enum_class_name),
228 Z_STRVAL_P(existing_case_name),
229 ZSTR_VAL(name));
230 goto failure;
231 }
232 Z_TRY_ADDREF_P(case_name);
233 zend_hash_index_add_new(backed_enum_table, long_key, case_name);
234 } else {
235 ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
236 zend_string *string_key = Z_STR_P(case_value);
237 zval *existing_case_name = zend_hash_find(backed_enum_table, string_key);
238 if (existing_case_name != NULL) {
239 zend_throw_error(NULL, "Duplicate value in enum %s for cases %s and %s",
240 ZSTR_VAL(enum_class_name),
241 Z_STRVAL_P(existing_case_name),
242 ZSTR_VAL(name));
243 goto failure;
244 }
245 Z_TRY_ADDREF_P(case_name);
246 zend_hash_add_new(backed_enum_table, string_key, case_name);
247 }
248 } ZEND_HASH_FOREACH_END();
249
250 return SUCCESS;
251
252 failure:
253 zend_hash_release(backed_enum_table);
254 zend_class_set_backed_enum_table(ce, NULL);
255 return FAILURE;
256 }
257
ZEND_NAMED_FUNCTION(zend_enum_cases_func)258 static ZEND_NAMED_FUNCTION(zend_enum_cases_func)
259 {
260 zend_class_entry *ce = execute_data->func->common.scope;
261 zend_class_constant *c;
262
263 ZEND_PARSE_PARAMETERS_NONE();
264
265 array_init(return_value);
266
267 ZEND_HASH_MAP_FOREACH_PTR(CE_CONSTANTS_TABLE(ce), c) {
268 if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) {
269 continue;
270 }
271 zval *zv = &c->value;
272 if (Z_TYPE_P(zv) == IS_CONSTANT_AST) {
273 if (zval_update_constant_ex(zv, c->ce) == FAILURE) {
274 RETURN_THROWS();
275 }
276 }
277 Z_ADDREF_P(zv);
278 zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), zv);
279 } ZEND_HASH_FOREACH_END();
280 }
281
zend_enum_get_case_by_value(zend_object ** result,zend_class_entry * ce,zend_long long_key,zend_string * string_key,bool try_from)282 ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try_from)
283 {
284 if (ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
285 if (zend_update_class_constants(ce) == FAILURE) {
286 return FAILURE;
287 }
288 }
289
290 HashTable *backed_enum_table = CE_BACKED_ENUM_TABLE(ce);
291 if (!backed_enum_table) {
292 goto not_found;
293 }
294
295 zval *case_name_zv;
296 if (ce->enum_backing_type == IS_LONG) {
297 case_name_zv = zend_hash_index_find(backed_enum_table, long_key);
298 } else {
299 ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
300 ZEND_ASSERT(string_key != NULL);
301 case_name_zv = zend_hash_find(backed_enum_table, string_key);
302 }
303
304 if (case_name_zv == NULL) {
305 not_found:
306 if (try_from) {
307 *result = NULL;
308 return SUCCESS;
309 }
310
311 if (ce->enum_backing_type == IS_LONG) {
312 zend_value_error(ZEND_LONG_FMT " is not a valid backing value for enum %s", long_key, ZSTR_VAL(ce->name));
313 } else {
314 ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
315 zend_value_error("\"%s\" is not a valid backing value for enum %s", ZSTR_VAL(string_key), ZSTR_VAL(ce->name));
316 }
317 return FAILURE;
318 }
319
320 // TODO: We might want to store pointers to constants in backed_enum_table instead of names,
321 // to make this lookup more efficient.
322 ZEND_ASSERT(Z_TYPE_P(case_name_zv) == IS_STRING);
323 zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), Z_STR_P(case_name_zv));
324 ZEND_ASSERT(c != NULL);
325 zval *case_zv = &c->value;
326 if (Z_TYPE_P(case_zv) == IS_CONSTANT_AST) {
327 if (zval_update_constant_ex(case_zv, c->ce) == FAILURE) {
328 return FAILURE;
329 }
330 }
331
332 *result = Z_OBJ_P(case_zv);
333 return SUCCESS;
334 }
335
zend_enum_from_base(INTERNAL_FUNCTION_PARAMETERS,bool try_from)336 static void zend_enum_from_base(INTERNAL_FUNCTION_PARAMETERS, bool try_from)
337 {
338 zend_class_entry *ce = execute_data->func->common.scope;
339 bool release_string = false;
340 zend_string *string_key = NULL;
341 zend_long long_key = 0;
342
343 if (ce->enum_backing_type == IS_LONG) {
344 ZEND_PARSE_PARAMETERS_START(1, 1)
345 Z_PARAM_LONG(long_key)
346 ZEND_PARSE_PARAMETERS_END();
347 } else {
348 ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
349
350 if (ZEND_ARG_USES_STRICT_TYPES()) {
351 ZEND_PARSE_PARAMETERS_START(1, 1)
352 Z_PARAM_STR(string_key)
353 ZEND_PARSE_PARAMETERS_END();
354 } else {
355 // We allow long keys so that coercion to string doesn't happen implicitly. The JIT
356 // skips deallocation of params that don't require it. In the case of from/tryFrom
357 // passing int to from(int|string) looks like no coercion will happen, so the JIT
358 // won't emit a dtor call. Thus we allocate/free the string manually.
359 ZEND_PARSE_PARAMETERS_START(1, 1)
360 Z_PARAM_STR_OR_LONG(string_key, long_key)
361 ZEND_PARSE_PARAMETERS_END();
362
363 if (string_key == NULL) {
364 release_string = true;
365 string_key = zend_long_to_str(long_key);
366 }
367 }
368 }
369
370 zend_object *case_obj;
371 if (zend_enum_get_case_by_value(&case_obj, ce, long_key, string_key, try_from) == FAILURE) {
372 goto throw;
373 }
374
375 if (case_obj == NULL) {
376 ZEND_ASSERT(try_from);
377 goto return_null;
378 }
379
380 if (release_string) {
381 zend_string_release(string_key);
382 }
383 RETURN_OBJ_COPY(case_obj);
384
385 throw:
386 if (release_string) {
387 zend_string_release(string_key);
388 }
389 RETURN_THROWS();
390
391 return_null:
392 if (release_string) {
393 zend_string_release(string_key);
394 }
395 RETURN_NULL();
396 }
397
ZEND_NAMED_FUNCTION(zend_enum_from_func)398 static ZEND_NAMED_FUNCTION(zend_enum_from_func)
399 {
400 zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
401 }
402
ZEND_NAMED_FUNCTION(zend_enum_try_from_func)403 static ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
404 {
405 zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
406 }
407
zend_enum_register_func(zend_class_entry * ce,zend_known_string_id name_id,zend_internal_function * zif)408 static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) {
409 zend_string *name = ZSTR_KNOWN(name_id);
410 zif->type = ZEND_INTERNAL_FUNCTION;
411 zif->module = EG(current_module);
412 zif->scope = ce;
413 zif->T = ZEND_OBSERVER_ENABLED;
414 if (EG(active)) { // at run-time
415 ZEND_MAP_PTR_INIT(zif->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size()));
416 } else {
417 ZEND_MAP_PTR_NEW(zif->run_time_cache);
418 }
419
420 if (!zend_hash_add_ptr(&ce->function_table, name, zif)) {
421 zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(name));
422 }
423 }
424
zend_enum_register_funcs(zend_class_entry * ce)425 void zend_enum_register_funcs(zend_class_entry *ce)
426 {
427 const uint32_t fn_flags =
428 ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED;
429 zend_internal_function *cases_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
430 cases_function->handler = zend_enum_cases_func;
431 cases_function->function_name = ZSTR_KNOWN(ZEND_STR_CASES);
432 cases_function->fn_flags = fn_flags;
433 cases_function->arg_info = (zend_internal_arg_info *) (arginfo_class_UnitEnum_cases + 1);
434 zend_enum_register_func(ce, ZEND_STR_CASES, cases_function);
435
436 if (ce->enum_backing_type != IS_UNDEF) {
437 zend_internal_function *from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
438 from_function->handler = zend_enum_from_func;
439 from_function->function_name = ZSTR_KNOWN(ZEND_STR_FROM);
440 from_function->fn_flags = fn_flags;
441 from_function->num_args = 1;
442 from_function->required_num_args = 1;
443 from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_from + 1);
444 zend_enum_register_func(ce, ZEND_STR_FROM, from_function);
445
446 zend_internal_function *try_from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
447 try_from_function->handler = zend_enum_try_from_func;
448 try_from_function->function_name = ZSTR_KNOWN(ZEND_STR_TRYFROM);
449 try_from_function->fn_flags = fn_flags;
450 try_from_function->num_args = 1;
451 try_from_function->required_num_args = 1;
452 try_from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_tryFrom + 1);
453 zend_enum_register_func(ce, ZEND_STR_TRYFROM_LOWERCASE, try_from_function);
454 }
455 }
456
zend_enum_register_props(zend_class_entry * ce)457 void zend_enum_register_props(zend_class_entry *ce)
458 {
459 ce->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES;
460
461 zval name_default_value;
462 ZVAL_UNDEF(&name_default_value);
463 zend_type name_type = ZEND_TYPE_INIT_CODE(IS_STRING, 0, 0);
464 zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_NAME), &name_default_value, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY, NULL, name_type);
465
466 if (ce->enum_backing_type != IS_UNDEF) {
467 zval value_default_value;
468 ZVAL_UNDEF(&value_default_value);
469 zend_type value_type = ZEND_TYPE_INIT_CODE(ce->enum_backing_type, 0, 0);
470 zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &value_default_value, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY, NULL, value_type);
471 }
472 }
473
474 static const zend_function_entry unit_enum_methods[] = {
475 ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
476 ZEND_FE_END
477 };
478
479 static const zend_function_entry backed_enum_methods[] = {
480 ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
481 ZEND_NAMED_ME(from, zend_enum_from_func, arginfo_class_BackedEnum_from, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
482 ZEND_NAMED_ME(tryFrom, zend_enum_try_from_func, arginfo_class_BackedEnum_tryFrom, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
483 ZEND_FE_END
484 };
485
zend_register_internal_enum(const char * name,zend_uchar type,const zend_function_entry * functions)486 ZEND_API zend_class_entry *zend_register_internal_enum(
487 const char *name, zend_uchar type, const zend_function_entry *functions)
488 {
489 ZEND_ASSERT(type == IS_UNDEF || type == IS_LONG || type == IS_STRING);
490
491 zend_class_entry tmp_ce;
492 INIT_CLASS_ENTRY_EX(tmp_ce, name, strlen(name), functions);
493
494 zend_class_entry *ce = zend_register_internal_class(&tmp_ce);
495 ce->ce_flags |= ZEND_ACC_ENUM;
496 ce->enum_backing_type = type;
497 if (type != IS_UNDEF) {
498 HashTable *backed_enum_table = pemalloc(sizeof(HashTable), 1);
499 zend_hash_init(backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 1);
500 zend_class_set_backed_enum_table(ce, backed_enum_table);
501 }
502
503 zend_enum_register_props(ce);
504 if (type == IS_UNDEF) {
505 zend_register_functions(
506 ce, unit_enum_methods, &ce->function_table, EG(current_module)->type);
507 zend_class_implements(ce, 1, zend_ce_unit_enum);
508 } else {
509 zend_register_functions(
510 ce, backed_enum_methods, &ce->function_table, EG(current_module)->type);
511 zend_class_implements(ce, 1, zend_ce_backed_enum);
512 }
513
514 return ce;
515 }
516
create_enum_case_ast(zend_string * class_name,zend_string * case_name,zval * value)517 static zend_ast_ref *create_enum_case_ast(
518 zend_string *class_name, zend_string *case_name, zval *value) {
519 // TODO: Use custom node type for enum cases?
520 size_t size = sizeof(zend_ast_ref) + zend_ast_size(3)
521 + (value ? 3 : 2) * sizeof(zend_ast_zval);
522 char *p = pemalloc(size, 1);
523 zend_ast_ref *ref = (zend_ast_ref *) p; p += sizeof(zend_ast_ref);
524 GC_SET_REFCOUNT(ref, 1);
525 GC_TYPE_INFO(ref) = GC_CONSTANT_AST | GC_PERSISTENT | GC_IMMUTABLE;
526
527 zend_ast *ast = (zend_ast *) p; p += zend_ast_size(3);
528 ast->kind = ZEND_AST_CONST_ENUM_INIT;
529 ast->attr = 0;
530 ast->lineno = 0;
531
532 ast->child[0] = (zend_ast *) p; p += sizeof(zend_ast_zval);
533 ast->child[0]->kind = ZEND_AST_ZVAL;
534 ast->child[0]->attr = 0;
535 ZEND_ASSERT(ZSTR_IS_INTERNED(class_name));
536 ZVAL_STR(zend_ast_get_zval(ast->child[0]), class_name);
537 Z_LINENO_P(zend_ast_get_zval(ast->child[0])) = 0;
538
539 ast->child[1] = (zend_ast *) p; p += sizeof(zend_ast_zval);
540 ast->child[1]->kind = ZEND_AST_ZVAL;
541 ast->child[1]->attr = 0;
542 ZEND_ASSERT(ZSTR_IS_INTERNED(case_name));
543 ZVAL_STR(zend_ast_get_zval(ast->child[1]), case_name);
544 Z_LINENO_P(zend_ast_get_zval(ast->child[1])) = 0;
545
546 if (value) {
547 ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval);
548 ast->child[2]->kind = ZEND_AST_ZVAL;
549 ast->child[2]->attr = 0;
550 ZEND_ASSERT(!Z_REFCOUNTED_P(value));
551 ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[2]), value);
552 Z_LINENO_P(zend_ast_get_zval(ast->child[2])) = 0;
553 } else {
554 ast->child[2] = NULL;
555 }
556
557 return ref;
558 }
559
zend_enum_add_case(zend_class_entry * ce,zend_string * case_name,zval * value)560 ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value)
561 {
562 if (value) {
563 ZEND_ASSERT(ce->enum_backing_type == Z_TYPE_P(value));
564 if (Z_TYPE_P(value) == IS_STRING && !ZSTR_IS_INTERNED(Z_STR_P(value))) {
565 zval_make_interned_string(value);
566 }
567
568 HashTable *backed_enum_table = CE_BACKED_ENUM_TABLE(ce);
569
570 zval case_name_zv;
571 ZVAL_STR(&case_name_zv, case_name);
572 if (Z_TYPE_P(value) == IS_LONG) {
573 zend_hash_index_add_new(backed_enum_table, Z_LVAL_P(value), &case_name_zv);
574 } else {
575 zend_hash_add_new(backed_enum_table, Z_STR_P(value), &case_name_zv);
576 }
577 } else {
578 ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF);
579 }
580
581 zval ast_zv;
582 Z_TYPE_INFO(ast_zv) = IS_CONSTANT_AST;
583 Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_name, value);
584 zend_class_constant *c = zend_declare_class_constant_ex(
585 ce, case_name, &ast_zv, ZEND_ACC_PUBLIC, NULL);
586 ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE;
587 }
588
zend_enum_add_case_cstr(zend_class_entry * ce,const char * name,zval * value)589 ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value)
590 {
591 zend_string *name_str = zend_string_init_interned(name, strlen(name), 1);
592 zend_enum_add_case(ce, name_str, value);
593 zend_string_release(name_str);
594 }
595
zend_enum_get_case(zend_class_entry * ce,zend_string * name)596 ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name) {
597 zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), name);
598 ZEND_ASSERT(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE);
599
600 if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
601 if (zval_update_constant_ex(&c->value, c->ce) == FAILURE) {
602 ZEND_UNREACHABLE();
603 }
604 }
605 ZEND_ASSERT(Z_TYPE(c->value) == IS_OBJECT);
606 return Z_OBJ(c->value);
607 }
608
zend_enum_get_case_cstr(zend_class_entry * ce,const char * name)609 ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name) {
610 zend_string *name_str = zend_string_init(name, strlen(name), 0);
611 zend_object *result = zend_enum_get_case(ce, name_str);
612 zend_string_release(name_str);
613 return result;
614 }
615