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