Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions ext/json/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,17 @@ static PHP_GINIT_FUNCTION(json)
#endif
json_globals->encoder_depth = 0;
json_globals->error_code = 0;
json_globals->error_line = 0;
json_globals->error_column = 0;
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;
return SUCCESS;
}

Expand Down Expand Up @@ -177,6 +181,18 @@ 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) /* {{{ */
{
const char *base_msg = php_json_get_error_msg(error_code);

if (line > 0 && column > 0) {
return zend_strpprintf(0, "%s near location %zu,%zu", base_msg, line, column);
}

return zend_string_init(base_msg, strlen(base_msg), 0);
}
/* }}} */

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) /* {{{ */
{
php_json_parser parser;
Expand All @@ -185,10 +201,17 @@ PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str,

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);

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;
} else {
zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(error_code), error_code);
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_release(error_msg);
Comment on lines +213 to +214
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
zend_throw_exception(php_json_exception_ce, ZSTR_VAL(error_msg), error_code);
zend_string_release(error_msg);
zend_throw_exception_zstr(php_json_exception_ce, error_msg, error_code);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This not a public API (yet), see #20547

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah good catch (I was just searching for something like that but didn't realise it's not in the header...). Let's wait for that first commit in the PR then...

}
RETVAL_NULL();
return FAILURE;
Expand All @@ -208,7 +231,12 @@ PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_lon

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);

JSON_G(error_code) = error_code;
JSON_G(error_line) = error_line;
JSON_G(error_column) = error_column;
return false;
}

Expand Down Expand Up @@ -274,11 +302,15 @@ PHP_FUNCTION(json_decode)

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;
}

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;
} else {
zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX);
}
Expand Down Expand Up @@ -331,10 +363,14 @@ 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;
RETURN_FALSE;
}

JSON_G(error_code) = PHP_JSON_ERROR_NONE;
JSON_G(error_line) = 0;
JSON_G(error_column) = 0;

if (depth <= 0) {
zend_argument_value_error(2, "must be greater than 0");
Expand Down Expand Up @@ -364,6 +400,15 @@ PHP_FUNCTION(json_last_error_msg)
{
ZEND_PARSE_PARAMETERS_NONE();

RETURN_STRING(php_json_get_error_msg(JSON_G(error_code)));
if (JSON_G(error_line) > 0 && JSON_G(error_column) > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is not necessary as it is checked in php_json_get_error_msg_with_location. If it was to save creation of zend_string, then it is not necessary because it is created in RETURN_STRING anyway.

zend_string *error_msg = php_json_get_error_msg_with_location(
JSON_G(error_code),
JSON_G(error_line),
JSON_G(error_column)
);
RETVAL_STR(error_msg);
} else {
RETURN_STRING(php_json_get_error_msg(JSON_G(error_code)));
}
}
/* }}} */
30 changes: 25 additions & 5 deletions ext/json/json_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ int json_yydebug = 1;

}

%locations
%define api.prefix {php_json_yy}
%define api.pure full
%param { php_json_parser *parser }
Expand All @@ -49,7 +50,6 @@ int json_yydebug = 1;
zval value;
}


%token <value> PHP_JSON_T_NUL
%token <value> PHP_JSON_T_TRUE
%token <value> PHP_JSON_T_FALSE
Expand All @@ -66,8 +66,8 @@ int json_yydebug = 1;
%destructor { zval_ptr_dtor_nogc(&$$); } <value>

%code {
static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser);
static void php_json_yyerror(php_json_parser *parser, char const *msg);
static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser);
static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg);
static int php_json_parser_array_create(php_json_parser *parser, zval *array);
static int php_json_parser_object_create(php_json_parser *parser, zval *array);

Expand Down Expand Up @@ -277,7 +277,7 @@ static int php_json_parser_object_update_validate(php_json_parser *parser, zval
return SUCCESS;
}

static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser)
static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser)
{
int token = php_json_scan(&parser->scanner);

Expand All @@ -293,21 +293,41 @@ static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser)
value->value = parser->scanner.value;
}

