Coverage Report

Created: 2026-04-01 06:49

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