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.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/info.h"
22
#include "zend_smart_str.h"
23
#include "php_json.h"
24
#include "php_json_encoder.h"
25
#include "php_json_parser.h"
26
#include "json_arginfo.h"
27
#include <zend_exceptions.h>
28
29
static PHP_MINFO_FUNCTION(json);
30
31
PHP_JSON_API zend_class_entry *php_json_serializable_ce;
32
PHP_JSON_API zend_class_entry *php_json_exception_ce;
33
34
PHP_JSON_API ZEND_DECLARE_MODULE_GLOBALS(json)
35
36
static int php_json_implement_json_serializable(zend_class_entry *interface, zend_class_entry *class_type)
37
78
{
38
78
  class_type->ce_flags |= ZEND_ACC_USE_GUARDS;
39
78
  return SUCCESS;
40
78
}
41
42
/* {{{ MINIT */
43
static PHP_MINIT_FUNCTION(json)
44
16
{
45
16
  php_json_serializable_ce = register_class_JsonSerializable();
46
16
  php_json_serializable_ce->interface_gets_implemented = php_json_implement_json_serializable;
47
48
16
  php_json_exception_ce = register_class_JsonException(zend_ce_exception);
49
50
16
  register_json_symbols(module_number);
51
52
16
  return SUCCESS;
53
16
}
54
/* }}} */
55
56
/* {{{ PHP_GINIT_FUNCTION */
57
static PHP_GINIT_FUNCTION(json)
58
16
{
59
#if defined(COMPILE_DL_JSON) && defined(ZTS)
60
  ZEND_TSRMLS_CACHE_UPDATE();
61
#endif
62
16
  json_globals->encoder_depth = 0;
63
16
  php_json_error_details_clear(&json_globals->error_details);
64
16
  json_globals->encode_max_depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
65
16
}
66
/* }}} */
67
68
static PHP_RINIT_FUNCTION(json)
69
300k
{
70
300k
  php_json_error_details_clear(&JSON_G(error_details));
71
300k
  return SUCCESS;
72
300k
}
73
74
/* {{{ json_module_entry */
75
zend_module_entry json_module_entry = {
76
  STANDARD_MODULE_HEADER,
77
  "json",
78
  ext_functions,
79
  PHP_MINIT(json),
80
  NULL,
81
  PHP_RINIT(json),
82
  NULL,
83
  PHP_MINFO(json),
84
  PHP_JSON_VERSION,
85
  PHP_MODULE_GLOBALS(json),
86
  PHP_GINIT(json),
87
  NULL,
88
  NULL,
89
  STANDARD_MODULE_PROPERTIES_EX
90
};
91
/* }}} */
92
93
#ifdef COMPILE_DL_JSON
94
#ifdef ZTS
95
ZEND_TSRMLS_CACHE_DEFINE()
96
#endif
97
ZEND_GET_MODULE(json)
98
#endif
99
100
/* {{{ PHP_MINFO_FUNCTION */
101
static PHP_MINFO_FUNCTION(json)
102
4
{
103
4
  php_info_print_table_start();
104
4
  php_info_print_table_row(2, "json support", "enabled");
105
4
  php_info_print_table_end();
106
4
}
107
/* }}} */
108
109
PHP_JSON_API zend_string *php_json_encode_string(const char *s, size_t len, int options)
110
0
{
111
0
  smart_str buf = {0};
112
0
  php_json_encoder encoder;
113
114
0
  php_json_encode_init(&encoder);
115
116
0
  if (php_json_escape_string(&buf, s, len, options, &encoder) == FAILURE) {
117
0
    smart_str_free(&buf);
118
0
    return NULL;
119
0
  }
120
121
0
  return smart_str_extract(&buf);
122
0
}
123
124
PHP_JSON_API zend_result php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth) /* {{{ */
125
0
{
126
0
  php_json_encoder encoder;
127
0
  zend_result return_code;
128
129
0
  php_json_encode_init(&encoder);
130
0
  encoder.max_depth = depth;
131
132
0
  return_code = php_json_encode_zval(buf, val, options, &encoder);
133
0
  JSON_G(error_details) = (php_json_error_details){
134
0
    .code = encoder.error_code,
135
0
    .line = 0,
136
0
    .column = 0,
137
0
  };
138
139
0
  return return_code;
140
0
}
141
/* }}} */
142
143
PHP_JSON_API zend_result php_json_encode(smart_str *buf, zval *val, int options) /* {{{ */
144
0
{
145
0
  return php_json_encode_ex(buf, val, options, JSON_G(encode_max_depth));
146
0
}
147
/* }}} */
148
149
static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ */
150
225
{
151
225
  switch(error_code) {
152
112
    case PHP_JSON_ERROR_NONE:
153
112
      return "No error";
154
0
    case PHP_JSON_ERROR_DEPTH:
155
0
      return "Maximum stack depth exceeded";
156
0
    case PHP_JSON_ERROR_STATE_MISMATCH:
157
0
      return "State mismatch (invalid or malformed JSON)";
158
0
    case PHP_JSON_ERROR_CTRL_CHAR:
159
0
      return "Control character error, possibly incorrectly encoded";
160
0
    case PHP_JSON_ERROR_SYNTAX:
161
0
      return "Syntax error";
162
26
    case PHP_JSON_ERROR_UTF8:
163
26
      return "Malformed UTF-8 characters, possibly incorrectly encoded";
164
0
    case PHP_JSON_ERROR_RECURSION:
165
0
      return "Recursion detected";
166
0
    case PHP_JSON_ERROR_INF_OR_NAN:
167
0
      return "Inf and NaN cannot be JSON encoded";
168
0
    case PHP_JSON_ERROR_UNSUPPORTED_TYPE:
169
0
      return "Type is not supported";
170
0
    case PHP_JSON_ERROR_INVALID_PROPERTY_NAME:
171
0
      return "The decoded property name is invalid";
172
0
    case PHP_JSON_ERROR_UTF16:
173
0
      return "Single unpaired UTF-16 surrogate in unicode escape";
174
87
    case PHP_JSON_ERROR_NON_BACKED_ENUM:
175
87
      return "Non-backed enums have no default serialization";
176
0
    default:
177
0
      return "Unknown error";
178
225
  }
179
225
}
180
/* }}} */
181
182
static zend_string *php_json_get_error_msg_with_location(const php_json_error_details *details) /* {{{ */
183
207
{
184
207
  const char *base_msg = php_json_get_error_msg(details->code);
185
  
186
207
  if (details->line > 0 && details->column > 0) {
187
0
    return zend_strpprintf(0, "%s near location %" PRIu64 ":%" PRIu64, base_msg, details->line, details->column);
188
0
  }
189
  
190
207
  return zend_string_init(base_msg, strlen(base_msg), 0);
191
207
}
192
/* }}} */
193
194
PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth) /* {{{ */
195
38
{
196
38
  php_json_parser parser;
197
198
38
  php_json_parser_init(&parser, return_value, str, str_len, (int)options, (int)depth);
199
200
38
  if (php_json_yyparse(&parser)) {
201
0
    php_json_error_details details;
202
0
    php_json_parser_error_details(&parser, &details);
203
204
0
    if (!(options & PHP_JSON_THROW_ON_ERROR)) {
205
0
      JSON_G(error_details) = details;
206
0
    } else {
207
0
      zend_string *error_msg = php_json_get_error_msg_with_location(&details);
208
0
      zend_throw_exception(php_json_exception_ce, ZSTR_VAL(error_msg), details.code);
209
0
      zend_string_release(error_msg);
210
0
    }
211
0
    RETVAL_NULL();
212
0
    return FAILURE;
213
0
  }
214
215
38
  return SUCCESS;
216
38
}
217
/* }}} */
218
219
/* {{{ */
220
PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_long options, zend_long depth)
221
0
{
222
0
  php_json_parser parser;
223
0
  zval tmp;
224
0
  const php_json_parser_methods* parser_validate_methods = php_json_get_validate_methods();
225
0
  php_json_parser_init_ex(&parser, &tmp, str, str_len, (int)options, (int)depth, parser_validate_methods);
226
227
0
  if (php_json_yyparse(&parser)) {
228
0
    php_json_parser_error_details(&parser, &JSON_G(error_details));
229
230
0
    return false;
231
0
  }
232
233
0
  return true;
234
0
}
235
/* }}} */
236
237
/* {{{ Returns the JSON representation of a value */
238
PHP_FUNCTION(json_encode)
239
3.22k
{
240
3.22k
  zval *parameter;
241
3.22k
  php_json_encoder encoder;
242
3.22k
  smart_str buf = {0};
243
3.22k
  zend_long options = 0;
244
3.22k
  zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
245
246
9.67k
  ZEND_PARSE_PARAMETERS_START(1, 3)
247
12.9k
    Z_PARAM_ZVAL(parameter)
248
12.9k
    Z_PARAM_OPTIONAL
249
12.9k
    Z_PARAM_LONG(options)
250
2.36k
    Z_PARAM_LONG(depth)
251
3.22k
  ZEND_PARSE_PARAMETERS_END();
252
253
3.22k
  php_json_encode_init(&encoder);
254
3.22k
  encoder.max_depth = (int)depth;
255
3.22k
  php_json_encode_zval(&buf, parameter, (int)options, &encoder);
256
257
3.22k
  if (!(options & PHP_JSON_THROW_ON_ERROR) || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
258
3.18k
    JSON_G(error_details) = (php_json_error_details){
259
3.18k
      .code = encoder.error_code,
260
3.18k
      .line = 0,
261
3.18k
      .column = 0,
262
3.18k
    };
263
264
3.18k
    if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
265
114
      smart_str_free(&buf);
266
114
      RETURN_FALSE;
267
114
    }
268
3.18k
  } else {
269
46
    if (encoder.error_code != PHP_JSON_ERROR_NONE) {
270
18
      smart_str_free(&buf);
271
18
      zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(encoder.error_code), encoder.error_code);
272
18
      RETURN_THROWS();
273
18
    }
