xref: /PHP-8.4/ext/json/json_encoder.c (revision a551b99b)
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: Omar Kilani <omar@php.net>                                   |
14   |         Jakub Zelenka <bukka@php.net>                                |
15   +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
21 
22 #include "php.h"
23 #include "ext/standard/html.h"
24 #include "zend_smart_str.h"
25 #include "php_json.h"
26 #include "php_json_encoder.h"
27 #include "zend_portability.h"
28 #include <zend_exceptions.h>
29 #include "zend_enum.h"
30 #include "zend_property_hooks.h"
31 #include "zend_lazy_objects.h"
32 
33 static const char digits[] = "0123456789abcdef";
34 
php_json_check_stack_limit(void)35 static zend_always_inline bool php_json_check_stack_limit(void)
36 {
37 #ifdef ZEND_CHECK_STACK_LIMIT
38 	return zend_call_stack_overflowed(EG(stack_limit));
39 #else
40 	return false;
41 #endif
42 }
43 
php_json_determine_array_type(zval * val)44 static int php_json_determine_array_type(zval *val) /* {{{ */
45 {
46 	zend_array *myht = Z_ARRVAL_P(val);
47 
48 	if (myht) {
49 		return zend_array_is_list(myht) ? PHP_JSON_OUTPUT_ARRAY : PHP_JSON_OUTPUT_OBJECT;
50 	}
51 
52 	return PHP_JSON_OUTPUT_ARRAY;
53 }
54 /* }}} */
55 
56 /* {{{ Pretty printing support functions */
57 
php_json_pretty_print_char(smart_str * buf,int options,char c)58 static inline void php_json_pretty_print_char(smart_str *buf, int options, char c) /* {{{ */
59 {
60 	if (options & PHP_JSON_PRETTY_PRINT) {
61 		smart_str_appendc(buf, c);
62 	}
63 }
64 /* }}} */
65 
php_json_pretty_print_indent(smart_str * buf,int options,php_json_encoder * encoder)66 static inline void php_json_pretty_print_indent(smart_str *buf, int options, php_json_encoder *encoder) /* {{{ */
67 {
68 	int i;
69 
70 	if (options & PHP_JSON_PRETTY_PRINT) {
71 		for (i = 0; i < encoder->depth; ++i) {
72 			smart_str_appendl(buf, "    ", 4);
73 		}
74 	}
75 }
76 /* }}} */
77 
78 /* }}} */
79 
80 static
81 #if defined(_MSC_VER) && defined(_M_ARM64)
82 // MSVC bug: https://developercommunity.visualstudio.com/t/corrupt-optimization-on-arm64-with-Ox-/10102551
83 zend_never_inline
84 #else
85 inline
86 #endif
php_json_is_valid_double(double d)87 bool php_json_is_valid_double(double d) /* {{{ */
88 {
89 	return !zend_isinf(d) && !zend_isnan(d);
90 }
91 /* }}} */
92 
php_json_encode_double(smart_str * buf,double d,int options)93 static inline void php_json_encode_double(smart_str *buf, double d, int options) /* {{{ */
94 {
95 	size_t len;
96 	char num[ZEND_DOUBLE_MAX_LENGTH];
97 
98 	zend_gcvt(d, (int)PG(serialize_precision), '.', 'e', num);
99 	len = strlen(num);
100 	if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < ZEND_DOUBLE_MAX_LENGTH - 2) {
101 		num[len++] = '.';
102 		num[len++] = '0';
103 		num[len] = '\0';
104 	}
105 	smart_str_appendl(buf, num, len);
106 }
107 /* }}} */
108 
109 #define PHP_JSON_HASH_PROTECT_RECURSION(_tmp_ht) \
110 	do { \
111 		if (_tmp_ht) { \
112 			GC_TRY_PROTECT_RECURSION(_tmp_ht); \
113 		} \
114 	} while (0)
115 
116 #define PHP_JSON_HASH_UNPROTECT_RECURSION(_tmp_ht) \
117 	do { \
118 		if (_tmp_ht) { \
119 			GC_TRY_UNPROTECT_RECURSION(_tmp_ht); \
120 		} \
121 	} while (0)
122 
php_json_encode_array(smart_str * buf,zval * val,int options,php_json_encoder * encoder)123 static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
124 {
125 	int r, need_comma = 0;
126 	HashTable *myht, *prop_ht;
127 	zend_refcounted *recursion_rc;
128 
129 	if (php_json_check_stack_limit()) {
130 		encoder->error_code = PHP_JSON_ERROR_DEPTH;
131 		if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
132 			smart_str_appendl(buf, "null", 4);
133 		}
134 		return FAILURE;
135 	}
136 
137 	if (Z_TYPE_P(val) == IS_ARRAY) {
138 		myht = Z_ARRVAL_P(val);
139 		recursion_rc = (zend_refcounted *)myht;
140 		prop_ht = NULL;
141 		r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
142 	} else if (Z_OBJ_P(val)->properties == NULL
143 	 && Z_OBJ_HT_P(val)->get_properties_for == NULL
144 	 && Z_OBJ_HT_P(val)->get_properties == zend_std_get_properties
145 	 && Z_OBJ_P(val)->ce->num_hooked_props == 0
146 	 && !zend_object_is_lazy(Z_OBJ_P(val))) {
147 		/* Optimized version without rebuilding properties HashTable */
148 		zend_object *obj = Z_OBJ_P(val);
149 		zend_class_entry *ce = obj->ce;
150 		zend_property_info *prop_info;
151 		zval *prop;
152 
153 		if (GC_IS_RECURSIVE(obj)) {
154 			encoder->error_code = PHP_JSON_ERROR_RECURSION;
155 			smart_str_appendl(buf, "null", 4);
156 			return FAILURE;
157 		}
158 
159 		PHP_JSON_HASH_PROTECT_RECURSION(obj);
160 
161 		smart_str_appendc(buf, '{');
162 
163 		++encoder->depth;
164 
165 		for (int i = 0; i < ce->default_properties_count; i++) {
166 			prop_info = ce->properties_info_table[i];
167 			if (!prop_info) {
168 				continue;
169 			}
170 			if (ZSTR_VAL(prop_info->name)[0] == '\0' && ZSTR_LEN(prop_info->name) > 0) {
171 				/* Skip protected and private members. */
172 				continue;
173 			}
174 			prop = OBJ_PROP(obj, prop_info->offset);
175 			if (Z_TYPE_P(prop) == IS_UNDEF) {
176 				continue;
177 			}
178 
179 			if (need_comma) {
180 				smart_str_appendc(buf, ',');
181 			} else {
182 				need_comma = 1;
183 			}
184 
185 			php_json_pretty_print_char(buf, options, '\n');
186 			php_json_pretty_print_indent(buf, options, encoder);
187 
188 			if (php_json_escape_string(buf, ZSTR_VAL(prop_info->name), ZSTR_LEN(prop_info->name),
189 					options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE &&
190 					(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) &&
191 					buf->s) {
192 				ZSTR_LEN(buf->s) -= 4;
193 				smart_str_appendl(buf, "\"\"", 2);
194 			}
195 
196 			smart_str_appendc(buf, ':');
197 			php_json_pretty_print_char(buf, options, ' ');
198 
199 			if (php_json_encode_zval(buf, prop, options, encoder) == FAILURE &&
200 					!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
201 				PHP_JSON_HASH_UNPROTECT_RECURSION(obj);
202 				return FAILURE;
203 			}
204 		}
205 
206 		PHP_JSON_HASH_UNPROTECT_RECURSION(obj);
207 		if (encoder->depth > encoder->max_depth) {
208 			encoder->error_code = PHP_JSON_ERROR_DEPTH;
209 			if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
210 				return FAILURE;
211 			}
212 		}
213 		--encoder->depth;
214 
215 		if (need_comma) {
216 			php_json_pretty_print_char(buf, options, '\n');
217 			php_json_pretty_print_indent(buf, options, encoder);
218 		}
219 		smart_str_appendc(buf, '}');
220 		return SUCCESS;
221 	} else {
222 		zend_object *obj = Z_OBJ_P(val);
223 		prop_ht = myht = zend_get_properties_for(val, ZEND_PROP_PURPOSE_JSON);
224 		if (obj->ce->num_hooked_props == 0) {
225 			recursion_rc = (zend_refcounted *)prop_ht;
226 		} else {
227 			/* Protecting the object itself is fine here because myht is temporary and can't be
228 			 * referenced from a different place in the object graph. */
229 			recursion_rc = (zend_refcounted *)obj;
230 		}
231 		r = PHP_JSON_OUTPUT_OBJECT;
232 	}
233 
234 	if (recursion_rc && GC_IS_RECURSIVE(recursion_rc)) {
235 		encoder->error_code = PHP_JSON_ERROR_RECURSION;
236 		smart_str_appendl(buf, "null", 4);
237 		zend_release_properties(prop_ht);
238 		return FAILURE;
239 	}
240 
241 	PHP_JSON_HASH_PROTECT_RECURSION(recursion_rc);
242 
243 	if (r == PHP_JSON_OUTPUT_ARRAY) {
244 		smart_str_appendc(buf, '[');
245 	} else {
246 		smart_str_appendc(buf, '{');
247 	}
248 
249 	++encoder->depth;
250 
251 	uint32_t i = myht ? zend_hash_num_elements(myht) : 0;
252 
253 	if (i > 0) {
254 		zend_string *key;
255 		zval *data;
256 		zend_ulong index;
257 
258 		ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
259 			zval tmp;
260 			ZVAL_UNDEF(&tmp);
261 
262 			if (r == PHP_JSON_OUTPUT_ARRAY) {
263 				ZEND_ASSERT(Z_TYPE_P(data) != IS_PTR);
264 
265 				if (need_comma) {
266 					smart_str_appendc(buf, ',');
267 				} else {
268 					need_comma = 1;
269 				}
270 
271 				php_json_pretty_print_char(buf, options, '\n');
272 				php_json_pretty_print_indent(buf, options, encoder);
273 			} else if (r == PHP_JSON_OUTPUT_OBJECT) {
274 				if (key) {
275 					if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) {
276 						/* Skip protected and private members. */
277 						continue;
278 					}
279 
280 					/* data is IS_PTR for properties with hooks. */
281 					if (UNEXPECTED(Z_TYPE_P(data) == IS_PTR)) {
282 						zend_property_info *prop_info = Z_PTR_P(data);
283 						if ((prop_info->flags & ZEND_ACC_VIRTUAL) && !prop_info->hooks[ZEND_PROPERTY_HOOK_GET]) {
284 							continue;
285 						}
286 						zend_read_property_ex(prop_info->ce, Z_OBJ_P(val), prop_info->name, /* silent */ true, &tmp);
287 						if (EG(exception)) {
288 							PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc);
289 							zend_release_properties(prop_ht);
290 							return FAILURE;
291 						}
292 						data = &tmp;
293 					}
294 
295 					if (need_comma) {
296 						smart_str_appendc(buf, ',');
297 					} else {
298 						need_comma = 1;
299 					}
300 
301 					php_json_pretty_print_char(buf, options, '\n');
302 					php_json_pretty_print_indent(buf, options, encoder);
303 
304 					if (php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key),
305 								options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE &&
306 							(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) &&
307 							buf->s) {
308 						ZSTR_LEN(buf->s) -= 4;
309 						smart_str_appendl(buf, "\"\"", 2);
310 					}
311 				} else {
312 					if (need_comma) {
313 						smart_str_appendc(buf, ',');
314 					} else {
315 						need_comma = 1;
316 					}
317 
318 					php_json_pretty_print_char(buf, options, '\n');
319 					php_json_pretty_print_indent(buf, options, encoder);
320 
321 					smart_str_appendc(buf, '"');
322 					smart_str_append_long(buf, (zend_long) index);
323 					smart_str_appendc(buf, '"');
324 				}
325 
326 				smart_str_appendc(buf, ':');
327 				php_json_pretty_print_char(buf, options, ' ');
328 			}
329 
330 			if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
331 					!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
332 				PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc);
333 				zend_release_properties(prop_ht);
334 				zval_ptr_dtor(&tmp);
335 				return FAILURE;
336 			}
337 			zval_ptr_dtor(&tmp);
338 		} ZEND_HASH_FOREACH_END();
339 	}
340 
341 	PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc);
342 
343 	if (encoder->depth > encoder->max_depth) {
344 		encoder->error_code = PHP_JSON_ERROR_DEPTH;
345 		if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
346 			zend_release_properties(prop_ht);
347 			return FAILURE;
348 		}
349 	}
350 	--encoder->depth;
351 
352 	/* Only keep closing bracket on same line for empty arrays/objects */
353 	if (need_comma) {
354 		php_json_pretty_print_char(buf, options, '\n');
355 		php_json_pretty_print_indent(buf, options, encoder);
356 	}
357 
358 	if (r == PHP_JSON_OUTPUT_ARRAY) {
359 		smart_str_appendc(buf, ']');
360 	} else {
361 		smart_str_appendc(buf, '}');
362 	}
363 
364 	zend_release_properties(prop_ht);
365 	return SUCCESS;
366 }
367 /* }}} */
368 
php_json_escape_string(smart_str * buf,const char * s,size_t len,int options,php_json_encoder * encoder)369 zend_result php_json_escape_string(
370 		smart_str *buf, const char *s, size_t len,
371 		int options, php_json_encoder *encoder) /* {{{ */
372 {
373 	unsigned int us;
374 	size_t pos, checkpoint;
375 	char *dst;
376 
377 	if (len == 0) {
378 		smart_str_appendl(buf, "\"\"", 2);
379 		return SUCCESS;
380 	}
381 
382 	if (options & PHP_JSON_NUMERIC_CHECK) {
383 		double d;
384 		int type;
385 		zend_long p;
386 
387 		if ((type = is_numeric_string(s, len, &p, &d, 0)) != 0) {
388 			if (type == IS_LONG) {
389 				smart_str_append_long(buf, p);
390 				return SUCCESS;
391 			} else if (type == IS_DOUBLE && php_json_is_valid_double(d)) {
392 				php_json_encode_double(buf, d, options);
393 				return SUCCESS;
394 			}
395 		}
396 
397 	}
398 	checkpoint = buf->s ? ZSTR_LEN(buf->s) : 0;
399 
400 	/* pre-allocate for string length plus 2 quotes */
401 	smart_str_alloc(buf, len+2, 0);
402 	smart_str_appendc(buf, '"');
403 
404 	pos = 0;
405 
406 	do {
407 		static const uint32_t charmap[8] = {
408 			0xffffffff, 0x500080c4, 0x10000000, 0x00000000,
409 			0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff};
410 
411 		us = (unsigned char)s[pos];
412 		if (EXPECTED(!ZEND_BIT_TEST(charmap, us))) {
413 			pos++;
414 			len--;
415 			if (len == 0) {
416 				smart_str_appendl(buf, s, pos);
417 				break;
418 			}
419 		} else {
420 			if (pos) {
421 				smart_str_appendl(buf, s, pos);
422 				s += pos;
423 				pos = 0;
424 			}
425 			us = (unsigned char)s[0];
426 			if (UNEXPECTED(us >= 0x80)) {
427 				zend_result status;
428 				us = php_next_utf8_char((unsigned char *)s, len, &pos, &status);
429 
430 				/* check whether UTF8 character is correct */
431 				if (UNEXPECTED(status != SUCCESS)) {
432 					if (options & PHP_JSON_INVALID_UTF8_IGNORE) {
433 						/* ignore invalid UTF8 character */
434 					} else if (options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) {
435 						/* Use Unicode character 'REPLACEMENT CHARACTER' (U+FFFD) */
436 						if (options & PHP_JSON_UNESCAPED_UNICODE) {
437 							smart_str_appendl(buf, "\xef\xbf\xbd", 3);
438 						} else {
439 							smart_str_appendl(buf, "\\ufffd", 6);
440 						}
441 					} else {
442 						ZSTR_LEN(buf->s) = checkpoint;
443 						encoder->error_code = PHP_JSON_ERROR_UTF8;
444 						if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
445 							smart_str_appendl(buf, "null", 4);
446 						}
447 						return FAILURE;
448 					}
449 
450 				/* Escape U+2028/U+2029 line terminators, UNLESS both
451 				   JSON_UNESCAPED_UNICODE and
452 				   JSON_UNESCAPED_LINE_TERMINATORS were provided */
453 				} else if ((options & PHP_JSON_UNESCAPED_UNICODE)
454 				    && ((options & PHP_JSON_UNESCAPED_LINE_TERMINATORS)
455 						|| us < 0x2028 || us > 0x2029)) {
456 					smart_str_appendl(buf, s, pos);
457 				} else {
458 					/* From http://en.wikipedia.org/wiki/UTF16 */
459 					if (us >= 0x10000) {
460 						unsigned int next_us;
461 
462 						us -= 0x10000;
463 						next_us = (unsigned short)((us & 0x3ff) | 0xdc00);
464 						us = (unsigned short)((us >> 10) | 0xd800);
465 						dst = smart_str_extend(buf, 6);
466 						dst[0] = '\\';
467 						dst[1] = 'u';
468 						dst[2] = digits[(us >> 12) & 0xf];
469 						dst[3] = digits[(us >> 8) & 0xf];
470 						dst[4] = digits[(us >> 4) & 0xf];
471 						dst[5] = digits[us & 0xf];
472 						us = next_us;
473 					}
474 					dst = smart_str_extend(buf, 6);
475 					dst[0] = '\\';
476 					dst[1] = 'u';
477 					dst[2] = digits[(us >> 12) & 0xf];
478 					dst[3] = digits[(us >> 8) & 0xf];
479 					dst[4] = digits[(us >> 4) & 0xf];
480 					dst[5] = digits[us & 0xf];
481 				}
482 				s += pos;
483 				len -= pos;
484 				pos = 0;
485 			} else {
486 				s++;
487 				switch (us) {
488 					case '"':
489 						if (options & PHP_JSON_HEX_QUOT) {
490 							smart_str_appendl(buf, "\\u0022", 6);
491 						} else {
492 							smart_str_appendl(buf, "\\\"", 2);
493 						}
494 						break;
495 
496 					case '\\':
497 						smart_str_appendl(buf, "\\\\", 2);
498 						break;
499 
500 					case '/':
501 						if (options & PHP_JSON_UNESCAPED_SLASHES) {
502 							smart_str_appendc(buf, '/');
503 						} else {
504 							smart_str_appendl(buf, "\\/", 2);
505 						}
506 						break;
507 
508 					case '\b':
509 						smart_str_appendl(buf, "\\b", 2);
510 						break;
511 
512 					case '\f':
513 						smart_str_appendl(buf, "\\f", 2);
514 						break;
515 
516 					case '\n':
517 						smart_str_appendl(buf, "\\n", 2);
518 						break;
519 
520 					case '\r':
521 						smart_str_appendl(buf, "\\r", 2);
522 						break;
523 
524 					case '\t':
525 						smart_str_appendl(buf, "\\t", 2);
526 						break;
527 
528 					case '<':
529 						if (options & PHP_JSON_HEX_TAG) {
530 							smart_str_appendl(buf, "\\u003C", 6);
531 						} else {
532 							smart_str_appendc(buf, '<');
533 						}
534 						break;
535 
536 					case '>':
537 						if (options & PHP_JSON_HEX_TAG) {
538 							smart_str_appendl(buf, "\\u003E", 6);
539 						} else {
540 							smart_str_appendc(buf, '>');
541 						}
542 						break;
543 
544 					case '&':
545 						if (options & PHP_JSON_HEX_AMP) {
546 							smart_str_appendl(buf, "\\u0026", 6);
547 						} else {
548 							smart_str_appendc(buf, '&');
549 						}
550 						break;
551 
552 					case '\'':
553 						if (options & PHP_JSON_HEX_APOS) {
554 							smart_str_appendl(buf, "\\u0027", 6);
555 						} else {
556 							smart_str_appendc(buf, '\'');
557 						}
558 						break;
559 
560 					default:
561 						ZEND_ASSERT(us < ' ');
562 						dst = smart_str_extend(buf, 6);
563 						dst[0] = '\\';
564 						dst[1] = 'u';
565 						dst[2] = '0';
566 						dst[3] = '0';
567 						dst[4] = digits[(us >> 4) & 0xf];
568 						dst[5] = digits[us & 0xf];
569 						break;
570 				}
571 				len--;
572 			}
573 		}
574 	} while (len);
575 
576 	smart_str_appendc(buf, '"');
577 
578 	return SUCCESS;
579 }
580 /* }}} */
581 
php_json_encode_serializable_object(smart_str * buf,zval * val,int options,php_json_encoder * encoder)582 static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
583 {
584 	zend_class_entry *ce = Z_OBJCE_P(val);
585 	zend_object *obj = Z_OBJ_P(val);
586 	uint32_t *guard = zend_get_recursion_guard(obj);
587 	zval retval, fname;
588 	zend_result return_code;
589 
590 	ZEND_ASSERT(guard != NULL);
591 
592 	if (ZEND_GUARD_IS_RECURSIVE(guard, JSON)) {
593 		encoder->error_code = PHP_JSON_ERROR_RECURSION;
594 		if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
595 			smart_str_appendl(buf, "null", 4);
596 		}
597 		return FAILURE;
598 	}
599 
600 	ZEND_GUARD_PROTECT_RECURSION(guard, JSON);
601 
602 	ZVAL_STRING(&fname, "jsonSerialize");
603 
604 	if (FAILURE == call_user_function(NULL, val, &fname, &retval, 0, NULL) || Z_TYPE(retval) == IS_UNDEF) {
605 		if (!EG(exception)) {
606 			zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name));
607 		}
608 		zval_ptr_dtor(&fname);
609 
610 		if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
611 			smart_str_appendl(buf, "null", 4);
612 		}
613 		ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
614 		return FAILURE;
615 	}
616 
617 	if (EG(exception)) {
618 		/* Error already raised */
619 		zval_ptr_dtor(&retval);
620 		zval_ptr_dtor(&fname);
621 
622 		if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
623 			smart_str_appendl(buf, "null", 4);
624 		}
625 		ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
626 		return FAILURE;
627 	}
628 
629 	if ((Z_TYPE(retval) == IS_OBJECT) &&
630 		(Z_OBJ(retval) == Z_OBJ_P(val))) {
631 		/* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
632 		ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
633 		return_code = php_json_encode_array(buf, &retval, options, encoder);
634 	} else {
635 		/* All other types, encode as normal */
636 		return_code = php_json_encode_zval(buf, &retval, options, encoder);
637 		ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
638 	}
639 
640 	zval_ptr_dtor(&retval);
641 	zval_ptr_dtor(&fname);
642 
643 	return return_code;
644 }
645 /* }}} */
646 
php_json_encode_serializable_enum(smart_str * buf,zval * val,int options,php_json_encoder * encoder)647 static zend_result php_json_encode_serializable_enum(smart_str *buf, zval *val, int options, php_json_encoder *encoder)
648 {
649 	zend_class_entry *ce = Z_OBJCE_P(val);
650 	if (ce->enum_backing_type == IS_UNDEF) {
651 		encoder->error_code = PHP_JSON_ERROR_NON_BACKED_ENUM;
652 		smart_str_appendc(buf, '0');
653 		return FAILURE;
654 	}
655 
656 	zval *value_zv = zend_enum_fetch_case_value(Z_OBJ_P(val));
657 	return php_json_encode_zval(buf, value_zv, options, encoder);
658 }
659 
php_json_encode_zval(smart_str * buf,zval * val,int options,php_json_encoder * encoder)660 zend_result php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
661 {
662 again:
663 	switch (Z_TYPE_P(val))
664 	{
665 		case IS_NULL:
666 			smart_str_appendl(buf, "null", 4);
667 			break;
668 
669 		case IS_TRUE:
670 			smart_str_appendl(buf, "true", 4);
671 			break;
672 		case IS_FALSE:
673 			smart_str_appendl(buf, "false", 5);
674 			break;
675 
676 		case IS_LONG:
677 			smart_str_append_long(buf, Z_LVAL_P(val));
678 			break;
679 
680 		case IS_DOUBLE:
681 			if (php_json_is_valid_double(Z_DVAL_P(val))) {
682 				php_json_encode_double(buf, Z_DVAL_P(val), options);
683 			} else {
684 				encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN;
685 				smart_str_appendc(buf, '0');
686 			}
687 			break;
688 
689 		case IS_STRING:
690 			return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder);
691 
692 		case IS_OBJECT:
693 			if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
694 				return php_json_encode_serializable_object(buf, val, options, encoder);
695 			}
696 			if (Z_OBJCE_P(val)->ce_flags & ZEND_ACC_ENUM) {
697 				return php_json_encode_serializable_enum(buf, val, options, encoder);
698 			}
699 			/* fallthrough -- Non-serializable object */
700 			ZEND_FALLTHROUGH;
701 		case IS_ARRAY: {
702 			/* Avoid modifications (and potential freeing) of the array through a reference when a
703 			 * jsonSerialize() method is invoked. */
704 			zval zv;
705 			int res;
706 			ZVAL_COPY(&zv, val);
707 			res = php_json_encode_array(buf, &zv, options, encoder);
708 			zval_ptr_dtor_nogc(&zv);
709 			return res;
710 		}
711 
712 		case IS_REFERENCE:
713 			val = Z_REFVAL_P(val);
714 			goto again;
715 
716 		default:
717 			encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
718 			if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
719 				smart_str_appendl(buf, "null", 4);
720 			}
721 			return FAILURE;
722 	}
723 
724 	return SUCCESS;
725 }
726 /* }}} */
727