Coverage Report

Created: 2025-11-16 06:23

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