location->first_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_column);
location->first_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_line);
location->last_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_column);
location->last_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_line);

return token;
}

static void php_json_yyerror(php_json_parser *parser, char const *msg)
static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg)
{
if (!parser->scanner.errcode) {
parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
}

parser->scanner.errloc.first_column = location->first_column;
parser->scanner.errloc.first_line = location->first_line;
parser->scanner.errloc.last_column = location->last_column;
parser->scanner.errloc.last_line = location->last_line;
Comment on lines +309 to +313
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? Just checking because I don't have this in jso and it seems to still work so wondering why it's added here?

}

PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parser *parser)
{
return parser->scanner.errcode;
}

PHP_JSON_API size_t php_json_parser_error_line(const php_json_parser *parser)
{
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;
}

static const php_json_parser_methods default_parser_methods =
{
php_json_parser_array_create,
Expand Down
81 changes: 71 additions & 10 deletions ext/json/json_scanner.re
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@

#define PHP_JSON_INT_MAX_LENGTH (MAX_LENGTH_OF_LONG - 1)

#define PHP_JSON_TOKEN_LENGTH(s) ((size_t) ((s)->cursor - (s)->token))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess s is kind of given in other macros so I would omit it here as well for consistency.

#define PHP_JSON_TOKEN_LOCATION(location) (s)->errloc.location

static void php_json_scanner_copy_string(php_json_scanner *s, size_t esc_size)
{
Expand Down Expand Up @@ -96,6 +98,10 @@ void php_json_scanner_init(php_json_scanner *s, const char *str, size_t str_len,
s->cursor = (php_json_ctype *) str;
s->limit = (php_json_ctype *) str + str_len;
s->options = options;
PHP_JSON_TOKEN_LOCATION(first_column) = 1;
PHP_JSON_TOKEN_LOCATION(first_line) = 1;
PHP_JSON_TOKEN_LOCATION(last_column) = 1;
PHP_JSON_TOKEN_LOCATION(last_line) = 1;
PHP_JSON_CONDITION_SET(JS);
}

Expand All @@ -104,6 +110,8 @@ int php_json_scan(php_json_scanner *s)
ZVAL_NULL(&s->value);

std:
PHP_JSON_TOKEN_LOCATION(first_column) = s->errloc.last_column;
PHP_JSON_TOKEN_LOCATION(first_line) = s->errloc.last_line;
s->token = s->cursor;

/*!re2c
Expand Down Expand Up @@ -149,27 +157,50 @@ std:
UTF16_3 = UTFPREF ( ( ( HEXC | [efEF] ) HEX ) | ( [dD] HEX7 ) ) HEX{2} ;
UTF16_4 = UTFPREF [dD] [89abAB] HEX{2} UTFPREF [dD] [c-fC-F] HEX{2} ;

<JS>"{" { return '{'; }
<JS>"}" { return '}'; }
<JS>"[" { return '['; }
<JS>"]" { return ']'; }
<JS>":" { return ':'; }
<JS>"," { return ','; }
<JS>"{" {
PHP_JSON_TOKEN_LOCATION(last_column)++;
return '{';
}
<JS>"}" {
PHP_JSON_TOKEN_LOCATION(last_column)++;
return '}';
}
<JS>"[" {
PHP_JSON_TOKEN_LOCATION(last_column)++;
return '[';
}
<JS>"]" {
PHP_JSON_TOKEN_LOCATION(last_column)++;
return ']';
}
<JS>":" {
PHP_JSON_TOKEN_LOCATION(last_column)++;
return ':';
}
<JS>"," {
PHP_JSON_TOKEN_LOCATION(last_column)++;
return ',';
}
<JS>"null" {
PHP_JSON_TOKEN_LOCATION(last_column) += 4;
ZVAL_NULL(&s->value);
return PHP_JSON_T_NUL;
}
<JS>"true" {
PHP_JSON_TOKEN_LOCATION(last_column) += 4;
ZVAL_TRUE(&s->value);
return PHP_JSON_T_TRUE;
}
<JS>"false" {
PHP_JSON_TOKEN_LOCATION(last_column) += 5;
ZVAL_FALSE(&s->value);
return PHP_JSON_T_FALSE;
}
<JS>INT {
bool bigint = 0, negative = s->token[0] == '-';
size_t digits = (size_t) (s->cursor - s->token - negative);
size_t digits = PHP_JSON_TOKEN_LENGTH(s);
PHP_JSON_TOKEN_LOCATION(last_column) += digits;
digits -= negative;
if (digits >= PHP_JSON_INT_MAX_LENGTH) {
if (digits == PHP_JSON_INT_MAX_LENGTH) {
int cmp = strncmp((char *) (s->token + negative), LONG_MIN_DIGITS, PHP_JSON_INT_MAX_LENGTH);
Expand All @@ -192,10 +223,19 @@ std:
}
}
<JS>FLOAT|EXP {
PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH(s);
ZVAL_DOUBLE(&s->value, zend_strtod((char *) s->token, NULL));
return PHP_JSON_T_DOUBLE;
}
<JS>NL|WS { goto std; }
<JS>NL {
PHP_JSON_TOKEN_LOCATION(last_line)++;
PHP_JSON_TOKEN_LOCATION(last_column) = 1;
goto std;
}
<JS>WS {
PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH(s);
goto std;
}
<JS>EOI {
if (s->limit < s->cursor) {
return PHP_JSON_T_EOI;
Expand All @@ -205,6 +245,7 @@ std:
}
}
<JS>["] {
PHP_JSON_TOKEN_LOCATION(last_column)++;
s->str_start = s->cursor;
s->str_esc = 0;
s->utf8_invalid = 0;
Expand All @@ -229,18 +270,22 @@ std:
return PHP_JSON_T_ERROR;
}
<STR_P1>UTF16_1 {
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
s->str_esc += 5;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
<STR_P1>UTF16_2 {
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
s->str_esc += 4;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
<STR_P1>UTF16_3 {
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
s->str_esc += 3;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
<STR_P1>UTF16_4 {
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
s->str_esc += 8;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
Expand All @@ -249,6 +294,7 @@ std:
return PHP_JSON_T_ERROR;
}
<STR_P1>ESC {
PHP_JSON_TOKEN_LOCATION(last_column) += 2;
s->str_esc++;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
Expand All @@ -257,6 +303,7 @@ std:
return PHP_JSON_T_ERROR;
}
<STR_P1>["] {
PHP_JSON_TOKEN_LOCATION(last_column)++;
zend_string *str;
size_t len = (size_t)(s->cursor - s->str_start - s->str_esc - 1 + s->utf8_invalid_count);
if (len == 0) {
Expand All @@ -277,7 +324,22 @@ std:
return PHP_JSON_T_STRING;
}
}
<STR_P1>UTF8 { PHP_JSON_CONDITION_GOTO(STR_P1); }
<STR_P1>UTF8_1 {
PHP_JSON_TOKEN_LOCATION(last_column)++;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
<STR_P1>UTF8_2 {
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
<STR_P1>UTF8_3 {
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
<STR_P1>UTF8_4 {
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
PHP_JSON_CONDITION_GOTO(STR_P1);
}
<STR_P1>ANY {
if (s->options & (PHP_JSON_INVALID_UTF8_IGNORE | PHP_JSON_INVALID_UTF8_SUBSTITUTE)) {
if (s->options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) {
Expand All @@ -295,7 +357,6 @@ std:
s->errcode = PHP_JSON_ERROR_UTF8;
return PHP_JSON_T_ERROR;
}

<STR_P2_UTF,STR_P2_BIN>UTF16_1 {
int utf16 = php_json_ucs2_to_int(s, 2);
PHP_JSON_SCANNER_COPY_UTF();
Expand Down
2 changes: 2 additions & 0 deletions ext/json/php_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ 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;
ZEND_END_MODULE_GLOBALS(json)

PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json)
Expand Down
Loading
Loading