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