Skip to content

Commit b80ffc5

Browse files
authored
Json last error msg/error message with location error (#20629)
This slightly extend error messages with locations taken from scanner / parser
1 parent 46a1534 commit b80ffc5

22 files changed

+1453
-32
lines changed

ext/json/json.c

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,17 @@ static PHP_GINIT_FUNCTION(json)
6363
#endif
6464
json_globals->encoder_depth = 0;
6565
json_globals->error_code = 0;
66+
json_globals->error_line = 0;
67+
json_globals->error_column = 0;
6668
json_globals->encode_max_depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
6769
}
6870
/* }}} */
6971

7072
static PHP_RINIT_FUNCTION(json)
7173
{
7274
JSON_G(error_code) = 0;
75+
JSON_G(error_line) = 0;
76+
JSON_G(error_column) = 0;
7377
return SUCCESS;
7478
}
7579

@@ -177,6 +181,18 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{
177181
}
178182
/* }}} */
179183

184+
static zend_string *php_json_get_error_msg_with_location(php_json_error_code error_code, size_t line, size_t column) /* {{{ */
185+
{
186+
const char *base_msg = php_json_get_error_msg(error_code);
187+
188+
if (line > 0 && column > 0) {
189+
return zend_strpprintf(0, "%s near location %zu:%zu", base_msg, line, column);
190+
}
191+
192+
return zend_string_init(base_msg, strlen(base_msg), 0);
193+
}
194+
/* }}} */
195+
180196
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) /* {{{ */
181197
{
182198
php_json_parser parser;
@@ -185,10 +201,17 @@ PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str,
185201

186202
if (php_json_yyparse(&parser)) {
187203
php_json_error_code error_code = php_json_parser_error_code(&parser);
204+
size_t error_line = php_json_parser_error_line(&parser);
205+
size_t error_column = php_json_parser_error_column(&parser);
206+
188207
if (!(options & PHP_JSON_THROW_ON_ERROR)) {
189208
JSON_G(error_code) = error_code;
209+
JSON_G(error_line) = error_line;
210+
JSON_G(error_column) = error_column;
190211
} else {
191-
zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(error_code), error_code);
212+
zend_string *error_msg = php_json_get_error_msg_with_location(error_code, error_line, error_column);
213+
zend_throw_exception(php_json_exception_ce, ZSTR_VAL(error_msg), error_code);
214+
zend_string_release(error_msg);
192215
}
193216
RETVAL_NULL();
194217
return FAILURE;
@@ -208,7 +231,12 @@ PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_lon
208231

209232
if (php_json_yyparse(&parser)) {
210233
php_json_error_code error_code = php_json_parser_error_code(&parser);
234+
size_t error_line = php_json_parser_error_line(&parser);
235+
size_t error_column = php_json_parser_error_column(&parser);
236+
211237
JSON_G(error_code) = error_code;
238+
JSON_G(error_line) = error_line;
239+
JSON_G(error_column) = error_column;
212240
return false;
213241
}
214242

@@ -274,11 +302,15 @@ PHP_FUNCTION(json_decode)
274302

275303
if (!(options & PHP_JSON_THROW_ON_ERROR)) {
276304
JSON_G(error_code) = PHP_JSON_ERROR_NONE;
305+
JSON_G(error_line) = 0;
306+
JSON_G(error_column) = 0;
277307
}
278308

279309
if (!str_len) {
280310
if (!(options & PHP_JSON_THROW_ON_ERROR)) {
281311
JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX;
312+
JSON_G(error_line) = 0;
313+
JSON_G(error_column) = 0;
282314
} else {
283315
zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX);
284316
}
@@ -331,10 +363,14 @@ PHP_FUNCTION(json_validate)
331363

332364
if (!str_len) {
333365
JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX;
366+
JSON_G(error_line) = 0;
367+
JSON_G(error_column) = 0;
334368
RETURN_FALSE;
335369
}
336370

337371
JSON_G(error_code) = PHP_JSON_ERROR_NONE;
372+
JSON_G(error_line) = 0;
373+
JSON_G(error_column) = 0;
338374

339375
if (depth <= 0) {
340376
zend_argument_value_error(2, "must be greater than 0");
@@ -364,6 +400,10 @@ PHP_FUNCTION(json_last_error_msg)
364400
{
365401
ZEND_PARSE_PARAMETERS_NONE();
366402

367-
RETURN_STRING(php_json_get_error_msg(JSON_G(error_code)));
403+
RETVAL_STR(php_json_get_error_msg_with_location(
404+
JSON_G(error_code),
405+
JSON_G(error_line),
406+
JSON_G(error_column)
407+
));
368408
}
369409
/* }}} */

ext/json/json_parser.y

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ int json_yydebug = 1;
4141

4242
}
4343

44+
%locations
4445
%define api.prefix {php_json_yy}
4546
%define api.pure full
4647
%param { php_json_parser *parser }
@@ -49,7 +50,6 @@ int json_yydebug = 1;
4950
zval value;
5051
}
5152

