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