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