Coverage Report

Created: 2025-09-27 06:26

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