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