274
46
  }
275
276
3.09k
  RETURN_STR(smart_str_extract(&buf));
277
3.09k
}
278
/* }}} */
279
280
/* {{{ Decodes the JSON representation into a PHP value */
281
PHP_FUNCTION(json_decode)
282
38
{
283
38
  char *str;
284
38
  size_t str_len;
285
38
  bool assoc = 0; /* return JS objects as PHP objects by default */
286
38
  bool assoc_null = 1;
287
38
  zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
288
38
  zend_long options = 0;
289
290
114
  ZEND_PARSE_PARAMETERS_START(1, 4)
291
152
    Z_PARAM_STRING(str, str_len)
292
38
    Z_PARAM_OPTIONAL
293
76
    Z_PARAM_BOOL_OR_NULL(assoc, assoc_null)
294
0
    Z_PARAM_LONG(depth)
295
0
    Z_PARAM_LONG(options)
296
38
  ZEND_PARSE_PARAMETERS_END();
297
298
38
  if (!(options & PHP_JSON_THROW_ON_ERROR)) {
299
38
    php_json_error_details_clear(&JSON_G(error_details));
300
38
  }
301
302
38
  if (!str_len) {
303
0
    if (!(options & PHP_JSON_THROW_ON_ERROR)) {
304
0
      JSON_G(error_details) = (php_json_error_details){
305
0
        .code = PHP_JSON_ERROR_SYNTAX,
306
0
        .line = 0,
307
0
        .column = 0,
308
0
      };
309
0
    } else {
310
0
      zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX);
311
0
    }
312
0
    RETURN_NULL();
313
0
  }
