Coverage Report

Created: 2026-10-28 06:20

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