Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
44 changes: 42 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);
}
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,10 @@ PHP_FUNCTION(json_last_error_msg)
{
ZEND_PARSE_PARAMETERS_NONE();

RETURN_STRING(php_json_get_error_msg(JSON_G(error_code)));
RETVAL_STR(php_json_get_error_msg_with_location(
JSON_G(error_code),
JSON_G(error_line),
JSON_G(error_column)
));
}
/* }}} */
25 changes: 20 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,10 +293,15 @@ 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;
Expand All @@ -308,6 +313,16 @@ 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)
{
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() ((size_t) (s->cursor - s->token))
#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();
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();
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();
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