Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for trailing garbage (comma) at the end of objects and arrays, before closing bracket #156

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
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 .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:

- name: Build and run tests
run: |
c89 -ansi -Wall -Wpedantic -Werror -pedantic -pedantic-errors -D_ANSI_SOURCE -DJSON_TRACK_SOURCE -I. json.c tests/test.c -lm -o json-test
c89 -ansi -Wall -Wpedantic -Werror -pedantic -pedantic-errors -D_ANSI_SOURCE -DJSON_TRACK_SOURCE -DJSON_LINTING=1 -I. json.c tests/test.c -lm -o json-test
cd tests
../json-test

Expand Down Expand Up @@ -103,7 +103,7 @@ jobs:
run: cl /permissive- /Zc:preprocessor /Zc:throwingNew /volatile:iso /utf-8 /std:c++latest /Zc:__cplusplus /Wall /DJSON_TRACK_SOURCE /LD /Tp json.c

- name: Build test executable
run: cl /permissive- /Zc:preprocessor /Zc:throwingNew /volatile:iso /utf-8 /std:c++latest /Zc:__cplusplus /Wall /DJSON_TRACK_SOURCE /I . /MT /Tp json.c /Tp tests\test.c /Fetests\json-test.exe
run: cl /permissive- /Zc:preprocessor /Zc:throwingNew /volatile:iso /utf-8 /std:c++latest /Zc:__cplusplus /Wall /DJSON_TRACK_SOURCE /DJSON_LINTING=1 /I . /MT /Tp json.c /Tp tests\test.c /Fetests\json-test.exe

- name: Run tests
if: matrix.arch != 'amd64_arm' && matrix.arch != 'amd64_arm64'
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ Example usage:
* `"-Djson_int_t=long long"`
* `-Djson_int_t=__int128`

### `JSON_LINTING`
Loads code branches used for strict ECMA-404 JSON parsing.

Setting JSON_LINTING to 1 additionally requires the use of the runtime option (json_enable_comments) to perform linting.
Setting JSON_LINTING to 2 performs linting without requiring the runtime option.

Example usage:
* `-DJSON_LINTING=1`
* `-DJSON_LINTING=2`


Runtime Options
---------------
Expand All @@ -69,6 +79,10 @@ settings |= json_enable_comments;
```
Enables C-style `// line` and `/* block */` comments.
```c
settings |= json_enable_linting;
```
Enables strict ECMA-404 JSON parsing
```c
size_t value_extra
```
The amount of space (if any) to allocate at the end of each `json_value`, in
Expand Down
10 changes: 10 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ if test "x$enable_value_pos" = xyes; then
CFLAGS="$CFLAGS -DJSON_TRACK_SOURCE"
fi

AC_ARG_ENABLE([enable-linting],
[AS_HELP_STRING([--enable-linting],
[ Enables strict ECMA-404 JSON parsing. @<:@default=disabled@:>@])],
[enable_linting="$enableval"],
[enable_linting=no]
)
if test "x$enable_linting" = xyes; then
CFLAGS="$CFLAGS -DJSON_LINTING=1"
fi

AC_SUBST(VERSION_MAJOR, $VERSION_MAJOR)