314
315
38
  if (depth <= 0) {
316
0
    zend_argument_value_error(3, "must be greater than 0");
317
0
    RETURN_THROWS();
318
0
  }
319
320
38
  if (depth > INT_MAX) {
321
0
    zend_argument_value_error(3, "must be less than %d", INT_MAX);
322
0
    RETURN_THROWS();
323
0
  }
324
325
  /* For BC reasons, the bool $assoc overrides the long $options bit for PHP_JSON_OBJECT_AS_ARRAY */
326
38
  if (!assoc_null) {
327
0
    if (assoc) {
328
0
      options |=  PHP_JSON_OBJECT_AS_ARRAY;
329
0
    } else {
330
0
      options &= ~PHP_JSON_OBJECT_AS_ARRAY;
331
0
    }
332
0
  }
333
334
38
  php_json_decode_ex(return_value, str, str_len, options, depth);
335
38
}
336
/* }}} */
337
338
/* {{{ Validates if a string contains a valid json */
339
PHP_FUNCTION(json_validate)
340
0
{
341
0
  char *str;
342
0
  size_t str_len;
343
0
  zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
344
0
  zend_long options = 0;
345
346
0
  ZEND_PARSE_PARAMETERS_START(1, 3)
347
0
    Z_PARAM_STRING(str, str_len)
348
0
    Z_PARAM_OPTIONAL
349
0
    Z_PARAM_LONG(depth)
350
0
    Z_PARAM_LONG(options)
351
0
  ZEND_PARSE_PARAMETERS_END();
352
353
354
0
  if ((options != 0) && (options != PHP_JSON_INVALID_UTF8_IGNORE)) {
355
0
    zend_argument_value_error(3, "must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)");
356
0
    RETURN_THROWS();
357
0
  }
358
359
0
  if (!str_len) {
360
0
    JSON_G(error_details) = (php_json_error_details){
361
0
      .code = PHP_JSON_ERROR_SYNTAX,
362
0
      .line = 0,
363
0
      .column = 0,
364
0
    };
365
366
0
    RETURN_FALSE;
367
0
  }
368
369
0
  php_json_error_details_clear(&JSON_G(error_details));
370
371
0
  if (depth <= 0) {
372
0
    zend_argument_value_error(2, "must be greater than 0");
373
0
    RETURN_THROWS();
374
0
  }
375
376
0
  if (depth > INT_MAX) {
377
0
    zend_argument_value_error(2, "must be less than %d", INT_MAX);
378
0
    RETURN_THROWS();
379
0
  }
380
381
0
  RETURN_BOOL(php_json_validate_ex(str, str_len, options, depth));
382
0
}
383
/* }}} */
384
385
/* {{{ Returns the error code of the last json_encode() or json_decode() call. */
386
PHP_FUNCTION(json_last_error)
387
110
{
388
110
  ZEND_PARSE_PARAMETERS_NONE();
389
390
110
  RETURN_LONG(JSON_G(error_details).code);
391
110
}
392
/* }}} */
393
394
/* {{{ Returns the error string of the last json_encode() or json_decode() call. */
395
PHP_FUNCTION(json_last_error_msg)
396
207
{
397
207
  ZEND_PARSE_PARAMETERS_NONE();
398
399
207
  RETURN_STR(php_json_get_error_msg_with_location(&JSON_G(error_details)));
400
207
}
401
/* }}} */