xref: /php-src/ext/standard/var_unserializer.re (revision baac01f5)
1/*
2  +----------------------------------------------------------------------+
3  | Copyright (c) The PHP Group                                          |
4  +----------------------------------------------------------------------+
5  | This source file is subject to version 3.01 of the PHP license,      |
6  | that is bundled with this package in the file LICENSE, and is        |
7  | available through the world-wide-web at the following url:           |
8  | https://www.php.net/license/3_01.txt                                 |
9  | If you did not receive a copy of the PHP license and are unable to   |
10  | obtain it through the world-wide-web, please send a note to          |
11  | license@php.net so we can mail you a copy immediately.               |
12  +----------------------------------------------------------------------+
13  | Author: Sascha Schumann <sascha@schumann.cx>                         |
14  +----------------------------------------------------------------------+
15*/
16
17#include "php.h"
18#include "ext/standard/php_var.h"
19#include "php_incomplete_class.h"
20#include "zend_portability.h"
21#include "zend_exceptions.h"
22
23/* {{{ reference-handling for unserializer: var_* */
24#define VAR_ENTRIES_MAX 1018     /* 1024 - offsetof(php_unserialize_data, entries) / sizeof(void*) */
25#define VAR_DTOR_ENTRIES_MAX 255 /* 256 - offsetof(var_dtor_entries, data) / sizeof(zval) */
26#define VAR_ENTRIES_DBG 0
27
28/* VAR_FLAG used in var_dtor entries to signify an entry on which
29 * __wakeup/__unserialize should be called */
30#define VAR_WAKEUP_FLAG 1
31#define VAR_UNSERIALIZE_FLAG 2
32
33/* Each element is encoded using at least 2 characters. */
34#define IS_FAKE_ELEM_COUNT(num_elems, serialized_len) \
35	((num_elems) > (serialized_len) / 2)
36
37typedef struct {
38	zend_long used_slots;
39	void *next;
40	zval *data[VAR_ENTRIES_MAX];
41} var_entries;
42
43typedef struct {
44	zend_long used_slots;
45	void *next;
46	zval data[VAR_DTOR_ENTRIES_MAX];
47} var_dtor_entries;
48
49struct php_unserialize_data {
50	var_entries      *last;
51	var_dtor_entries *first_dtor;
52	var_dtor_entries *last_dtor;
53	HashTable        *allowed_classes;
54	HashTable        *ref_props;
55	zend_long         cur_depth;
56	zend_long         max_depth;
57	var_entries       entries;
58};
59
60PHPAPI php_unserialize_data_t php_var_unserialize_init(void) {
61	php_unserialize_data_t d;
62	/* fprintf(stderr, "UNSERIALIZE_INIT    == lock: %u, level: %u\n", BG(serialize_lock), BG(unserialize).level); */
63	if (BG(serialize_lock) || !BG(unserialize).level) {
64		d = emalloc(sizeof(struct php_unserialize_data));
65		d->last = &d->entries;
66		d->first_dtor = d->last_dtor = NULL;
67		d->allowed_classes = NULL;
68		d->ref_props = NULL;
69		d->cur_depth = 0;
70		d->max_depth = BG(unserialize_max_depth);
71		d->entries.used_slots = 0;
72		d->entries.next = NULL;
73		if (!BG(serialize_lock)) {
74			BG(unserialize).data = d;
75			BG(unserialize).level = 1;
76		}
77	} else {
78		d = BG(unserialize).data;
79		++BG(unserialize).level;
80	}
81	return d;
82}
83
84PHPAPI void php_var_unserialize_destroy(php_unserialize_data_t d) {
85	/* fprintf(stderr, "UNSERIALIZE_DESTROY == lock: %u, level: %u\n", BG(serialize_lock), BG(unserialize).level); */
86	if (BG(serialize_lock) || BG(unserialize).level == 1) {
87		var_destroy(&d);
88		efree(d);
89	}
90	if (!BG(serialize_lock) && !--BG(unserialize).level) {
91		BG(unserialize).data = NULL;
92	}
93}
94
95PHPAPI HashTable *php_var_unserialize_get_allowed_classes(php_unserialize_data_t d) {
96	return d->allowed_classes;
97}
98PHPAPI void php_var_unserialize_set_allowed_classes(php_unserialize_data_t d, HashTable *classes) {
99	d->allowed_classes = classes;
100}
101
102PHPAPI void php_var_unserialize_set_max_depth(php_unserialize_data_t d, zend_long max_depth) {
103	d->max_depth = max_depth;
104}
105PHPAPI zend_long php_var_unserialize_get_max_depth(php_unserialize_data_t d) {
106	return d->max_depth;
107}
108
109PHPAPI void php_var_unserialize_set_cur_depth(php_unserialize_data_t d, zend_long cur_depth) {
110	d->cur_depth = cur_depth;
111}
112PHPAPI zend_long php_var_unserialize_get_cur_depth(php_unserialize_data_t d) {
113	return d->cur_depth;
114}
115
116static inline void var_push(php_unserialize_data_t *var_hashx, zval *rval)
117{
118	var_entries *var_hash = (*var_hashx)->last;
119#if VAR_ENTRIES_DBG
120	fprintf(stderr, "var_push(" ZEND_LONG_FMT "): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_P(rval));
121#endif
122
123	if (var_hash->used_slots == VAR_ENTRIES_MAX) {
124		var_hash = emalloc(sizeof(var_entries));
125		var_hash->used_slots = 0;
126		var_hash->next = 0;
127
128		(*var_hashx)->last->next = var_hash;
129		(*var_hashx)->last = var_hash;
130	}
131
132	var_hash->data[var_hash->used_slots++] = rval;
133}
134
135PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval *rval)
136{
137	if (Z_REFCOUNTED_P(rval)) {
138		zval *tmp_var = var_tmp_var(var_hashx);
139		if (!tmp_var) {
140			return;
141		}
142		ZVAL_COPY(tmp_var, rval);
143	}
144}
145
146static zend_never_inline void var_push_dtor_value(php_unserialize_data_t *var_hashx, zval *rval)
147{
148	if (Z_REFCOUNTED_P(rval)) {
149		zval *tmp_var = var_tmp_var(var_hashx);
150		if (!tmp_var) {
151			return;
152		}
153		ZVAL_COPY_VALUE(tmp_var, rval);
154	}
155}
156
157static zend_always_inline zval *tmp_var(php_unserialize_data_t *var_hashx, zend_long num)
158{
159    var_dtor_entries *var_hash;
160	zend_long used_slots;
161
162    if (!var_hashx || !*var_hashx || num < 1) {
163        return NULL;
164    }
165
166    var_hash = (*var_hashx)->last_dtor;
167    if (!var_hash || var_hash->used_slots + num > VAR_DTOR_ENTRIES_MAX) {
168        var_hash = emalloc(sizeof(var_dtor_entries));
169        var_hash->used_slots = 0;
170        var_hash->next = 0;
171
172        if (!(*var_hashx)->first_dtor) {
173            (*var_hashx)->first_dtor = var_hash;
174        } else {
175            (*var_hashx)->last_dtor->next = var_hash;
176        }
177
178        (*var_hashx)->last_dtor = var_hash;
179    }
180	for (used_slots = var_hash->used_slots; var_hash->used_slots < used_slots + num; var_hash->used_slots++) {
181		ZVAL_UNDEF(&var_hash->data[var_hash->used_slots]);
182		Z_EXTRA(var_hash->data[var_hash->used_slots]) = 0;
183	}
184    return &var_hash->data[used_slots];
185}
186
187PHPAPI zval *var_tmp_var(php_unserialize_data_t *var_hashx)
188{
189    return tmp_var(var_hashx, 1);
190}
191
192PHPAPI void var_replace(php_unserialize_data_t *var_hashx, zval *ozval, zval *nzval)
193{
194	zend_long i;
195	var_entries *var_hash = &(*var_hashx)->entries;
196#if VAR_ENTRIES_DBG
197	fprintf(stderr, "var_replace(" ZEND_LONG_FMT "): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_P(nzval));
198#endif
199
200	while (var_hash) {
201		for (i = 0; i < var_hash->used_slots; i++) {
202			if (var_hash->data[i] == ozval) {
203				var_hash->data[i] = nzval;
204				/* do not break here */
205			}
206		}
207		var_hash = var_hash->next;
208	}
209}
210
211static zval *var_access(php_unserialize_data_t *var_hashx, zend_long id)
212{
213	var_entries *var_hash = &(*var_hashx)->entries;
214#if VAR_ENTRIES_DBG
215	fprintf(stderr, "var_access(" ZEND_LONG_FMT "): " ZEND_LONG_FMT "\n", var_hash?var_hash->used_slots:-1L, id);
216#endif
217
218	while (id >= VAR_ENTRIES_MAX && var_hash && var_hash->used_slots == VAR_ENTRIES_MAX) {
219		var_hash = var_hash->next;
220		id -= VAR_ENTRIES_MAX;
221	}
222
223	if (!var_hash) return NULL;
224
225	if (id < 0 || id >= var_hash->used_slots) return NULL;
226
227	return var_hash->data[id];
228}
229
230PHPAPI void var_destroy(php_unserialize_data_t *var_hashx)
231{
232	void *next;
233	zend_long i;
234	var_entries *var_hash = (*var_hashx)->entries.next;
235	var_dtor_entries *var_dtor_hash = (*var_hashx)->first_dtor;
236	bool delayed_call_failed = 0;
237
238#if VAR_ENTRIES_DBG
239	fprintf(stderr, "var_destroy( " ZEND_LONG_FMT ")\n", var_hash?var_hash->used_slots:-1L);
240#endif
241
242	while (var_hash) {
243		next = var_hash->next;
244		efree_size(var_hash, sizeof(var_entries));
245		var_hash = next;
246	}
247
248	while (var_dtor_hash) {
249		for (i = 0; i < var_dtor_hash->used_slots; i++) {
250			zval *zv = &var_dtor_hash->data[i];
251#if VAR_ENTRIES_DBG
252			fprintf(stderr, "var_destroy dtor(%p, %ld)\n", &var_dtor_hash->data[i], Z_REFCOUNT_P(&var_dtor_hash->data[i]));
253#endif
254
255			if (Z_EXTRA_P(zv) == VAR_WAKEUP_FLAG) {
256				/* Perform delayed __wakeup calls */
257				if (!delayed_call_failed) {
258					zval retval;
259					zend_fcall_info fci;
260					zend_fcall_info_cache fci_cache;
261
262					ZEND_ASSERT(Z_TYPE_P(zv) == IS_OBJECT);
263
264					fci.size = sizeof(fci);
265					fci.object = Z_OBJ_P(zv);
266					fci.retval = &retval;
267					fci.param_count = 0;
268					fci.params = NULL;
269					fci.named_params = NULL;
270					ZVAL_UNDEF(&fci.function_name);
271
272					fci_cache.function_handler = zend_hash_find_ptr(
273						&fci.object->ce->function_table, ZSTR_KNOWN(ZEND_STR_WAKEUP));
274					fci_cache.object = fci.object;
275					fci_cache.called_scope = fci.object->ce;
276
277					BG(serialize_lock)++;
278					if (zend_call_function(&fci, &fci_cache) == FAILURE || Z_ISUNDEF(retval)) {
279						delayed_call_failed = 1;
280						GC_ADD_FLAGS(Z_OBJ_P(zv), IS_OBJ_DESTRUCTOR_CALLED);
281					}
282					BG(serialize_lock)--;
283
284					zval_ptr_dtor(&retval);
285				} else {
286					GC_ADD_FLAGS(Z_OBJ_P(zv), IS_OBJ_DESTRUCTOR_CALLED);
287				}
288			} else if (Z_EXTRA_P(zv) == VAR_UNSERIALIZE_FLAG) {
289				/* Perform delayed __unserialize calls */
290				if (!delayed_call_failed) {
291					zval param;
292					ZVAL_COPY(&param, &var_dtor_hash->data[i + 1]);
293
294					BG(serialize_lock)++;
295					zend_call_known_instance_method_with_1_params(
296						Z_OBJCE_P(zv)->__unserialize, Z_OBJ_P(zv), NULL, &param);
297					if (EG(exception)) {
298						delayed_call_failed = 1;
299						GC_ADD_FLAGS(Z_OBJ_P(zv), IS_OBJ_DESTRUCTOR_CALLED);
300					}
301					BG(serialize_lock)--;
302					zval_ptr_dtor(&param);
303				} else {
304					GC_ADD_FLAGS(Z_OBJ_P(zv), IS_OBJ_DESTRUCTOR_CALLED);
305				}
306			}
307
308			i_zval_ptr_dtor(zv);
309		}
310		next = var_dtor_hash->next;
311		efree_size(var_dtor_hash, sizeof(var_dtor_entries));
312		var_dtor_hash = next;
313	}
314
315	if ((*var_hashx)->ref_props) {
316		zend_hash_destroy((*var_hashx)->ref_props);
317		FREE_HASHTABLE((*var_hashx)->ref_props);
318	}
319}
320
321/* }}} */
322
323static zend_string *unserialize_str(const unsigned char **p, size_t len, size_t maxlen)
324{
325	size_t i, j;
326	zend_string *str = zend_string_safe_alloc(1, len, 0, 0);
327	unsigned char *end = *(unsigned char **)p+maxlen;
328
329	for (i = 0; i < len; i++) {
330		if (*p >= end) {
331			zend_string_efree(str);
332			return NULL;
333		}
334		if (**p != '\\') {
335			ZSTR_VAL(str)[i] = (char)**p;
336		} else {
337			unsigned char ch = 0;
338
339			for (j = 0; j < 2; j++) {
340				(*p)++;
341				if (**p >= '0' && **p <= '9') {
342					ch = (ch << 4) + (**p -'0');
343				} else if (**p >= 'a' && **p <= 'f') {
344					ch = (ch << 4) + (**p -'a'+10);
345				} else if (**p >= 'A' && **p <= 'F') {
346					ch = (ch << 4) + (**p -'A'+10);
347				} else {
348					zend_string_efree(str);
349					return NULL;
350				}
351			}
352			ZSTR_VAL(str)[i] = (char)ch;
353		}
354		(*p)++;
355	}
356	ZSTR_VAL(str)[i] = 0;
357	ZSTR_LEN(str) = i;
358	return str;
359}
360
361static inline int unserialize_allowed_class(
362		zend_string *lcname, php_unserialize_data_t *var_hashx)
363{
364	HashTable *classes = (*var_hashx)->allowed_classes;
365
366	if(classes == NULL) {
367		return 1;
368	}
369	if(!zend_hash_num_elements(classes)) {
370		return 0;
371	}
372
373	return zend_hash_exists(classes, lcname);
374}
375
376#define YYFILL(n) do { } while (0)
377#define YYCTYPE unsigned char
378#define YYCURSOR cursor
379#define YYLIMIT limit
380#define YYMARKER marker
381
382
383/*!re2c
384uiv = [0-9]+;
385iv = [+-]? [0-9]+;
386nv = [+-]? ([0-9]* "." [0-9]+|[0-9]+ "." [0-9]*);
387nvexp = (iv | nv) [eE] iv;
388any = [\000-\377];
389object = [OC];
390*/
391
392
393
394static inline zend_long parse_iv2(const unsigned char *p, const unsigned char **q)
395{
396	zend_ulong result = 0;
397	zend_ulong neg = 0;
398	const unsigned char *start;
399
400	if (*p == '-') {
401		neg = 1;
402		p++;
403	} else if (UNEXPECTED(*p == '+')) {
404		p++;
405	}
406
407	while (UNEXPECTED(*p == '0')) {
408		p++;
409	}
410
411	start = p;
412
413	while (*p >= '0' && *p <= '9') {
414		result = result * 10 + ((zend_ulong)(*p) - '0');
415		p++;
416	}
417
418	if (q) {
419		*q = p;
420	}
421
422	/* number too long or overflow */
423	if (UNEXPECTED(p - start > MAX_LENGTH_OF_LONG - 1)
424	 || (SIZEOF_ZEND_LONG == 4
425	 	&& UNEXPECTED(p - start == MAX_LENGTH_OF_LONG - 1)
426	 	&& UNEXPECTED(*start > '2'))
427	 || UNEXPECTED(result > ZEND_LONG_MAX + neg)) {
428		php_error_docref(NULL, E_WARNING, "Numerical result out of range");
429		return (!neg) ? ZEND_LONG_MAX : ZEND_LONG_MIN;
430	}
431
432	return (zend_long) ((!neg) ? result : -result);
433}
434
435static inline zend_long parse_iv(const unsigned char *p)
436{
437	return parse_iv2(p, NULL);
438}
439
440/* no need to check for length - re2c already did */
441static inline size_t parse_uiv(const unsigned char *p)
442{
443	unsigned char cursor;
444	size_t result = 0;
445
446	while (1) {
447		cursor = *p;
448		if (cursor >= '0' && cursor <= '9') {
449			result = result * 10 + (size_t)(cursor - (unsigned char)'0');
450		} else {
451			break;
452		}
453		p++;
454	}
455	return result;
456}
457
458#define UNSERIALIZE_PARAMETER zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash
459#define UNSERIALIZE_PASSTHRU rval, p, max, var_hash
460
461static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER);
462
463static zend_always_inline int process_nested_array_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements)
464{
465	if (var_hash) {
466		if ((*var_hash)->max_depth > 0 && (*var_hash)->cur_depth >= (*var_hash)->max_depth) {
467			php_error_docref(NULL, E_WARNING,
468				"Maximum depth of " ZEND_LONG_FMT " exceeded. "
469				"The depth limit can be changed using the max_depth unserialize() option "
470				"or the unserialize_max_depth ini setting",
471				(*var_hash)->max_depth);
472			return 0;
473		}
474		(*var_hash)->cur_depth++;
475	}
476
477	while (elements-- > 0) {
478		zval key, *data;
479		zend_ulong idx;
480
481		ZVAL_UNDEF(&key);
482
483		if (!php_var_unserialize_internal(&key, p, max, NULL)) {
484			zval_ptr_dtor(&key);
485			goto failure;
486		}
487
488		if (Z_TYPE(key) == IS_LONG) {
489			idx = Z_LVAL(key);
490numeric_key:
491			data = zend_hash_index_lookup(ht, idx);
492			if (UNEXPECTED(Z_TYPE_INFO_P(data) != IS_NULL)) {
493				var_push_dtor_value(var_hash, data);
494				ZVAL_NULL(data);
495			}
496		} else if (Z_TYPE(key) == IS_STRING) {
497			if (UNEXPECTED(ZEND_HANDLE_NUMERIC(Z_STR(key), idx))) {
498				zval_ptr_dtor_str(&key);
499				goto numeric_key;
500			}
501			data = zend_hash_lookup(ht, Z_STR(key));
502			if (UNEXPECTED(Z_TYPE_INFO_P(data) != IS_NULL)) {
503				var_push_dtor_value(var_hash, data);
504				ZVAL_NULL(data);
505			}
506			zval_ptr_dtor_str(&key);
507		} else {
508			zval_ptr_dtor(&key);
509			goto failure;
510		}
511
512		if (!php_var_unserialize_internal(data, p, max, var_hash)) {
513			goto failure;
514		}
515
516		if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
517			(*p)--;
518			goto failure;
519		}
520	}
521
522	if (var_hash) {
523		(*var_hash)->cur_depth--;
524	}
525	return 1;
526
527failure:
528	if (var_hash) {
529		(*var_hash)->cur_depth--;
530	}
531	return 0;
532}
533
534static int is_property_visibility_changed(zend_class_entry *ce, zval *key)
535{
536	if (zend_hash_num_elements(&ce->properties_info) > 0) {
537		zend_property_info *existing_propinfo = NULL;
538		const char *unmangled_class = NULL;
539		const char *unmangled_prop;
540		size_t unmangled_prop_len;
541
542		if (UNEXPECTED(zend_unmangle_property_name_ex(Z_STR_P(key), &unmangled_class, &unmangled_prop, &unmangled_prop_len) == FAILURE)) {
543			zval_ptr_dtor_str(key);
544			return -1;
545		}
546
547		if (unmangled_class == NULL) {
548			existing_propinfo = zend_hash_find_ptr(&ce->properties_info, Z_STR_P(key));
549		} else {
550			if (!strcmp(unmangled_class, "*")
551			 || !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))) {
552				existing_propinfo = zend_hash_str_find_ptr(
553					&ce->properties_info, unmangled_prop, unmangled_prop_len);
554			}
555		}
556
557		if (existing_propinfo != NULL) {
558			if (!(existing_propinfo->flags & ZEND_ACC_VIRTUAL)) {
559				zval_ptr_dtor_str(key);
560				ZVAL_STR_COPY(key, existing_propinfo->name);
561				return 1;
562			} else {
563				php_error_docref(NULL, E_WARNING,
564					"Cannot unserialize value for virtual property %s::$%s",
565					ZSTR_VAL(existing_propinfo->ce->name), Z_STRVAL_P(key));
566				zval_ptr_dtor_str(key);
567				return -1;
568			}
569		}
570	}
571	return 0;
572}
573
574
575static zend_always_inline int process_nested_object_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, zend_object *obj)
576{
577	if (var_hash) {
578		if ((*var_hash)->max_depth > 0 && (*var_hash)->cur_depth >= (*var_hash)->max_depth) {
579			php_error_docref(NULL, E_WARNING,
580				"Maximum depth of " ZEND_LONG_FMT " exceeded. "
581				"The depth limit can be changed using the max_depth unserialize() option "
582				"or the unserialize_max_depth ini setting",
583				(*var_hash)->max_depth);
584			return 0;
585		}
586		(*var_hash)->cur_depth++;
587	}
588
589	while (elements-- > 0) {
590		zval key, *data;
591		zend_property_info *info = NULL;
592
593		ZVAL_UNDEF(&key);
594
595		if (!php_var_unserialize_internal(&key, p, max, NULL)) {
596			zval_ptr_dtor(&key);
597			goto failure;
598		}
599
600		if (EXPECTED(Z_TYPE(key) == IS_STRING)) {
601string_key:
602			data = zend_hash_find(ht, Z_STR(key));
603			if (data != NULL) {
604				if (Z_TYPE_P(data) == IS_INDIRECT) {
605declared_property:
606					/* This is a property with a declaration */
607					data = Z_INDIRECT_P(data);
608					info = zend_get_typed_property_info_for_slot(obj, data);
609					if (info) {
610						if (Z_ISREF_P(data)) {
611							/* If the value is overwritten, remove old type source from ref. */
612							ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(data), info);
613						}
614
615						if ((*var_hash)->ref_props) {
616							/* Remove old entry from ref_props table, if it exists. */
617							zend_hash_index_del(
618								(*var_hash)->ref_props, (uintptr_t) data);
619						}
620					}
621					/* We may override default property value, but they are usually immutable */
622					if (Z_REFCOUNTED_P(data)) {
623						var_push_dtor_value(var_hash, data);
624					}
625					ZVAL_NULL(data);
626				} else {
627					/* Unusual override of dynamic property */
628					int ret = is_property_visibility_changed(obj->ce, &key);
629
630					if (ret > 0) {
631						goto second_try;
632					} else if (!ret) {
633						var_push_dtor_value(var_hash, data);
634						ZVAL_NULL(data);
635					} else if (ret < 0) {
636						goto failure;
637					}
638				}
639			} else {
640				int ret = is_property_visibility_changed(obj->ce, &key);
641
642				if (EXPECTED(!ret)) {
643					if (UNEXPECTED(obj->ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) {
644						zend_throw_error(NULL, "Cannot create dynamic property %s::$%s",
645							ZSTR_VAL(obj->ce->name), zend_get_unmangled_property_name(Z_STR_P(&key)));
646						zval_ptr_dtor_str(&key);
647						goto failure;
648					} else if (!(obj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) {
649						zend_error(E_DEPRECATED, "Creation of dynamic property %s::$%s is deprecated",
650							ZSTR_VAL(obj->ce->name), zend_get_unmangled_property_name(Z_STR_P(&key)));
651						if (EG(exception)) {
652							zval_ptr_dtor_str(&key);
653							goto failure;
654						}
655					}
656
657					data = zend_hash_add_new(ht, Z_STR(key), &EG(uninitialized_zval));
658				} else if (ret < 0) {
659					goto failure;
660				} else {
661second_try:
662					data = zend_hash_lookup(ht, Z_STR(key));
663					if (Z_TYPE_P(data) == IS_INDIRECT) {
664						goto declared_property;
665					} else if (UNEXPECTED(Z_TYPE_INFO_P(data) != IS_NULL)) {
666						var_push_dtor_value(var_hash, data);
667						ZVAL_NULL(data);
668					}
669				}
670			}
671			zval_ptr_dtor_str(&key);
672		} else if (Z_TYPE(key) == IS_LONG) {
673			/* object properties should include no integers */
674			convert_to_string(&key);
675			goto string_key;
676		} else {
677			zval_ptr_dtor(&key);
678			goto failure;
679		}
680
681		if (!php_var_unserialize_internal(data, p, max, var_hash)) {
682			if (info && Z_ISREF_P(data)) {
683				/* Add type source even if we failed to unserialize.
684				 * The data is still stored in the property. */
685				ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info);
686			}
687			goto failure;
688		}
689
690		if (UNEXPECTED(info)) {
691			if (!zend_verify_prop_assignable_by_ref(info, data, /* strict */ 1)) {
692				zval_ptr_dtor(data);
693				ZVAL_UNDEF(data);
694				goto failure;
695			}
696
697			if (Z_ISREF_P(data)) {
698				ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info);
699			} else {
700				/* Remember to which property this slot belongs, so we can add a
701				 * type source if it is turned into a reference lateron. */
702				if (!(*var_hash)->ref_props) {
703					(*var_hash)->ref_props = emalloc(sizeof(HashTable));
704					zend_hash_init((*var_hash)->ref_props, 8, NULL, NULL, 0);
705				}
706				zend_hash_index_update_ptr(
707					(*var_hash)->ref_props, (uintptr_t) data, info);
708			}
709		}
710
711		if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
712			(*p)--;
713			goto failure;
714		}
715	}
716
717	if (var_hash) {
718		(*var_hash)->cur_depth--;
719	}
720	return 1;
721
722failure:
723	if (var_hash) {
724		(*var_hash)->cur_depth--;
725	}
726	return 0;
727}
728
729static inline int finish_nested_data(UNSERIALIZE_PARAMETER)
730{
731	if (*p >= max || **p != '}') {
732		return 0;
733	}
734
735	(*p)++;
736	return 1;
737}
738
739static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
740{
741	zend_long datalen;
742
743	datalen = parse_iv2((*p) + 2, p);
744
745	if (max - (*p) < 2) {
746		return 0;
747	}
748
749	if ((*p)[0] != ':') {
750		return 0;
751	}
752
753	if ((*p)[1] != '{') {
754		(*p) += 1;
755		return 0;
756	}
757
758	(*p) += 2;
759
760	if (datalen < 0 || (max - (*p)) <= datalen) {
761		zend_error(E_WARNING, "Insufficient data for unserializing - " ZEND_LONG_FMT " required, " ZEND_LONG_FMT " present", datalen, (zend_long)(max - (*p)));
762		return 0;
763	}
764
765	/* Check that '}' is present before calling ce->unserialize() to mitigate issues
766	 * with unserialize reading past the end of the passed buffer if the string is not
767	 * appropriately terminated (usually NUL terminated, but '}' is also sufficient.) */
768	if ((*p)[datalen] != '}') {
769		(*p) += datalen;
770		return 0;
771	}
772
773	if (ce->unserialize == NULL) {
774		zend_error(E_WARNING, "Class %s has no unserializer", ZSTR_VAL(ce->name));
775		object_init_ex(rval, ce);
776	} else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) {
777		return 0;
778	}
779
780	(*p) += datalen + 1; /* +1 for '}' */
781	return 1;
782}
783
784#ifdef PHP_WIN32
785# pragma optimize("", off)
786#endif
787static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool has_unserialize)
788{
789	HashTable *ht;
790	bool has_wakeup;
791
792	if (has_unserialize) {
793		zval ary, *tmp;
794
795		if (elements >= HT_MAX_SIZE) {
796			return 0;
797		}
798
799		array_init_size(&ary, elements);
800		/* Avoid reallocation due to packed -> mixed conversion. */
801		zend_hash_real_init_mixed(Z_ARRVAL(ary));
802		if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL(ary), elements)) {
803			ZVAL_DEREF(rval);
804			GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED);
805			zval_ptr_dtor(&ary);
806			return 0;
807		}
808
809		/* Delay __unserialize() call until end of serialization. We use two slots here to
810		 * store both the object and the unserialized data array. */
811		ZVAL_DEREF(rval);
812		tmp = tmp_var(var_hash, 2);
813		ZVAL_COPY(tmp, rval);
814		Z_EXTRA_P(tmp) = VAR_UNSERIALIZE_FLAG;
815		tmp++;
816		ZVAL_COPY_VALUE(tmp, &ary);
817
818		return finish_nested_data(UNSERIALIZE_PASSTHRU);
819	}
820
821	has_wakeup = Z_OBJCE_P(rval) != PHP_IC_ENTRY
822		&& zend_hash_exists(&Z_OBJCE_P(rval)->function_table, ZSTR_KNOWN(ZEND_STR_WAKEUP));
823
824	ht = Z_OBJPROP_P(rval);
825	if (elements >= (zend_long)(HT_MAX_SIZE - zend_hash_num_elements(ht))) {
826		return 0;
827	}
828
829	zend_hash_extend(ht, zend_hash_num_elements(ht) + elements, HT_IS_PACKED(ht));
830	if (!process_nested_object_data(UNSERIALIZE_PASSTHRU, ht, elements, Z_OBJ_P(rval))) {
831		if (has_wakeup) {
832			ZVAL_DEREF(rval);
833			GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED);
834		}
835		return 0;
836	}
837
838	ZVAL_DEREF(rval);
839	if (has_wakeup) {
840		/* Delay __wakeup call until end of serialization */
841		zval *wakeup_var = var_tmp_var(var_hash);
842		ZVAL_COPY(wakeup_var, rval);
843		Z_EXTRA_P(wakeup_var) = VAR_WAKEUP_FLAG;
844	}
845
846	return finish_nested_data(UNSERIALIZE_PASSTHRU);
847}
848#ifdef PHP_WIN32
849# pragma optimize("", on)
850#endif
851
852PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
853{
854	var_entries *orig_var_entries = (*var_hash)->last;
855	zend_long orig_used_slots = orig_var_entries ? orig_var_entries->used_slots : 0;
856	int result;
857
858	result = php_var_unserialize_internal(UNSERIALIZE_PASSTHRU);
859
860	if (!result) {
861		/* If the unserialization failed, mark all elements that have been added to var_hash
862		 * as NULL. This will forbid their use by other unserialize() calls in the same
863		 * unserialization context. */
864		var_entries *e = orig_var_entries;
865		zend_long s = orig_used_slots;
866		while (e) {
867			for (; s < e->used_slots; s++) {
868				e->data[s] = NULL;
869			}
870
871			e = e->next;
872			s = 0;
873		}
874	}
875
876	return result;
877}
878
879static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER)
880{
881	const unsigned char *cursor, *limit, *marker, *start;
882	zval *rval_ref;
883
884	limit = max;
885	cursor = *p;
886
887	if (YYCURSOR >= YYLIMIT) {
888		return 0;
889	}
890
891	if (var_hash && (*p)[0] != 'R') {
892		var_push(var_hash, rval);
893	}
894
895	start = cursor;
896
897/*!re2c
898
899"R:" uiv ";"		{
900	zend_long id;
901
902 	*p = YYCURSOR;
903	if (!var_hash) return 0;
904
905	id = parse_uiv(start + 2) - 1;
906	if (id == -1 || (rval_ref = var_access(var_hash, id)) == NULL) {
907		return 0;
908	}
909
910	if (rval_ref == rval || (Z_ISREF_P(rval_ref) && Z_REFVAL_P(rval_ref) == rval)) {
911		return 0;
912	}
913
914	if (!Z_ISREF_P(rval_ref)) {
915		zend_property_info *info = NULL;
916		if ((*var_hash)->ref_props) {
917			info = zend_hash_index_find_ptr((*var_hash)->ref_props, (uintptr_t) rval_ref);
918		}
919		ZVAL_NEW_REF(rval_ref, rval_ref);
920		if (info) {
921			ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(rval_ref), info);
922		}
923	}
924
925	ZVAL_COPY(rval, rval_ref);
926
927	return 1;
928}
929
930"r:" uiv ";"		{
931	zend_long id;
932
933 	*p = YYCURSOR;
934	if (!var_hash) return 0;
935
936	id = parse_uiv(start + 2) - 1;
937	if (id == -1 || (rval_ref = var_access(var_hash, id)) == NULL) {
938		return 0;
939	}
940
941	if (rval_ref == rval) {
942		return 0;
943	}
944
945	ZVAL_DEREF(rval_ref);
946	if (Z_TYPE_P(rval_ref) != IS_OBJECT) {
947		return 0;
948	}
949
950	ZVAL_COPY(rval, rval_ref);
951
952	return 1;
953}
954
955"N;"	{
956	*p = YYCURSOR;
957	ZVAL_NULL(rval);
958	return 1;
959}
960
961"b:0;"	{
962	*p = YYCURSOR;
963	ZVAL_FALSE(rval);
964	return 1;
965}
966
967"b:1;"	{
968	*p = YYCURSOR;
969	ZVAL_TRUE(rval);
970	return 1;
971}
972
973"i:" iv ";"	{
974#if SIZEOF_ZEND_LONG == 4
975	int digits = YYCURSOR - start - 3;
976
977	if (start[2] == '-' || start[2] == '+') {
978		digits--;
979	}
980
981	/* Use double for large zend_long values that were serialized on a 64-bit system */
982	if (digits >= MAX_LENGTH_OF_LONG - 1) {
983		if (digits == MAX_LENGTH_OF_LONG - 1) {
984			int cmp = strncmp((char*)YYCURSOR - MAX_LENGTH_OF_LONG, long_min_digits, MAX_LENGTH_OF_LONG - 1);
985
986			if (!(cmp < 0 || (cmp == 0 && start[2] == '-'))) {
987				goto use_double;
988			}
989		} else {
990			goto use_double;
991		}
992	}
993#endif
994	*p = YYCURSOR;
995	ZVAL_LONG(rval, parse_iv(start + 2));
996	return 1;
997}
998
999"d:" ("NAN" | "-"? "INF") ";"	{
1000	*p = YYCURSOR;
1001
1002	if (!strncmp((char*)start + 2, "NAN", 3)) {
1003		ZVAL_DOUBLE(rval, ZEND_NAN);
1004	} else if (!strncmp((char*)start + 2, "INF", 3)) {
1005		ZVAL_DOUBLE(rval, ZEND_INFINITY);
1006	} else if (!strncmp((char*)start + 2, "-INF", 4)) {
1007		ZVAL_DOUBLE(rval, -ZEND_INFINITY);
1008	} else {
1009		ZVAL_NULL(rval);
1010	}
1011
1012	return 1;
1013}
1014
1015"d:" (iv | nv | nvexp) ";"	{
1016#if SIZEOF_ZEND_LONG == 4
1017use_double:
1018#endif
1019	*p = YYCURSOR;
1020	ZVAL_DOUBLE(rval, zend_strtod((const char *)start + 2, NULL));
1021	return 1;
1022}
1023
1024"s:" uiv ":" ["] 	{
1025	size_t len, maxlen;
1026	char *str;
1027
1028	len = parse_uiv(start + 2);
1029	maxlen = max - YYCURSOR;
1030	if (maxlen < len) {
1031		*p = start + 2;
1032		return 0;
1033	}
1034
1035	str = (char*)YYCURSOR;
1036
1037	YYCURSOR += len;
1038
1039	if (*(YYCURSOR) != '"') {
1040		*p = YYCURSOR;
1041		return 0;
1042	}
1043
1044	if (*(YYCURSOR + 1) != ';') {
1045		*p = YYCURSOR + 1;
1046		return 0;
1047	}
1048
1049	YYCURSOR += 2;
1050	*p = YYCURSOR;
1051
1052	if (!var_hash) {
1053		/* Array or object key unserialization */
1054		ZVAL_STR(rval, zend_string_init_existing_interned(str, len, 0));
1055	} else {
1056		ZVAL_STRINGL_FAST(rval, str, len);
1057	}
1058	return 1;
1059}
1060
1061"S:" uiv ":" ["] 	{
1062	size_t len, maxlen;
1063	zend_string *str;
1064
1065	len = parse_uiv(start + 2);
1066	maxlen = max - YYCURSOR;
1067	if (maxlen < len) {
1068		*p = start + 2;
1069		return 0;
1070	}
1071
1072	if ((str = unserialize_str(&YYCURSOR, len, maxlen)) == NULL) {
1073		return 0;
1074	}
1075
1076	if (*(YYCURSOR) != '"') {
1077		zend_string_efree(str);
1078		*p = YYCURSOR;
1079		return 0;
1080	}
1081
1082	if (*(YYCURSOR + 1) != ';') {
1083		efree(str);
1084		*p = YYCURSOR + 1;
1085		return 0;
1086	}
1087
1088	YYCURSOR += 2;
1089	*p = YYCURSOR;
1090
1091	ZVAL_STR(rval, str);
1092
1093	php_error_docref(NULL, E_DEPRECATED, "Unserializing the 'S' format is deprecated");
1094
1095	return 1;
1096}
1097
1098"a:" uiv ":" "{" {
1099	zend_long elements = parse_iv(start + 2);
1100	/* use iv() not uiv() in order to check data range */
1101	*p = YYCURSOR;
1102    if (!var_hash) return 0;
1103
1104	if (elements < 0 || elements >= HT_MAX_SIZE || IS_FAKE_ELEM_COUNT(elements, max - YYCURSOR)) {
1105		return 0;
1106	}
1107
1108	if (elements) {
1109		array_init_size(rval, elements);
1110		/* we can't convert from packed to hash during unserialization, because
1111		   reference to some zvals might be kept in var_hash (to support references) */
1112		zend_hash_real_init_mixed(Z_ARRVAL_P(rval));
1113	} else {
1114		ZVAL_EMPTY_ARRAY(rval);
1115		return finish_nested_data(UNSERIALIZE_PASSTHRU);
1116	}
1117
1118	/* The array may contain references to itself, in which case we'll be modifying an
1119	 * rc>1 array. This is okay, since the array is, ostensibly, only visible to
1120	 * unserialize (in practice unserialization handlers also see it). Ideally we should
1121	 * prohibit "r:" references to non-objects, as we only generate them for objects. */
1122	HT_ALLOW_COW_VIOLATION(Z_ARRVAL_P(rval));
1123
1124	if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements)) {
1125		return 0;
1126	}
1127
1128	return finish_nested_data(UNSERIALIZE_PASSTHRU);
1129}
1130
1131object ":" uiv ":" ["]	{
1132	size_t len, maxlen;
1133	zend_long elements;
1134	char *str;
1135	zend_string *class_name;
1136	zend_class_entry *ce;
1137	bool incomplete_class = 0;
1138	bool custom_object = 0;
1139	bool has_unserialize = 0;
1140
1141	zval user_func;
1142	zval retval;
1143	zval args[1];
1144
1145    if (!var_hash) return 0;
1146	if (*start == 'C') {
1147		custom_object = 1;
1148	}
1149
1150	len = parse_uiv(start + 2);
1151	maxlen = max - YYCURSOR;
1152	if (maxlen < len || len == 0) {
1153		*p = start + 2;
1154		return 0;
1155	}
1156
1157	str = (char*)YYCURSOR;
1158
1159	YYCURSOR += len;
1160
1161	if (*(YYCURSOR) != '"') {
1162		*p = YYCURSOR;
1163		return 0;
1164	}
1165	if (*(YYCURSOR+1) != ':') {
1166		*p = YYCURSOR+1;
1167		return 0;
1168	}
1169
1170	if (len == 0) {
1171		/* empty class names are not allowed */
1172		return 0;
1173	}
1174
1175	if (str[0] == '\000') {
1176		/* runtime definition keys are not allowed */
1177		return 0;
1178	}
1179
1180	if (str[0] == '\\') {
1181		/* class name can't start from namespace separator */
1182		return 0;
1183	}
1184
1185	class_name = zend_string_init_interned(str, len, 0);
1186
1187	do {
1188		zend_string *lc_name;
1189
1190		if (!(*var_hash)->allowed_classes && ZSTR_HAS_CE_CACHE(class_name)) {
1191			ce = ZSTR_GET_CE_CACHE(class_name);
1192			if (ce) {
1193				break;
1194			}
1195		}
1196
1197		lc_name = zend_string_tolower(class_name);
1198		if(!unserialize_allowed_class(lc_name, var_hash)) {
1199			zend_string_release_ex(lc_name, 0);
1200			if (!zend_is_valid_class_name(class_name)) {
1201				zend_string_release_ex(class_name, 0);
1202				return 0;
1203			}
1204			incomplete_class = 1;
1205			ce = PHP_IC_ENTRY;
1206			break;
1207		}
1208
1209		if ((*var_hash)->allowed_classes && ZSTR_HAS_CE_CACHE(class_name)) {
1210			ce = ZSTR_GET_CE_CACHE(class_name);
1211			if (ce) {
1212				zend_string_release_ex(lc_name, 0);
1213				break;
1214			}
1215		}
1216
1217		ce = zend_hash_find_ptr(EG(class_table), lc_name);
1218		if (ce
1219		 && (ce->ce_flags & ZEND_ACC_LINKED)
1220		 && !(ce->ce_flags & ZEND_ACC_ANON_CLASS)) {
1221			zend_string_release_ex(lc_name, 0);
1222			break;
1223		}
1224
1225		if (!ZSTR_HAS_CE_CACHE(class_name) && !zend_is_valid_class_name(class_name)) {
1226			zend_string_release_ex(lc_name, 0);
1227			zend_string_release_ex(class_name, 0);
1228			return 0;
1229		}
1230
1231		/* Try to find class directly */
1232		BG(serialize_lock)++;
1233		ce = zend_lookup_class_ex(class_name, lc_name, 0);
1234		BG(serialize_lock)--;
1235		zend_string_release_ex(lc_name, 0);
1236		if (EG(exception)) {
1237			zend_string_release_ex(class_name, 0);
1238			return 0;
1239		}
1240
1241		if (ce) {
1242			break;
1243		}
1244
1245		/* Check for unserialize callback */
1246		if ((PG(unserialize_callback_func) == NULL) || (PG(unserialize_callback_func)[0] == '\0')) {
1247			incomplete_class = 1;
1248			ce = PHP_IC_ENTRY;
1249			break;
1250		}
1251
1252		/* Call unserialize callback */
1253		ZVAL_STRING(&user_func, PG(unserialize_callback_func));
1254
1255		ZVAL_STR(&args[0], class_name);
1256		BG(serialize_lock)++;
1257		call_user_function(NULL, NULL, &user_func, &retval, 1, args);
1258		BG(serialize_lock)--;
1259		zval_ptr_dtor(&retval);
1260
1261		if (EG(exception)) {
1262			zend_string_release_ex(class_name, 0);
1263			zval_ptr_dtor(&user_func);
1264			return 0;
1265		}
1266
1267		/* The callback function may have defined the class */
1268		BG(serialize_lock)++;
1269		if ((ce = zend_lookup_class(class_name)) == NULL) {
1270			php_error_docref(NULL, E_WARNING, "Function %s() hasn't defined the class it was called for", Z_STRVAL(user_func));
1271			incomplete_class = 1;
1272			ce = PHP_IC_ENTRY;
1273		}
1274		BG(serialize_lock)--;
1275
1276		zval_ptr_dtor(&user_func);
1277	} while (0);
1278
1279	*p = YYCURSOR;
1280
1281	if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) {
1282		zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed",
1283			ZSTR_VAL(ce->name));
1284		zend_string_release_ex(class_name, 0);
1285		return 0;
1286	}
1287
1288	if (custom_object) {
1289		int ret;
1290
1291		ret = object_custom(UNSERIALIZE_PASSTHRU, ce);
1292
1293		if (ret && incomplete_class) {
1294			php_store_class_name(rval, class_name);
1295		}
1296		zend_string_release_ex(class_name, 0);
1297		return ret;
1298	}
1299
1300	if (*p >= max - 2) {
1301		zend_error(E_WARNING, "Bad unserialize data");
1302		zend_string_release_ex(class_name, 0);
1303		return 0;
1304	}
1305
1306	elements = parse_iv2(*p + 2, p);
1307	if (elements < 0 || IS_FAKE_ELEM_COUNT(elements, max - YYCURSOR)) {
1308		zend_string_release_ex(class_name, 0);
1309		return 0;
1310	}
1311
1312	YYCURSOR = *p;
1313
1314	if (*(YYCURSOR) != ':') {
1315		return 0;
1316	}
1317	if (*(YYCURSOR+1) != '{') {
1318		*p = YYCURSOR+1;
1319		return 0;
1320	}
1321
1322	*p += 2;
1323
1324	has_unserialize = !incomplete_class && ce->__unserialize;
1325
1326	/* If this class implements Serializable, it should not land here but in object_custom().
1327	 * The passed string obviously doesn't descend from the regular serializer. However, if
1328	 * there is both Serializable::unserialize() and __unserialize(), then both may be used,
1329	 * depending on the serialization format. */
1330	if (ce->serialize != NULL && !has_unserialize) {
1331		zend_error(E_WARNING, "Erroneous data format for unserializing '%s'", ZSTR_VAL(ce->name));
1332		zend_string_release_ex(class_name, 0);
1333		return 0;
1334	}
1335
1336	if (object_init_ex(rval, ce) == FAILURE) {
1337		zend_string_release_ex(class_name, 0);
1338		return 0;
1339	}
1340
1341	if (incomplete_class) {
1342		php_store_class_name(rval, class_name);
1343	}
1344	zend_string_release_ex(class_name, 0);
1345
1346	return object_common(UNSERIALIZE_PASSTHRU, elements, has_unserialize);
1347}
1348
1349"E:" uiv ":" ["] {
1350	if (!var_hash) return 0;
1351
1352	size_t len = parse_uiv(start + 2);
1353	size_t maxlen = max - YYCURSOR;
1354	if (maxlen < len || len == 0) {
1355		*p = start + 2;
1356		return 0;
1357	}
1358
1359	char *str = (char *) YYCURSOR;
1360	YYCURSOR += len;
1361
1362	if (*(YYCURSOR) != '"') {
1363		*p = YYCURSOR;
1364		return 0;
1365	}
1366	if (*(YYCURSOR+1) != ';') {
1367		*p = YYCURSOR+1;
1368		return 0;
1369	}
1370
1371	char *colon_ptr = memchr(str, ':', len);
1372	if (colon_ptr == NULL) {
1373		php_error_docref(NULL, E_WARNING, "Invalid enum name '%.*s' (missing colon)", (int) len, str);
1374		return 0;
1375	}
1376	size_t colon_pos = colon_ptr - str;
1377
1378	zend_string *enum_name = zend_string_init(str, colon_pos, 0);
1379	zend_string *case_name = zend_string_init(&str[colon_pos + 1], len - colon_pos - 1, 0);
1380
1381	if (!zend_is_valid_class_name(enum_name)) {
1382		goto fail;
1383	}
1384
1385	zend_class_entry *ce = zend_lookup_class(enum_name);
1386	if (!ce) {
1387		php_error_docref(NULL, E_WARNING, "Class '%s' not found", ZSTR_VAL(enum_name));
1388		goto fail;
1389	}
1390	if (!(ce->ce_flags & ZEND_ACC_ENUM)) {
1391		php_error_docref(NULL, E_WARNING, "Class '%s' is not an enum", ZSTR_VAL(enum_name));
1392		goto fail;
1393	}
1394
1395	YYCURSOR += 2;
1396	*p = YYCURSOR;
1397
1398	zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), case_name);
1399	if (!c) {
1400		php_error_docref(NULL, E_WARNING, "Undefined constant %s::%s", ZSTR_VAL(enum_name), ZSTR_VAL(case_name));
1401		goto fail;
1402	}
1403
1404	if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) {
1405		php_error_docref(NULL, E_WARNING, "%s::%s is not an enum case", ZSTR_VAL(enum_name), ZSTR_VAL(case_name));
1406		goto fail;
1407	}
1408
1409	zend_string_release_ex(enum_name, 0);
1410	zend_string_release_ex(case_name, 0);
1411
1412	zval *value = &c->value;
1413	if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
1414		if (zval_update_constant_ex(value, c->ce) == FAILURE) {
1415			return 0;
1416		}
1417	}
1418	ZEND_ASSERT(Z_TYPE_P(value) == IS_OBJECT);
1419	ZVAL_COPY(rval, value);
1420
1421	return 1;
1422
1423fail:
1424	zend_string_release_ex(enum_name, 0);
1425	zend_string_release_ex(case_name, 0);
1426	return 0;
1427}
1428
1429"}" {
1430	/* this is the case where we have less data than planned */
1431	php_error_docref(NULL, E_WARNING, "Unexpected end of serialized data");
1432	return 0; /* not sure if it should be 0 or 1 here? */
1433}
1434
1435any	{ return 0; }
1436
1437*/
1438
1439	return 0;
1440}
1441