Coverage Report

Created: 2025-12-31 07:28

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