AC_CONFIG_FILES([
Expand Down
8 changes: 8 additions & 0 deletions examples/test_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ int main(int argc, char** argv)

value = json_parse(json,file_size);

/*
* Use json_parse_ex to enable runtime options
*/
/* json_settings settings = { 0 }; */
/* settings.settings |= json_enable_comments; */
/* settings.settings |= json_enable_linting; */
/* value = json_parse_ex(&settings, json, file_size, 0); */

if (value == NULL) {
fprintf(stderr, "Unable to parse data\n");
free(file_contents);
Expand Down
93 changes: 92 additions & 1 deletion json.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ typedef unsigned int json_uchar;

const struct _json_value json_value_none;

#if defined JSON_LINTING && JSON_LINTING == 1
int compile_option_linting = 0;
#elif defined JSON_LINTING && JSON_LINTING == 2
int compile_option_linting = 1;
#endif

/*
* Called when parsing closing brackets of objects and arrays
* Decrements json_char ptr, ignoring whitespace, allowing digits and { } [ ] " true false null
*/
static int trailing_garbage (const json_char * ptr);

static unsigned char hex_value (json_char c)
{
if (isdigit((unsigned char)c))
Expand Down Expand Up @@ -550,9 +562,23 @@ json_value * json_parse_ex (json_settings * settings,
case ']':

if (top && top->type == json_array)
{
#if defined JSON_LINTING && (JSON_LINTING == 1 || JSON_LINTING == 2)
if (state.settings.settings & json_enable_linting || compile_option_linting == 1)
{
if (trailing_garbage(state.ptr))
{
sprintf (error, "Trailing garbage before %d:%d",
state.cur_line, state.cur_col);
goto e_failed;
}
}
#endif
flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next;
}
else
{ sprintf (error, "%u:%u: Unexpected `]`", line_and_col);
{
sprintf (error, "%u:%u: Unexpected `]`", line_and_col);
goto e_failed;
}

Expand Down Expand Up @@ -742,6 +768,18 @@ json_value * json_parse_ex (json_settings * settings,

case '}':

#if defined JSON_LINTING && (JSON_LINTING == 1 || JSON_LINTING == 2)
if (state.settings.settings & json_enable_linting || compile_option_linting == 1)
{
if (trailing_garbage(state.ptr))
{
sprintf (error, "Trailing garbage before %d:%d",
state.cur_line, state.cur_col);
goto e_failed;
}
}
#endif

flags = (flags & ~ flag_need_comma) | flag_next;
break;

Expand Down Expand Up @@ -1055,3 +1093,56 @@ void json_value_free (json_value * value)
settings.mem_free = default_free;
json_value_free_ex (&settings, value);
}

int trailing_garbage (const json_char * ptr)
{
json_char * marker = (char *)ptr;
do
{
marker--;
}
while (isspace(*marker));

switch (*marker)
{
case '}':
case '{':
case ']':
case '[':
case '"':
return 0;

case 'e':
/* Allow true */
if (strncmp(marker-3, "true", 4) == 0)
{
return 0;
}

/* Allow false */
if (strncmp(marker-4, "false", 5) == 0)
{
return 0;
}
return 1;

case 'l':
/* Allow null */
if (strncmp(marker-3, "null", 4) == 0)
{
return 0;
}
return 1;

default:
/* Allow digits */
if (isdigit(*marker))
{
return 0;
}
else
{
return 1;
}
}
}
1 change: 1 addition & 0 deletions json.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ typedef struct
} json_settings;

#define json_enable_comments 0x01
#define json_enable_linting 0x02

typedef enum
{
Expand Down
3 changes: 3 additions & 0 deletions tests/invalid-0011.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"a": "b",
}
3 changes: 3 additions & 0 deletions tests/invalid-0012.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"foo",
]
5 changes: 3 additions & 2 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ static int json_verify(const char * filename_format, unsigned highest_file_num,

if(extensions == 1)
{
settings.settings = json_enable_comments;
settings.settings |= json_enable_comments;
settings.settings |= json_enable_linting;
}

for(test_num = 0; ; ++test_num)
Expand Down Expand Up @@ -227,7 +228,7 @@ int main(void)
JSON_COMPARE_STRING(1, "\\ud841\\udf31", "𠜱"); /* TODO: this should actually succeed after PR #58 is merged */

if(0 != json_verify( "valid-%04u.json", 13, 0, 0)){ exit_code = EXIT_FAILURE; }
if(0 != json_verify( "invalid-%04u.json", 10, 0, 1)){ exit_code = EXIT_FAILURE; }
if(0 != json_verify( "invalid-%04u.json", 12, 0, 1)){ exit_code = EXIT_FAILURE; }
if(0 != json_verify( "ext-valid-%04u.json", 3, 1, 0)){ exit_code = EXIT_FAILURE; }
if(0 != json_verify("ext-invalid-%04u.json", 2, 1, 1)){ exit_code = EXIT_FAILURE; }

Expand Down