Coverage Report

Created: 2025-12-14 06:09

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