52-
5353
%token <value> PHP_JSON_T_NUL
5454
%token <value> PHP_JSON_T_TRUE
5555
%token <value> PHP_JSON_T_FALSE
@@ -66,8 +66,8 @@ int json_yydebug = 1;
6666
%destructor { zval_ptr_dtor_nogc(&$$); } <value>
6767

6868
%code {
69-
static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser);
70-
static void php_json_yyerror(php_json_parser *parser, char const *msg);
69+
static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser);
70+
static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg);
7171
static int php_json_parser_array_create(php_json_parser *parser, zval *array);
7272
static int php_json_parser_object_create(php_json_parser *parser, zval *array);
7373

@@ -277,7 +277,7 @@ static int php_json_parser_object_update_validate(php_json_parser *parser, zval
277277
return SUCCESS;
278278
}
279279

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

@@ -293,10 +293,15 @@ static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser)
293293
value->value = parser->scanner.value;
294294
}
295295

296+
location->first_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_column);
297+
location->first_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_line);
298+
location->last_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_column);
299+
location->last_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_line);
300+
296301
return token;
297302
}
298303

299-
static void php_json_yyerror(php_json_parser *parser, char const *msg)
304+
static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg)
300305
{
301306
if (!parser->scanner.errcode) {
302307
parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
@@ -308,6 +313,16 @@ PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parse
308313
return parser->scanner.errcode;
309314
}
310315

316+
PHP_JSON_API size_t php_json_parser_error_line(const php_json_parser *parser)
317+
{
318+
return parser->scanner.errloc.first_line;
319+
}
320+
321+
PHP_JSON_API size_t php_json_parser_error_column(const php_json_parser *parser)
322+
{
323+
return parser->scanner.errloc.first_column;
324+
}
325+
311326
static const php_json_parser_methods default_parser_methods =
312327
{
313328
php_json_parser_array_create,

ext/json/json_scanner.re

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252

5353
#define PHP_JSON_INT_MAX_LENGTH (MAX_LENGTH_OF_LONG - 1)
5454

55+
#define PHP_JSON_TOKEN_LENGTH() ((size_t) (s->cursor - s->token))
56+
#define PHP_JSON_TOKEN_LOCATION(location) (s)->errloc.location
5557

5658
static void php_json_scanner_copy_string(php_json_scanner *s, size_t esc_size)
5759
{
@@ -96,6 +98,10 @@ void php_json_scanner_init(php_json_scanner *s, const char *str, size_t str_len,
9698
s->cursor = (php_json_ctype *) str;
9799
s->limit = (php_json_ctype *) str + str_len;
98100
s->options = options;
101+
PHP_JSON_TOKEN_LOCATION(first_column) = 1;
102+
PHP_JSON_TOKEN_LOCATION(first_line) = 1;
103+
PHP_JSON_TOKEN_LOCATION(last_column) = 1;
104+
PHP_JSON_TOKEN_LOCATION(last_line) = 1;
99105
PHP_JSON_CONDITION_SET(JS);
100106
}
101107

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

106112
std:
113+
PHP_JSON_TOKEN_LOCATION(first_column) = s->errloc.last_column;
114+
PHP_JSON_TOKEN_LOCATION(first_line) = s->errloc.last_line;
107115
s->token = s->cursor;
108116

109117
/*!re2c
@@ -149,27 +157,50 @@ std:
149157
UTF16_3 = UTFPREF ( ( ( HEXC | [efEF] ) HEX ) | ( [dD] HEX7 ) ) HEX{2} ;
150158
UTF16_4 = UTFPREF [dD] [89abAB] HEX{2} UTFPREF [dD] [c-fC-F] HEX{2} ;
151159
152-
<JS>"{" { return '{'; }
153-
<JS>"}" { return '}'; }
154-
<JS>"[" { return '['; }
155-
<JS>"]" { return ']'; }
156-
<JS>":" { return ':'; }
157-
<JS>"," { return ','; }
160+
<JS>"{" {
161+
PHP_JSON_TOKEN_LOCATION(last_column)++;
162+
return '{';
163+
}
164+
<JS>"}" {
165+
PHP_JSON_TOKEN_LOCATION(last_column)++;
166+
return '}';
167+
}
168+
<JS>"[" {
169+
PHP_JSON_TOKEN_LOCATION(last_column)++;
170+
return '[';
171+
}
172+
<JS>"]" {
173+
PHP_JSON_TOKEN_LOCATION(last_column)++;
174+
return ']';
175+
}
176+
<JS>":" {
177+
PHP_JSON_TOKEN_LOCATION(last_column)++;
178+
return ':';
179+
}
180+
<JS>"," {
181+
PHP_JSON_TOKEN_LOCATION(last_column)++;
182+
return ',';
183+
}
158184
<JS>"null" {
185+
PHP_JSON_TOKEN_LOCATION(last_column) += 4;
159186
ZVAL_NULL(&s->value);
160187
return PHP_JSON_T_NUL;
161188
}
162189
<JS>"true" {
190+
PHP_JSON_TOKEN_LOCATION(last_column) += 4;
163191
ZVAL_TRUE(&s->value);
164192
return PHP_JSON_T_TRUE;
165193
}
166194
<JS>"false" {
195+
PHP_JSON_TOKEN_LOCATION(last_column) += 5;
167196
ZVAL_FALSE(&s->value);
168197
return PHP_JSON_T_FALSE;
169198
}
170199
<JS>INT {
171200
bool bigint = 0, negative = s->token[0] == '-';
172-
size_t digits = (size_t) (s->cursor - s->token - negative);
201+
size_t digits = PHP_JSON_TOKEN_LENGTH();
202+
PHP_JSON_TOKEN_LOCATION(last_column) += digits;
203+
digits -= negative;
173204
if (digits >= PHP_JSON_INT_MAX_LENGTH) {
174205
if (digits == PHP_JSON_INT_MAX_LENGTH) {
175206
int cmp = strncmp((char *) (s->token + negative), LONG_MIN_DIGITS, PHP_JSON_INT_MAX_LENGTH);
@@ -192,10 +223,19 @@ std:
192223
}
193224
}
194225
<JS>FLOAT|EXP {
226+
PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH();
195227
ZVAL_DOUBLE(&s->value, zend_strtod((char *) s->token, NULL));
196228
return PHP_JSON_T_DOUBLE;
197229
}
198-
<JS>NL|WS { goto std; }
230+
<JS>NL {
231+
PHP_JSON_TOKEN_LOCATION(last_line)++;
232+
PHP_JSON_TOKEN_LOCATION(last_column) = 1;
233+
goto std;
234+
}
235+
<JS>WS {
236+
PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH();
237+
goto std;
238+
}
199239
<JS>EOI {
200240
if (s->limit < s->cursor) {
201241
return PHP_JSON_T_EOI;
@@ -205,6 +245,7 @@ std:
205245
}
206246
}
207247
<JS>["] {
248+
PHP_JSON_TOKEN_LOCATION(last_column)++;
208249
s->str_start = s->cursor;
209250
s->str_esc = 0;
210251
s->utf8_invalid = 0;
@@ -229,18 +270,22 @@ std:
229270
return PHP_JSON_T_ERROR;
230271
}
231272
<STR_P1>UTF16_1 {
273+
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
232274
s->str_esc += 5;
233275
PHP_JSON_CONDITION_GOTO(STR_P1);
234276
}
235277
<STR_P1>UTF16_2 {
278+
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
236279
s->str_esc += 4;
237280
PHP_JSON_CONDITION_GOTO(STR_P1);
238281
}
239282
<STR_P1>UTF16_3 {
283+
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
240284
s->str_esc += 3;
241285
PHP_JSON_CONDITION_GOTO(STR_P1);
242286
}
243287
<STR_P1>UTF16_4 {
288+
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
244289
s->str_esc += 8;
245290
PHP_JSON_CONDITION_GOTO(STR_P1);
246291
}
@@ -249,6 +294,7 @@ std:
249294
return PHP_JSON_T_ERROR;
250295
}
251296
<STR_P1>ESC {
297+
PHP_JSON_TOKEN_LOCATION(last_column) += 2;
252298
s->str_esc++;
253299
PHP_JSON_CONDITION_GOTO(STR_P1);
254300
}
@@ -257,6 +303,7 @@ std:
257303
return PHP_JSON_T_ERROR;
258304
}
259305
<STR_P1>["] {
306+
PHP_JSON_TOKEN_LOCATION(last_column)++;
260307
zend_string *str;
261308
size_t len = (size_t)(s->cursor - s->str_start - s->str_esc - 1 + s->utf8_invalid_count);
262309
if (len == 0) {
@@ -277,7 +324,22 @@ std:
277324
return PHP_JSON_T_STRING;
278325
}
279326
}
280-
<STR_P1>UTF8 { PHP_JSON_CONDITION_GOTO(STR_P1); }
327+
<STR_P1>UTF8_1 {
328+
PHP_JSON_TOKEN_LOCATION(last_column)++;
329+
PHP_JSON_CONDITION_GOTO(STR_P1);
330+
}
331+
<STR_P1>UTF8_2 {
332+
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
333+
PHP_JSON_CONDITION_GOTO(STR_P1);
334+
}
335+
<STR_P1>UTF8_3 {
336+
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
337+
PHP_JSON_CONDITION_GOTO(STR_P1);
338+
}
339+
<STR_P1>UTF8_4 {
340+
PHP_JSON_TOKEN_LOCATION(last_column) += 1;
341+
PHP_JSON_CONDITION_GOTO(STR_P1);
342+
}
281343
<STR_P1>ANY {
282344
if (s->options & (PHP_JSON_INVALID_UTF8_IGNORE | PHP_JSON_INVALID_UTF8_SUBSTITUTE)) {
283345
if (s->options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) {
@@ -295,7 +357,6 @@ std:
295357
s->errcode = PHP_JSON_ERROR_UTF8;
296358
return PHP_JSON_T_ERROR;
297359
}
298-
299360
<STR_P2_UTF,STR_P2_BIN>UTF16_1 {
300361
int utf16 = php_json_ucs2_to_int(s, 2);
301362
PHP_JSON_SCANNER_COPY_UTF();

ext/json/php_json.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ ZEND_BEGIN_MODULE_GLOBALS(json)
8686
int encoder_depth;
8787
int encode_max_depth;
8888
php_json_error_code error_code;
89+
size_t error_line;
90+
size_t error_column;
8991
ZEND_END_MODULE_GLOBALS(json)
9092

9193
PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json)

0 commit comments

Comments
 (0)