xref: /PHP-8.2/ext/standard/var_unserializer.re (revision f2e8c5da)
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	if (end < *p) {
330		zend_string_efree(str);
331		return NULL;
332	}
333
334	for (i = 0; i < len; i++) {
335		if (*p >= end) {
336			zend_string_efree(str);
337			return NULL;
338		}
339		if (**p != '\\') {
340			ZSTR_VAL(str)[i] = (char)**p;
341		} else {
342			unsigned char ch = 0;
343
344			for (j = 0; j < 2; j++) {
345				(*p)++;
346				if (**p >= '0' && **p <= '9') {
347					ch = (ch << 4) + (**p -'0');
348				} else if (**p >= 'a' && **p <= 'f') {
349					ch = (ch << 4) + (**p -'a'+10);
350				} else if (**p >= 'A' && **p <= 'F') {
351					ch = (ch << 4) + (**p -'A'+10);
352				} else {
353					zend_string_efree(str);
354					return NULL;
355				}
356			}
357			ZSTR_VAL(str)[i] = (char)ch;
358		}
359		(*p)++;
360	}
361	ZSTR_VAL(str)[i] = 0;
362	ZSTR_LEN(str) = i;
363	return str;
364}
365
366static inline int unserialize_allowed_class(
367		zend_string *lcname, php_unserialize_data_t *var_hashx)
368{
369	HashTable *classes = (*var_hashx)->allowed_classes;
370
371	if(classes == NULL) {
372		return 1;
373	}
374	if(!zend_hash_num_elements(classes)) {
375		return 0;
376	}
377
378	return zend_hash_exists(classes, lcname);
379}
380
381#define YYFILL(n) do { } while (0)
382#define YYCTYPE unsigned char
383#define YYCURSOR cursor
384#define YYLIMIT limit
385#define YYMARKER marker
386
387
388/*!re2c
389uiv = [0-9]+;
390iv = [+-]? [0-9]+;
391nv = [+-]? ([0-9]* "." [0-9]+|[0-9]+ "." [0-9]*);
392nvexp = (iv | nv) [eE] iv;
393any = [\000-\377];
394object = [OC];
395*/
396
397
398
399static inline zend_long parse_iv2(const unsigned char *p, const unsigned char **q)
400{
401	zend_ulong result = 0;
402	zend_ulong neg = 0;
403	const unsigned char *start;
404
405	if (*p == '-') {
406		neg = 1;
407		p++;
408	} else if (UNEXPECTED(*p == '+')) {
409		p++;
410	}
411
412	while (UNEXPECTED(*p == '0')) {
413		p++;
414	}
415
416	start = p;
417
418	while (*p >= '0' && *p <= '9') {
419		result = result * 10 + ((zend_ulong)(*p) - '0');
420		p++;
421	}
422
423	if (q) {
424		*q = p;
425	}
426
427	/* number too long or overflow */
428	if (UNEXPECTED(p - start > MAX_LENGTH_OF_LONG - 1)
429	 || (SIZEOF_ZEND_LONG == 4
430	 	&& UNEXPECTED(p - start == MAX_LENGTH_OF_LONG - 1)
431	 	&& UNEXPECTED(*start > '2'))
432	 || UNEXPECTED(result > ZEND_LONG_MAX + neg)) {
433		php_error_docref(NULL, E_WARNING, "Numerical result out of range");
434		return (!neg) ? ZEND_LONG_MAX : ZEND_LONG_MIN;
435	}
436
437	return (zend_long) ((!neg) ? result : -result);
438}
439
440static inline zend_long parse_iv(const unsigned char *p)
441{
442	return parse_iv2(p, NULL);
443}
444
445/* no need to check for length - re2c already did */
446static inline size_t parse_uiv(const unsigned char *p)
447{
448	unsigned char cursor;
449	size_t result = 0;
450
451	while (1) {
452		cursor = *p;
453		if (cursor >= '0' && cursor <= '9') {
454			result = result * 10 + (size_t)(cursor - (unsigned char)'0');
455		} else {
456			break;
457		}
458		p++;
459	}
460	return result;
461}
462
463#define UNSERIALIZE_PARAMETER zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash
464#define UNSERIALIZE_PASSTHRU rval, p, max, var_hash
465
466static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER);
467
468static zend_always_inline int process_nested_array_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements)
469{
470	if (var_hash) {
471		if ((*var_hash)->max_depth > 0 && (*var_hash)->cur_depth >= (*var_hash)->max_depth) {
472			php_error_docref(NULL, E_WARNING,
473				"Maximum depth of " ZEND_LONG_FMT " exceeded. "
474				"The depth limit can be changed using the max_depth unserialize() option "
475				"or the unserialize_max_depth ini setting",
476				(*var_hash)->max_depth);
477			return 0;
478		}
479		(*var_hash)->cur_depth++;
480	}
481
482	while (elements-- > 0) {
483		zval key, *data;
484		zend_ulong idx;
485
486		ZVAL_UNDEF(&key);
487
488		if (!php_var_unserialize_internal(&key, p, max, NULL)) {
489			zval_ptr_dtor(&key);
490			goto failure;
491		}
492
493		if (Z_TYPE(key) == IS_LONG) {
494			idx = Z_LVAL(key);
495numeric_key:
496			data = zend_hash_index_lookup(ht, idx);
497			if (UNEXPECTED(Z_TYPE_INFO_P(data) != IS_NULL)) {
498				var_push_dtor_value(var_hash, data);
499				ZVAL_NULL(data);
500			}
501		} else if (Z_TYPE(key) == IS_STRING) {
502			if (UNEXPECTED(ZEND_HANDLE_NUMERIC(Z_STR(key), idx))) {
503				zval_ptr_dtor_str(&key);
504				goto numeric_key;
505			}
506			data = zend_hash_lookup(ht, Z_STR(key));
507			if (UNEXPECTED(Z_TYPE_INFO_P(data) != IS_NULL)) {
508				var_push_dtor_value(var_hash, data);
509				ZVAL_NULL(data);
510			}
511			zval_ptr_dtor_str(&key);
512		} else {
513			zval_ptr_dtor(&key);
514			goto failure;
515		}
516
517		if (!php_var_unserialize_internal(data, p, max, var_hash)) {
518			goto failure;
519		}
520
521		if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
522			(*p)--;
523			goto failure;
524		}
525	}
526
527	if (var_hash) {
528		(*var_hash)->cur_depth--;
529	}
530	return 1;
531
532failure:
533	if (var_hash) {
534		(*var_hash)->cur_depth--;
535	}
536	return 0;
537}
538
539static int is_property_visibility_changed(zend_class_entry *ce, zval *key)
540{
541	if (zend_hash_num_elements(&ce->properties_info) > 0) {
542		zend_property_info *existing_propinfo;
543		const char *unmangled_class = NULL;
544		const char *unmangled_prop;
545		size_t unmangled_prop_len;
546
547		if (UNEXPECTED(zend_unmangle_property_name_ex(Z_STR_P(key), &unmangled_class, &unmangled_prop, &unmangled_prop_len) == FAILURE)) {
548			zval_ptr_dtor_str(key);
549			return -1;
550		}
551
552		if (unmangled_class == NULL) {
553			existing_propinfo = zend_hash_find_ptr(&ce->properties_info, Z_STR_P(key));
554			if (existing_propinfo != NULL) {
555				zval_ptr_dtor_str(key);
556				ZVAL_STR_COPY(key, existing_propinfo->name);
557				return 1;
558			}
559		} else {
560			if (!strcmp(unmangled_class, "*")
561			 || !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))) {
562				existing_propinfo = zend_hash_str_find_ptr(
563					&ce->properties_info, unmangled_prop, unmangled_prop_len);
564				if (existing_propinfo != NULL) {
565					zval_ptr_dtor_str(key);
566					ZVAL_STR_COPY(key, existing_propinfo->name);
567					return 1;
568				}
569			}
570		}
571	}
572	return 0;
573}
574
575
576static zend_always_inline int process_nested_object_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, zend_object *obj)
577{
578	if (var_hash) {
579		if ((*var_hash)->max_depth > 0 && (*var_hash)->cur_depth >= (*var_hash)->max_depth) {
580			php_error_docref(NULL, E_WARNING,
581				"Maximum depth of " ZEND_LONG_FMT " exceeded. "
582				"The depth limit can be changed using the max_depth unserialize() option "
583				"or the unserialize_max_depth ini setting",
584				(*var_hash)->max_depth);
585			return 0;
586		}
587		(*var_hash)->cur_depth++;
588	}
589
590	while (elements-- > 0) {
591		zval key, *data;
592		zend_property_info *info = NULL;
593
594		ZVAL_UNDEF(&key);
595
596		if (!php_var_unserialize_internal(&key, p, max, NULL)) {
597			zval_ptr_dtor(&key);
598			goto failure;
599		}
600
601		if (EXPECTED(Z_TYPE(key) == IS_STRING)) {
602string_key:
603			data = zend_hash_find(ht, Z_STR(key));
604			if (data != NULL) {
605				if (Z_TYPE_P(data) == IS_INDIRECT) {
606declared_property:
607					/* This is a property with a declaration */
608					data = Z_INDIRECT_P(data);
609					info = zend_get_typed_property_info_for_slot(obj, data);
610					if (info) {
611						if (Z_ISREF_P(data)) {
612							/* If the value is overwritten, remove old type source from ref. */
613							ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(data), info);
614						}
615
616						if ((*var_hash)->ref_props) {
617							/* Remove old entry from ref_props table, if it exists. */
618							zend_hash_index_del(
619								(*var_hash)->ref_props, (zend_uintptr_t) data);
620						}
621					}
622					/* We may override default property value, but they are usually immutable */
623					if (Z_REFCOUNTED_P(data)) {
624						var_push_dtor_value(var_hash, data);
625					}
626					ZVAL_NULL(data);
627				} else {
628					/* Unusual override of dynamic property */
629					int ret = is_property_visibility_changed(obj->ce, &key);
630
631					if (ret > 0) {
632						goto second_try;
633					} else if (!ret) {
634						var_push_dtor_value(var_hash, data);
635						ZVAL_NULL(data);
636					} else if (ret < 0) {
637						goto failure;
638					}
639				}
640			} else {
641				int ret = is_property_visibility_changed(obj->ce, &key);
642
643				if (EXPECTED(!ret)) {
644					if (UNEXPECTED(obj->ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) {
645						zend_throw_error(NULL, "Cannot create dynamic property %s::$%s",
646							ZSTR_VAL(obj->ce->name), zend_get_unmangled_property_name(Z_STR_P(&key)));
647						zval_ptr_dtor_str(&key);
648						goto failure;
649					} else if (!(obj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) {
650						zend_error(E_DEPRECATED, "Creation of dynamic property %s::$%s is deprecated",
651							ZSTR_VAL(obj->ce->name), zend_get_unmangled_property_name(Z_STR_P(&key)));
652						if (EG(exception)) {
653							zval_ptr_dtor_str(&key);
654							goto failure;
655						}
656					}
657
658					data = zend_hash_add_new(ht, Z_STR(key), &EG(uninitialized_zval));
659				} else if (ret < 0) {
660					goto failure;
661				} else {
662second_try:
663					data = zend_hash_lookup(ht, Z_STR(key));
664					if (Z_TYPE_P(data) == IS_INDIRECT) {
665						goto declared_property;
666					} else if (UNEXPECTED(Z_TYPE_INFO_P(data) != IS_NULL)) {
667						var_push_dtor_value(var_hash, data);
668						ZVAL_NULL(data);
669					}
670				}
671			}
672			zval_ptr_dtor_str(&key);
673		} else if (Z_TYPE(key) == IS_LONG) {
674			/* object properties should include no integers */
675			convert_to_string(&key);
676			goto string_key;
677		} else {
678			zval_ptr_dtor(&key);
679			goto failure;
680		}
681
682		if (!php_var_unserialize_internal(data, p, max, var_hash)) {
683			if (info && Z_ISREF_P(data)) {
684				/* Add type source even if we failed to unserialize.
685				 * The data is still stored in the property. */
686				ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info);
687			}
688			goto failure;
689		}
690
691		if (UNEXPECTED(info)) {
692			if (!zend_verify_prop_assignable_by_ref(info, data, /* strict */ 1)) {
693				zval_ptr_dtor(data);
694				ZVAL_UNDEF(data);
695				goto failure;
696			}
697
698			if (Z_ISREF_P(data)) {
699				ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info);
700			} else {
701				/* Remember to which property this slot belongs, so we can add a
702				 * type source if it is turned into a reference lateron. */
703				if (!(*var_hash)->ref_props) {
704					(*var_hash)->ref_props = emalloc(sizeof(HashTable));
705					zend_hash_init((*var_hash)->ref_props, 8, NULL, NULL, 0);
706				}
707				zend_hash_index_update_ptr(
708					(*var_hash)->ref_props, (zend_uintptr_t) data, info);
709			}
710		}
711
712		if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
713			(*p)--;
714			goto failure;
715		}
716	}
717
718	if (var_hash) {
719		(*var_hash)->cur_depth--;
720	}
721	return 1;
722
723failure:
724	if (var_hash) {
725		(*var_hash)->cur_depth--;
726	}
727	return 0;
728}
729
730static inline int finish_nested_data(UNSERIALIZE_PARAMETER)
731{
732	if (*p >= max || **p != '}') {
733		return 0;
734	}
735
736	(*p)++;
737	return 1;
738}
739
740static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
741{
742	zend_long datalen;
743
744	datalen = parse_iv2((*p) + 2, p);
745
746	if (max - (*p) < 2) {
747		return 0;
748	}
749
750	if ((*p)[0] != ':') {
751		return 0;
752	}
753
754	if ((*p)[1] != '{') {
755		(*p) += 1;
756		return 0;
757	}
758
759	(*p) += 2;
760
761	if (datalen < 0 || (max - (*p)) <= datalen) {
762		zend_error(E_WARNING, "Insufficient data for unserializing - " ZEND_LONG_FMT " required, " ZEND_LONG_FMT " present", datalen, (zend_long)(max - (*p)));
763		return 0;
764	}
765
766	/* Check that '}' is present before calling ce->unserialize() to mitigate issues
767	 * with unserialize reading past the end of the passed buffer if the string is not
768	 * appropriately terminated (usually NUL terminated, but '}' is also sufficient.) */
769	if ((*p)[datalen] != '}') {
770		(*p) += datalen;
771		return 0;
772	}
773
774	if (ce->unserialize == NULL) {
775		zend_error(E_WARNING, "Class %s has no unserializer", ZSTR_VAL(ce->name));
776		object_init_ex(rval, ce);
777	} else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) {
778		return 0;
779	}
780
781	(*p) += datalen + 1; /* +1 for '}' */
782	return 1;
783}
784
785#ifdef PHP_WIN32
786# pragma optimize("", off)
787#endif
788static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool has_unserialize)
789{
790	HashTable *ht;
791	bool has_wakeup;
792
793	if (has_unserialize) {
794		zval ary, *tmp;
795
796		if (elements >= HT_MAX_SIZE) {
797			return 0;
798		}
799
800		array_init_size(&ary, elements);
801		/* Avoid reallocation due to packed -> mixed conversion. */
802		zend_hash_real_init_mixed(Z_ARRVAL(ary));
803		if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL(ary), elements)) {
804			ZVAL_DEREF(rval);
805			GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED);
806			zval_ptr_dtor(&ary);
807			return 0;
808		}
809
810		/* Delay __unserialize() call until end of serialization. We use two slots here to
811		 * store both the object and the unserialized data array. */
812		ZVAL_DEREF(rval);
813		tmp = tmp_var(var_hash, 2);
814		ZVAL_COPY(tmp, rval);
815		Z_EXTRA_P(tmp) = VAR_UNSERIALIZE_FLAG;
816		tmp++;
817		ZVAL_COPY_VALUE(tmp, &ary);
818
819		return finish_nested_data(UNSERIALIZE_PASSTHRU);
820	}
821
822	has_wakeup = Z_OBJCE_P(rval) != PHP_IC_ENTRY
823		&& zend_hash_exists(&Z_OBJCE_P(rval)->function_table, ZSTR_KNOWN(ZEND_STR_WAKEUP));
824
825	ht = Z_OBJPROP_P(rval);
826	if (elements >= (zend_long)(HT_MAX_SIZE - zend_hash_num_elements(ht))) {
827		return 0;
828	}
829
830	zend_hash_extend(ht, zend_hash_num_elements(ht) + elements, HT_FLAGS(ht) & HASH_FLAG_PACKED);
831	if (!process_nested_object_data(UNSERIALIZE_PASSTHRU, ht, elements, Z_OBJ_P(rval))) {
832		if (has_wakeup) {
833			ZVAL_DEREF(rval);
834			GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED);
835		}
836		return 0;
837	}
838
839	ZVAL_DEREF(rval);
840	if (has_wakeup) {
841		/* Delay __wakeup call until end of serialization */
842		zval *wakeup_var = var_tmp_var(var_hash);
843		ZVAL_COPY(wakeup_var, rval);
844		Z_EXTRA_P(wakeup_var) = VAR_WAKEUP_FLAG;
845	}
846
847	return finish_nested_data(UNSERIALIZE_PASSTHRU);
848}
849#ifdef PHP_WIN32
850# pragma optimize("", on)
851#endif
852
853PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
854{
855	var_entries *orig_var_entries = (*var_hash)->last;
856	zend_long orig_used_slots = orig_var_entries ? orig_var_entries->used_slots : 0;
857	int result;
858
859	result = php_var_unserialize_internal(UNSERIALIZE_PASSTHRU);
860
861	if (!result) {
862		/* If the unserialization failed, mark all elements that have been added to var_hash
863		 * as NULL. This will forbid their use by other unserialize() calls in the same
864		 * unserialization context. */
865		var_entries *e = orig_var_entries;
866		zend_long s = orig_used_slots;
867		while (e) {
868			for (; s < e->used_slots; s++) {
869				e->data[s] = NULL;
870			}
871
872			e = e->next;
873			s = 0;
874		}
875	}
876
877	return result;
878}
879
880static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER)
881{
882	const unsigned char *cursor, *limit, *marker, *start;
883	zval *rval_ref;
884
885	limit = max;
886	cursor = *p;
887
888	if (YYCURSOR >= YYLIMIT) {
889		return 0;
890	}
891
892	if (var_hash && (*p)[0] != 'R') {
893		var_push(var_hash, rval);
894	}
895
896	start = cursor;
897
898/*!re2c
899
900"R:" uiv ";"		{
901	zend_long id;
902
903 	*p = YYCURSOR;
904	if (!var_hash) return 0;
905
906	id = parse_uiv(start + 2) - 1;
907	if (id == -1 || (rval_ref = var_access(var_hash, id)) == NULL) {
908		return 0;
909	}
910
911	if (rval_ref == rval || (Z_ISREF_P(rval_ref) && Z_REFVAL_P(rval_ref) == rval)) {
912		return 0;
913	}
914
915	if (!Z_ISREF_P(rval_ref)) {
916		zend_property_info *info = NULL;
917		if ((*var_hash)->ref_props) {
918			info = zend_hash_index_find_ptr((*var_hash)->ref_props, (zend_uintptr_t) rval_ref);
919		}
920		ZVAL_NEW_REF(rval_ref, rval_ref);
921		if (info) {
922			ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(rval_ref), info);
923		}
924	}
925
926	ZVAL_COPY(rval, rval_ref);
927
928	return 1;
929}
930
931"r:" uiv ";"		{
932	zend_long id;
933
934 	*p = YYCURSOR;
935	if (!var_hash) return 0;
936
937	id = parse_uiv(start + 2) - 1;
938	if (id == -1 || (rval_ref = var_access(var_hash, id)) == NULL) {
939		return 0;
940	}
941
942	if (rval_ref == rval) {
943		return 0;
944	}
945
946	ZVAL_DEREF(rval_ref);
947	if (Z_TYPE_P(rval_ref) != IS_OBJECT) {
948		return 0;
949	}
950
951	ZVAL_COPY(rval, rval_ref);
952
953	return 1;
954}
955
956"N;"	{
957	*p = YYCURSOR;
958	ZVAL_NULL(rval);
959	return 1;
960}
961
962"b:0;"	{
963	*p = YYCURSOR;
964	ZVAL_FALSE(rval);
965	return 1;
966}
967
968"b:1;"	{
969	*p = YYCURSOR;
970	ZVAL_TRUE(rval);
971	return 1;
972}
973
974"i:" iv ";"	{
975#if SIZEOF_ZEND_LONG == 4
976	int digits = YYCURSOR - start - 3;
977
978	if (start[2] == '-' || start[2] == '+') {
979		digits--;
980	}
981
982	/* Use double for large zend_long values that were serialized on a 64-bit system */
983	if (digits >= MAX_LENGTH_OF_LONG - 1) {
984		if (digits == MAX_LENGTH_OF_LONG - 1) {
985			int cmp = strncmp((char*)YYCURSOR - MAX_LENGTH_OF_LONG, long_min_digits, MAX_LENGTH_OF_LONG - 1);
986
987			if (!(cmp < 0 || (cmp == 0 && start[2] == '-'))) {
988				goto use_double;
989			}
990		} else {
991			goto use_double;
992		}
993	}
994#endif
995	*p = YYCURSOR;
996	ZVAL_LONG(rval, parse_iv(start + 2));
997	return 1;
998}
999
1000"d:" ("NAN" | "-"? "INF") ";"	{
1001	*p = YYCURSOR;
1002
1003	if (!strncmp((char*)start + 2, "NAN", 3)) {
1004		ZVAL_DOUBLE(rval, ZEND_NAN);
1005	} else if (!strncmp((char*)start + 2, "INF", 3)) {
1006		ZVAL_DOUBLE(rval, ZEND_INFINITY);
1007	} else if (!strncmp((char*)start + 2, "-INF", 4)) {
1008		ZVAL_DOUBLE(rval, -ZEND_INFINITY);
1009	} else {
1010		ZVAL_NULL(rval);
1011	}
1012
1013	return 1;
1014}
1015
1016"d:" (iv | nv | nvexp) ";"	{
1017#if SIZEOF_ZEND_LONG == 4
1018use_double:
1019#endif
1020	*p = YYCURSOR;
1021	ZVAL_DOUBLE(rval, zend_strtod((const char *)start + 2, NULL));
1022	return 1;
1023}
1024
1025"s:" uiv ":" ["] 	{
1026	size_t len, maxlen;
1027	char *str;
1028
1029	len = parse_uiv(start + 2);
1030	maxlen = max - YYCURSOR;
1031	if (maxlen < len) {
1032		*p = start + 2;
1033		return 0;
1034	}
1035
1036	str = (char*)YYCURSOR;
1037
1038	YYCURSOR += len;
1039
1040	if (*(YYCURSOR) != '"') {
1041		*p = YYCURSOR;
1042		return 0;
1043	}
1044
1045	if (*(YYCURSOR + 1) != ';') {
1046		*p = YYCURSOR + 1;
1047		return 0;
1048	}
1049
1050	YYCURSOR += 2;
1051	*p = YYCURSOR;
1052
1053	if (!var_hash) {
1054		/* Array or object key unserialization */
1055		ZVAL_STR(rval, zend_string_init_existing_interned(str, len, 0));
1056	} else {
1057		ZVAL_STRINGL_FAST(rval, str, len);
1058	}
1059	return 1;
1060}
1061
1062"S:" uiv ":" ["] 	{
1063	size_t len, maxlen;
1064	zend_string *str;
1065
1066	len = parse_uiv(start + 2);
1067	maxlen = max - YYCURSOR;
1068	if (maxlen < len) {
1069		*p = start + 2;
1070		return 0;
1071	}
1072
1073	if ((str = unserialize_str(&YYCURSOR, len, maxlen)) == NULL) {
1074		return 0;
1075	}
1076
1077	if (*(YYCURSOR) != '"') {
1078		zend_string_efree(str);
1079		*p = YYCURSOR;
1080		return 0;
1081	}
1082
1083	if (*(YYCURSOR + 1) != ';') {
1084		efree(str);
1085		*p = YYCURSOR + 1;
1086		return 0;
1087	}
1088
1089	YYCURSOR += 2;
1090	*p = YYCURSOR;
1091
1092	ZVAL_STR(rval, str);
1093	return 1;
1094}
1095
1096"a:" uiv ":" "{" {
1097	zend_long elements = parse_iv(start + 2);
1098	/* use iv() not uiv() in order to check data range */
1099	*p = YYCURSOR;
1100    if (!var_hash) return 0;
1101
1102	if (elements < 0 || elements >= HT_MAX_SIZE || IS_FAKE_ELEM_COUNT(elements, max - YYCURSOR)) {
1103		return 0;
1104	}
1105
1106	if (elements) {
1107		array_init_size(rval, elements);
1108		/* we can't convert from packed to hash during unserialization, because
1109		   reference to some zvals might be kept in var_hash (to support references) */
1110		zend_hash_real_init_mixed(Z_ARRVAL_P(rval));
1111	} else {
1112		ZVAL_EMPTY_ARRAY(rval);
1113		return finish_nested_data(UNSERIALIZE_PASSTHRU);
1114	}
1115
1116	/* The array may contain references to itself, in which case we'll be modifying an
1117	 * rc>1 array. This is okay, since the array is, ostensibly, only visible to
1118	 * unserialize (in practice unserialization handlers also see it). Ideally we should
1119	 * prohibit "r:" references to non-objects, as we only generate them for objects. */
1120	HT_ALLOW_COW_VIOLATION(Z_ARRVAL_P(rval));
1121
1122	if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements)) {
1123		return 0;
1124	}
1125
1126	return finish_nested_data(UNSERIALIZE_PASSTHRU);
1127}
1128
1129object ":" uiv ":" ["]	{
1130	size_t len, maxlen;
1131	zend_long elements;
1132	char *str;
1133	zend_string *class_name;
1134	zend_class_entry *ce;
1135	bool incomplete_class = 0;
1136	bool custom_object = 0;
1137	bool has_unserialize = 0;
1138
1139	zval user_func;
1140	zval retval;
1141	zval args[1];
1142
1143    if (!var_hash) return 0;
1144	if (*start == 'C') {
1145		custom_object = 1;
1146	}
1147
1148	len = parse_uiv(start + 2);
1149	maxlen = max - YYCURSOR;
1150	if (maxlen < len || len == 0) {
1151		*p = start + 2;
1152		return 0;
1153	}
1154
1155	str = (char*)YYCURSOR;
1156
1157	YYCURSOR += len;
1158
1159	if (*(YYCURSOR) != '"') {
1160		*p = YYCURSOR;
1161		return 0;
1162	}
1163	if (*(YYCURSOR+1) != ':') {
1164		*p = YYCURSOR+1;
1165		return 0;
1166	}
1167
1168	if (len == 0) {
1169		/* empty class names are not allowed */
1170		return 0;
1171	}
1172
1173	if (str[0] == '\000') {
1174		/* runtime definition keys are not allowed */
1175		return 0;
1176	}
1177
1178	if (str[0] == '\\') {
1179		/* class name can't start from namespace separator */
1180		return 0;
1181	}
1182
1183	class_name = zend_string_init_interned(str, len, 0);
1184
1185	do {
1186		zend_string *lc_name;
1187
1188		if (!(*var_hash)->allowed_classes && ZSTR_HAS_CE_CACHE(class_name)) {
1189			ce = ZSTR_GET_CE_CACHE(class_name);
1190			if (ce) {
1191				break;
1192			}
1193		}
1194
1195		lc_name = zend_string_tolower(class_name);
1196		if(!unserialize_allowed_class(lc_name, var_hash)) {
1197			zend_string_release_ex(lc_name, 0);
1198			if (!zend_is_valid_class_name(class_name)) {
1199				zend_string_release_ex(class_name, 0);
1200				return 0;
1201			}
1202			incomplete_class = 1;
1203			ce = PHP_IC_ENTRY;
1204			break;
1205		}
1206
1207		if ((*var_hash)->allowed_classes && ZSTR_HAS_CE_CACHE(class_name)) {
1208			ce = ZSTR_GET_CE_CACHE(class_name);
1209			if (ce) {
1210				zend_string_release_ex(lc_name, 0);
1211				break;
1212			}
1213		}
1214
1215		ce = zend_hash_find_ptr(EG(class_table), lc_name);
1216		if (ce
1217		 && (ce->ce_flags & ZEND_ACC_LINKED)
1218		 && !(ce->ce_flags & ZEND_ACC_ANON_CLASS)) {
1219			zend_string_release_ex(lc_name, 0);
1220			break;
1221		}
1222
1223		if (!ZSTR_HAS_CE_CACHE(class_name) && !zend_is_valid_class_name(class_name)) {
1224			zend_string_release_ex(lc_name, 0);
1225			zend_string_release_ex(class_name, 0);
1226			return 0;
1227		}
1228
1229		/* Try to find class directly */
1230		BG(serialize_lock)++;
1231		ce = zend_lookup_class_ex(class_name, lc_name, 0);
1232		BG(serialize_lock)--;
1233		zend_string_release_ex(lc_name, 0);
1234		if (EG(exception)) {
1235			zend_string_release_ex(class_name, 0);
1236			return 0;
1237		}
1238
1239		if (ce) {
1240			break;
1241		}
1242
1243		/* Check for unserialize callback */
1244		if ((PG(unserialize_callback_func) == NULL) || (PG(unserialize_callback_func)[0] == '\0')) {
1245			incomplete_class = 1;
1246			ce = PHP_IC_ENTRY;
1247			break;
1248		}
1249
1250		/* Call unserialize callback */
1251		ZVAL_STRING(&user_func, PG(unserialize_callback_func));
1252
1253		ZVAL_STR(&args[0], class_name);
1254		BG(serialize_lock)++;
1255		call_user_function(NULL, NULL, &user_func, &retval, 1, args);
1256		BG(serialize_lock)--;
1257		zval_ptr_dtor(&retval);
1258
1259		if (EG(exception)) {
1260			zend_string_release_ex(class_name, 0);
1261			zval_ptr_dtor(&user_func);
1262			return 0;
1263		}
1264
1265		/* The callback function may have defined the class */
1266		BG(serialize_lock)++;
1267		if ((ce = zend_lookup_class(class_name)) == NULL) {
1268			php_error_docref(NULL, E_WARNING, "Function %s() hasn't defined the class it was called for", Z_STRVAL(user_func));
1269			incomplete_class = 1;
1270			ce = PHP_IC_ENTRY;
1271		}
1272		BG(serialize_lock)--;
1273
1274		zval_ptr_dtor(&user_func);
1275	} while (0);
1276
1277	*p = YYCURSOR;
1278
1279	if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) {
1280		zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed",
1281			ZSTR_VAL(ce->name));
1282		zend_string_release_ex(class_name, 0);
1283		return 0;
1284	}
1285
1286	if (custom_object) {
1287		int ret;
1288
1289		ret = object_custom(UNSERIALIZE_PASSTHRU, ce);
1290
1291		if (ret && incomplete_class) {
1292			php_store_class_name(rval, class_name);
1293		}
1294		zend_string_release_ex(class_name, 0);
1295		return ret;
1296	}
1297
1298	if (*p >= max - 2) {
1299		zend_error(E_WARNING, "Bad unserialize data");
1300		zend_string_release_ex(class_name, 0);
1301		return 0;
1302	}
1303
1304	elements = parse_iv2(*p + 2, p);
1305	if (elements < 0 || IS_FAKE_ELEM_COUNT(elements, max - YYCURSOR)) {
1306		zend_string_release_ex(class_name, 0);
1307		return 0;
1308	}
1309
1310	YYCURSOR = *p;
1311
1312	if (*(YYCURSOR) != ':') {
1313		return 0;
1314	}
1315	if (*(YYCURSOR+1) != '{') {
1316		*p = YYCURSOR+1;
1317		return 0;
1318	}
1319
1320	*p += 2;
1321
1322	has_unserialize = !incomplete_class && ce->__unserialize;
1323
1324	/* If this class implements Serializable, it should not land here but in object_custom().
1325	 * The passed string obviously doesn't descend from the regular serializer. However, if
1326	 * there is both Serializable::unserialize() and __unserialize(), then both may be used,
1327	 * depending on the serialization format. */
1328	if (ce->serialize != NULL && !has_unserialize) {
1329		zend_error(E_WARNING, "Erroneous data format for unserializing '%s'", ZSTR_VAL(ce->name));
1330		zend_string_release_ex(class_name, 0);
1331		return 0;
1332	}
1333
1334	if (object_init_ex(rval, ce) == FAILURE) {
1335		zend_string_release_ex(class_name, 0);
1336		return 0;
1337	}
1338
1339	if (incomplete_class) {
1340		php_store_class_name(rval, class_name);
1341	}
1342	zend_string_release_ex(class_name, 0);
1343
1344	return object_common(UNSERIALIZE_PASSTHRU, elements, has_unserialize);
1345}
1346
1347"E:" uiv ":" ["] {
1348	if (!var_hash) return 0;
1349
1350	size_t len = parse_uiv(start + 2);
1351	size_t maxlen = max - YYCURSOR;
1352	if (maxlen < len || len == 0) {
1353		*p = start + 2;
1354		return 0;
1355	}
1356
1357	char *str = (char *) YYCURSOR;
1358	YYCURSOR += len;
1359
1360	if (*(YYCURSOR) != '"') {
1361		*p = YYCURSOR;
1362		return 0;
1363	}
1364	if (*(YYCURSOR+1) != ';') {
1365		*p = YYCURSOR+1;
1366		return 0;
1367	}
1368
1369	char *colon_ptr = memchr(str, ':', len);
1370	if (colon_ptr == NULL) {
1371		php_error_docref(NULL, E_WARNING, "Invalid enum name '%.*s' (missing colon)", (int) len, str);
1372		return 0;
1373	}
1374	size_t colon_pos = colon_ptr - str;
1375
1376	zend_string *enum_name = zend_string_init(str, colon_pos, 0);
1377	zend_string *case_name = zend_string_init(&str[colon_pos + 1], len - colon_pos - 1, 0);
1378
1379	if (!zend_is_valid_class_name(enum_name)) {
1380		goto fail;
1381	}
1382
1383	zend_class_entry *ce = zend_lookup_class(enum_name);
1384	if (!ce) {
1385		php_error_docref(NULL, E_WARNING, "Class '%s' not found", ZSTR_VAL(enum_name));
1386		goto fail;
1387	}
1388	if (!(ce->ce_flags & ZEND_ACC_ENUM)) {
1389		php_error_docref(NULL, E_WARNING, "Class '%s' is not an enum", ZSTR_VAL(enum_name));
1390		goto fail;
1391	}
1392
1393	YYCURSOR += 2;
1394	*p = YYCURSOR;
1395
1396	zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), case_name);
1397	if (!c) {
1398		php_error_docref(NULL, E_WARNING, "Undefined constant %s::%s", ZSTR_VAL(enum_name), ZSTR_VAL(case_name));
1399		goto fail;
1400	}
1401
1402	if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) {
1403		php_error_docref(NULL, E_WARNING, "%s::%s is not an enum case", ZSTR_VAL(enum_name), ZSTR_VAL(case_name));
1404		goto fail;
1405	}
1406
1407	zend_string_release_ex(enum_name, 0);
1408	zend_string_release_ex(case_name, 0);
1409
1410	zval *value = &c->value;
1411	if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
1412		if (zval_update_constant_ex(value, c->ce) == FAILURE) {
1413			return 0;
1414		}
1415	}
1416	ZEND_ASSERT(Z_TYPE_P(value) == IS_OBJECT);
1417	ZVAL_COPY(rval, value);
1418
1419	return 1;
1420
1421fail:
1422	zend_string_release_ex(enum_name, 0);
1423	zend_string_release_ex(case_name, 0);
1424	return 0;
1425}
1426
1427"}" {
1428	/* this is the case where we have less data than planned */
1429	php_error_docref(NULL, E_NOTICE, "Unexpected end of serialized data");
1430	return 0; /* not sure if it should be 0 or 1 here? */
1431}
1432
1433any	{ return 0; }
1434
1435*/
1436
1437	return 0;
1438}
1439