diff --git a/ext/json/json.c b/ext/json/json.c index 3ba53e959c5d..ac033c057ac4 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -60,18 +60,14 @@ static PHP_GINIT_FUNCTION(json) ZEND_TSRMLS_CACHE_UPDATE(); #endif json_globals->encoder_depth = 0; - json_globals->error_code = 0; - json_globals->error_line = 0; - json_globals->error_column = 0; + php_json_error_details_clear(&json_globals->error_details); json_globals->encode_max_depth = PHP_JSON_PARSER_DEFAULT_DEPTH; } /* }}} */ static PHP_RINIT_FUNCTION(json) { - JSON_G(error_code) = 0; - JSON_G(error_line) = 0; - JSON_G(error_column) = 0; + php_json_error_details_clear(&JSON_G(error_details)); return SUCCESS; } @@ -134,7 +130,11 @@ PHP_JSON_API zend_result php_json_encode_ex(smart_str *buf, zval *val, int optio encoder.max_depth = depth; return_code = php_json_encode_zval(buf, val, options, &encoder); - JSON_G(error_code) = encoder.error_code; + JSON_G(error_details) = (php_json_error_details){ + .code = encoder.error_code, + .line = 0, + .column = 0, + }; return return_code; } @@ -179,12 +179,12 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ } /* }}} */ -static zend_string *php_json_get_error_msg_with_location(php_json_error_code error_code, size_t line, size_t column) /* {{{ */ +static zend_string *php_json_get_error_msg_with_location(const php_json_error_details *details) /* {{{ */ { - const char *base_msg = php_json_get_error_msg(error_code); + const char *base_msg = php_json_get_error_msg(details->code); - if (line > 0 && column > 0) { - return zend_strpprintf(0, "%s near location %zu:%zu", base_msg, line, column); + if (details->line > 0 && details->column > 0) { + return zend_strpprintf(0, "%s near location %zu:%zu", base_msg, details->line, details->column); } return zend_string_init(base_msg, strlen(base_msg), 0); @@ -198,17 +198,14 @@ PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, php_json_parser_init(&parser, return_value, str, str_len, (int)options, (int)depth); if (php_json_yyparse(&parser)) { - php_json_error_code error_code = php_json_parser_error_code(&parser); - size_t error_line = php_json_parser_error_line(&parser); - size_t error_column = php_json_parser_error_column(&parser); + php_json_error_details details; + php_json_parser_error_details(&parser, &details); if (!(options & PHP_JSON_THROW_ON_ERROR)) { - JSON_G(error_code) = error_code; - JSON_G(error_line) = error_line; - JSON_G(error_column) = error_column; + JSON_G(error_details) = details; } else { - zend_string *error_msg = php_json_get_error_msg_with_location(error_code, error_line, error_column); - zend_throw_exception(php_json_exception_ce, ZSTR_VAL(error_msg), error_code); + zend_string *error_msg = php_json_get_error_msg_with_location(&details); + zend_throw_exception(php_json_exception_ce, ZSTR_VAL(error_msg), details.code); zend_string_release(error_msg); } RETVAL_NULL(); @@ -228,13 +225,8 @@ PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_lon php_json_parser_init_ex(&parser, &tmp, str, str_len, (int)options, (int)depth, parser_validate_methods); if (php_json_yyparse(&parser)) { - php_json_error_code error_code = php_json_parser_error_code(&parser); - size_t error_line = php_json_parser_error_line(&parser); - size_t error_column = php_json_parser_error_column(&parser); + php_json_parser_error_details(&parser, &JSON_G(error_details)); - JSON_G(error_code) = error_code; - JSON_G(error_line) = error_line; - JSON_G(error_column) = error_column; return false; } @@ -263,7 +255,12 @@ PHP_FUNCTION(json_encode) php_json_encode_zval(&buf, parameter, (int)options, &encoder); if (!(options & PHP_JSON_THROW_ON_ERROR) || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { - JSON_G(error_code) = encoder.error_code; + JSON_G(error_details) = (php_json_error_details){ + .code = encoder.error_code, + .line = 0, + .column = 0, + }; + if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { smart_str_free(&buf); RETURN_FALSE; @@ -299,16 +296,16 @@ PHP_FUNCTION(json_decode) ZEND_PARSE_PARAMETERS_END(); if (!(options & PHP_JSON_THROW_ON_ERROR)) { - JSON_G(error_code) = PHP_JSON_ERROR_NONE; - JSON_G(error_line) = 0; - JSON_G(error_column) = 0; + php_json_error_details_clear(&JSON_G(error_details)); } if (!str_len) { if (!(options & PHP_JSON_THROW_ON_ERROR)) { - JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX; - JSON_G(error_line) = 0; - JSON_G(error_column) = 0; + JSON_G(error_details) = (php_json_error_details){ + .code = PHP_JSON_ERROR_SYNTAX, + .line = 0, + .column = 0, + }; } else { zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX); } @@ -360,15 +357,16 @@ PHP_FUNCTION(json_validate) } if (!str_len) { - JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX; - JSON_G(error_line) = 0; - JSON_G(error_column) = 0; + JSON_G(error_details) = (php_json_error_details){ + .code = PHP_JSON_ERROR_SYNTAX, + .line = 0, + .column = 0, + }; + RETURN_FALSE; } - JSON_G(error_code) = PHP_JSON_ERROR_NONE; - JSON_G(error_line) = 0; - JSON_G(error_column) = 0; + php_json_error_details_clear(&JSON_G(error_details)); if (depth <= 0) { zend_argument_value_error(2, "must be greater than 0"); @@ -389,7 +387,7 @@ PHP_FUNCTION(json_last_error) { ZEND_PARSE_PARAMETERS_NONE(); - RETURN_LONG(JSON_G(error_code)); + RETURN_LONG(JSON_G(error_details).code); } /* }}} */ @@ -398,10 +396,6 @@ PHP_FUNCTION(json_last_error_msg) { ZEND_PARSE_PARAMETERS_NONE(); - RETVAL_STR(php_json_get_error_msg_with_location( - JSON_G(error_code), - JSON_G(error_line), - JSON_G(error_column) - )); + RETURN_STR(php_json_get_error_msg_with_location(&JSON_G(error_details))); } /* }}} */ diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y index cf296ed9db62..0d3b90b29e1e 100644 --- a/ext/json/json_parser.y +++ b/ext/json/json_parser.y @@ -311,14 +311,11 @@ PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parse return parser->scanner.errcode; } -PHP_JSON_API size_t php_json_parser_error_line(const php_json_parser *parser) +PHP_JSON_API void php_json_parser_error_details(const php_json_parser *parser, php_json_error_details *out) { - return parser->scanner.errloc.first_line; -} - -PHP_JSON_API size_t php_json_parser_error_column(const php_json_parser *parser) -{ - return parser->scanner.errloc.first_column; + out->code = parser->scanner.errcode; + out->line = parser->scanner.errloc.first_line; + out->column = parser->scanner.errloc.first_column; } static const php_json_parser_methods default_parser_methods = diff --git a/ext/json/php_json.h b/ext/json/php_json.h index e2728835ed42..f34684e149d8 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -52,6 +52,18 @@ typedef enum { PHP_JSON_ERROR_NON_BACKED_ENUM, } php_json_error_code; +typedef struct php_json_error_details { + php_json_error_code code; + size_t line; + size_t column; +} php_json_error_details; + +static inline void php_json_error_details_clear(php_json_error_details *out) { + out->code = PHP_JSON_ERROR_NONE; + out->line = 0; + out->column = 0; +} + /* json_decode() options */ #define PHP_JSON_OBJECT_AS_ARRAY (1<<0) #define PHP_JSON_BIGINT_AS_STRING (1<<1) @@ -83,9 +95,7 @@ typedef enum { ZEND_BEGIN_MODULE_GLOBALS(json) int encoder_depth; int encode_max_depth; - php_json_error_code error_code; - size_t error_line; - size_t error_column; + php_json_error_details error_details; ZEND_END_MODULE_GLOBALS(json) PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json) diff --git a/ext/json/php_json_parser.h b/ext/json/php_json_parser.h index 4a7d64307c43..888a0d317fe0 100644 --- a/ext/json/php_json_parser.h +++ b/ext/json/php_json_parser.h @@ -83,9 +83,7 @@ PHP_JSON_API void php_json_parser_init( PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parser *parser); -PHP_JSON_API size_t php_json_parser_error_line(const php_json_parser *parser); - -PHP_JSON_API size_t php_json_parser_error_column(const php_json_parser *parser); +PHP_JSON_API void php_json_parser_error_details(const php_json_parser *parser, php_json_error_details *out); PHP_JSON_API int php_json_parse(php_json_parser *parser); diff --git a/ext/json/tests/gh22420.phpt b/ext/json/tests/gh22420.phpt new file mode 100644 index 000000000000..eecdc6d97a8d --- /dev/null +++ b/ext/json/tests/gh22420.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-22420: json_encode() errors don't clear the error position +--FILE-- + +--EXPECTF-- +NULL +string(30) "Syntax error near location 1:2" +bool(false) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"