Coverage Report

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