Coverage Report

Created: 2026-07-25 06:39

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