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