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
4 changes: 2 additions & 2 deletions ext/json/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,8 @@ PHP_FUNCTION(json_validate)
ZEND_PARSE_PARAMETERS_END();


if ((options != 0) && (options != PHP_JSON_INVALID_UTF8_IGNORE)) {
zend_argument_value_error(3, "must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)");
if ((options & ~(PHP_JSON_INVALID_UTF8_IGNORE | PHP_JSON_ALLOW_COMMENTS | PHP_JSON_ALLOW_TRAILING_COMMAS)) != 0) {
zend_argument_value_error(3, "must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE, JSON_ALLOW_COMMENTS, JSON_ALLOW_TRAILING_COMMAS)");
RETURN_THROWS();
}

Expand Down
11 changes: 11 additions & 0 deletions ext/json/json.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@
*/
const JSON_THROW_ON_ERROR = UNKNOWN;

/**
* @var int
* @cvalue PHP_JSON_ALLOW_COMMENTS
*/
const JSON_ALLOW_COMMENTS = UNKNOWN;
/**
* @var int
* @cvalue PHP_JSON_ALLOW_TRAILING_COMMAS
*/
const JSON_ALLOW_TRAILING_COMMAS = UNKNOWN;

/**
* @var int
* @cvalue PHP_JSON_ERROR_NONE
Expand Down
4 changes: 3 additions & 1 deletion ext/json/json_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions ext/json/json_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ members:
}
}
| member
| member ','
{
if (!(parser->scanner.options & PHP_JSON_ALLOW_TRAILING_COMMAS)) {
if (!parser->scanner.errcode) {
parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
}
zval_ptr_dtor_nogc(&$1);
YYERROR;
}
ZVAL_COPY_VALUE(&$$, &$1);
}
;

member:
Expand Down Expand Up @@ -175,6 +186,17 @@ elements:
}
}
| element
| element ','
{
if (!(parser->scanner.options & PHP_JSON_ALLOW_TRAILING_COMMAS)) {
if (!parser->scanner.errcode) {
parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
}
zval_ptr_dtor_nogc(&$1);
YYERROR;
}
ZVAL_COPY_VALUE(&$$, &$1);
}
;

element:
Expand Down
23 changes: 23 additions & 0 deletions ext/json/json_scanner.re
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ std:
EXP = ( INT | FLOAT ) [eE] [+-]? DIGIT+ ;
NL = "\r"? "\n" ;
WS = [ \t\r]+ ;
CMT_SL = "//" [^\r\n\x00]* ;
CMT_ML = "/*" ( [^*\x00] | ( "*"+ [^*/\x00] ) )* "*"+ "/" ;
EOI = "\000";
CTRL = [\x00-\x1F] ;
UTF8T = [\x80-\xBF] ;
Expand Down Expand Up @@ -203,6 +205,27 @@ std:
goto std;
}
<JS>WS { goto std; }
<JS>CMT_SL {
if (!(s->options & PHP_JSON_ALLOW_COMMENTS)) {
s->errcode = PHP_JSON_ERROR_SYNTAX;
return PHP_JSON_T_ERROR;
}
goto std;
}
<JS>CMT_ML {
php_json_ctype *p;
if (!(s->options & PHP_JSON_ALLOW_COMMENTS)) {
s->errcode = PHP_JSON_ERROR_SYNTAX;
return PHP_JSON_T_ERROR;
}
for (p = s->token; p < s->cursor; p++) {
if (*p == '\n') {
s->line++;
s->line_start = p + 1;
}
}
goto std;
}
<JS>EOI {
if (s->limit < s->cursor) {
return PHP_JSON_T_EOI;
Expand Down
4 changes: 4 additions & 0 deletions ext/json/php_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ static inline void php_json_error_details_clear(php_json_error_details *out) {
#define PHP_JSON_INVALID_UTF8_SUBSTITUTE (1<<21)
#define PHP_JSON_THROW_ON_ERROR (1<<22)

/* json_validate() and json_decode() common options */
#define PHP_JSON_ALLOW_COMMENTS (1<<23)
#define PHP_JSON_ALLOW_TRAILING_COMMAS (1<<24)

/* default depth */
#define PHP_JSON_PARSER_DEFAULT_DEPTH 512

Expand Down
84 changes: 84 additions & 0 deletions ext/json/tests/json_decode_comments.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
--TEST--
json_decode() with JSON_ALLOW_COMMENTS - valid comment usage
--FILE--
<?php

function decode_dump(string $json) {
var_dump(json_decode($json, true, 512, JSON_ALLOW_COMMENTS));
if (json_last_error() !== JSON_ERROR_NONE) {
echo "error: ", json_last_error_msg(), "\n";
}
}

decode_dump("// comment\n1");
decode_dump("1 // comment at EOF without newline");
decode_dump("/* comment */ 1");
decode_dump("1/*c*/");
decode_dump("/**/1");
decode_dump("/***/1");
decode_dump("/*/*/1");
decode_dump("/* /* */1");
decode_dump("[1, // a\n2]");
decode_dump("{\"a\" /*x*/ : /*y*/ 1 /*z*/, // t\n \"b\":2}");
decode_dump('"// not a comment"');
decode_dump('"/* not a comment */"');
decode_dump("//only\n// comments\n[]");
// empty containers whose only content is a comment
decode_dump("[/*x*/]");
decode_dump("{/*x*/}");
decode_dump("[ //x\n]");
decode_dump("{ //x\n}");
// CRLF and bare CR both terminate a line comment
decode_dump("// crlf\r\n[1, 2]");
decode_dump("// bare cr\r1");
// comment bodies are opaque: bytes that are not valid UTF-8 are skipped
decode_dump("/* \xFF */1");
var_dump(json_validate("// c\n{\"a\": [1, 2]}", 512, JSON_ALLOW_COMMENTS));
var_dump(json_validate("{/*x*/}", 512, JSON_ALLOW_COMMENTS));
var_dump(json_validate("/* \xFF */{}", 512, JSON_ALLOW_COMMENTS));

?>
--EXPECT--
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
array(2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
string(16) "// not a comment"
string(19) "/* not a comment */"
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
int(1)
int(1)
bool(true)
bool(true)
bool(true)
69 changes: 69 additions & 0 deletions ext/json/tests/json_decode_comments_errors.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
--TEST--
json_decode() with JSON_ALLOW_COMMENTS - malformed comments and other errors
--FILE--
<?php

function decode_dump(string $json, int $flags = JSON_ALLOW_COMMENTS) {
var_dump(json_decode($json, true, 512, $flags));
echo json_last_error(), ": ", json_last_error_msg(), "\n";
}

// unterminated block comments: error at the comment opener
decode_dump("/*");
decode_dump("/* text");
decode_dump("{\n /* x\n y");
// lone slashes are still syntax errors
decode_dump("/");
decode_dump("/ /");
// the first */ closes the comment, the stray one is an error
decode_dump("/* /* */ */1");
// hash comments are not supported
decode_dump("# comment\n1");
// comment-only documents behave like whitespace-only documents
decode_dump(" ", 0);
decode_dump("//x");
decode_dump("/*x*/");
decode_dump("// x\n");
// comments separate tokens but do not join values
decode_dump("1 2", 0);
decode_dump("1/*c*/2");
// a comment does not replace a required colon
decode_dump("{\"a\" /*c*/ 1}");

try {
json_decode("/*", true, 512, JSON_ALLOW_COMMENTS | JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo "JsonException: ", $e->getCode(), " ", $e->getMessage(), "\n";
}

?>
--EXPECT--
NULL
4: Syntax error near location 1:1
NULL
4: Syntax error near location 1:1
NULL
4: Syntax error near location 2:2
NULL
4: Syntax error near location 1:1
NULL
4: Syntax error near location 1:1
NULL
4: Syntax error near location 1:10
NULL
4: Syntax error near location 1:1
NULL
4: Syntax error near location 1:2
NULL
4: Syntax error near location 1:4
NULL
4: Syntax error near location 1:6
NULL
4: Syntax error near location 2:1
NULL
4: Syntax error near location 1:3
NULL
4: Syntax error near location 1:7
NULL
4: Syntax error near location 1:12
JsonException: 4 Syntax error near location 1:1
61 changes: 61 additions & 0 deletions ext/json/tests/json_decode_comments_trailing_flags_off.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
--TEST--
JSONC syntax is rejected when JSON_ALLOW_COMMENTS / JSON_ALLOW_TRAILING_COMMAS are not set
--FILE--
<?php

function decode_dump(string $json, int $flags = 0) {
var_dump(json_decode($json, true, 512, $flags));
echo json_last_error(), ": ", json_last_error_msg(), "\n";
}

// no flags: comments are syntax errors at the first slash
decode_dump("// x");
decode_dump("/* x */ 1");
decode_dump("/**/");
decode_dump("[1, 2] // t");
// no flags: trailing commas are syntax errors at the closer
decode_dump("[1,]");
decode_dump("{\"a\":1,}");
decode_dump("[1, 2,]");
// one flag does not enable the other relaxation
decode_dump("[1,]", JSON_ALLOW_COMMENTS);
decode_dump("[1, /*c*/ 2]", JSON_ALLOW_TRAILING_COMMAS);
// scanner errors right after a comma keep their specific error codes
decode_dump("[1,\x01]");
decode_dump("[1,\x80]");
decode_dump("[1,\"\\ud834\"]");
decode_dump("{\"a\":1,\x01}");
// validate behaves identically
var_dump(json_validate("// x\n1", 512, JSON_ALLOW_TRAILING_COMMAS));
var_dump(json_validate("[1,]", 512, JSON_ALLOW_COMMENTS));

?>
--EXPECT--
NULL
4: Syntax error near location 1:1
NULL
4: Syntax error near location 1:1
NULL
4: Syntax error near location 1:1
NULL
4: Syntax error near location 1:8
NULL
4: Syntax error near location 1:4
NULL
4: Syntax error near location 1:8
NULL
4: Syntax error near location 1:7
NULL
4: Syntax error near location 1:4
NULL
4: Syntax error near location 1:5
NULL
3: Control character error, possibly incorrectly encoded near location 1:4
NULL
5: Malformed UTF-8 characters, possibly incorrectly encoded near location 1:4
NULL
10: Single unpaired UTF-16 surrogate in unicode escape near location 1:4
NULL
3: Control character error, possibly incorrectly encoded near location 1:8
bool(false)
bool(false)
Loading
Loading