Coverage Report

Created: 2025-06-13 06:43

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