From 97b8df691292d62d2b13e883c21c473e88c8ac86 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 9 Jul 2020 23:57:05 -0500 Subject: [PATCH] Added JSON5 support for parsing special numbers NaN and both Infinities, with tests. Special handling was added to yajl_test since different OSs don't always generate the same output for special numbers (nan/NaN/...). --- modules/libcom/src/yajl/yajl_lex.c | 107 ++++++++++++++++----------- modules/libcom/src/yajl/yajl_lex.h | 1 + modules/libcom/test/yajlTestCases.pm | 44 ++++++++++- modules/libcom/test/yajl_test.c | 11 ++- 4 files changed, 117 insertions(+), 46 deletions(-) diff --git a/modules/libcom/src/yajl/yajl_lex.c b/modules/libcom/src/yajl/yajl_lex.c index 2439d46a7..49dfb7544 100644 --- a/modules/libcom/src/yajl/yajl_lex.c +++ b/modules/libcom/src/yajl/yajl_lex.c @@ -399,6 +399,25 @@ yajl_lex_number(yajl_lexer lexer, const unsigned char * jsonText, c = readChar(lexer, jsonText, offset); } + if (c == 'I') { + const char * want = "nfinity"; + do { + RETURN_IF_EOF; + c = readChar(lexer, jsonText, offset); + if (c != *want) { + unreadChar(lexer, offset); + lexer->error = yajl_lex_invalid_string; + return yajl_tok_error; + } + } while (*(++want)); + if (!lexer->allowJson5) { + unreadChar(lexer, offset); + lexer->error = yajl_lex_unallowed_special_number; + return yajl_tok_error; + } + return yajl_tok_double; + } + /* a single zero, hex number, or a series of decimal digits */ if (c == '0') { numRd++; @@ -534,6 +553,23 @@ yajl_lex_comment(yajl_lexer lexer, const unsigned char * jsonText, return tok; } +/* Macro to reduce code duplication in yajl_lex_lex() */ +#define LEX_WANT(tring) \ + const char * want = tring; \ + do { \ + if (*offset >= jsonTextLen) { \ + tok = yajl_tok_eof; \ + goto lexed; \ + } \ + c = readChar(lexer, jsonText, offset); \ + if (c != *want) { \ + unreadChar(lexer, offset); \ + lexer->error = yajl_lex_invalid_string; \ + tok = yajl_tok_error; \ + goto lexed; \ + } \ + } while (*(++want)) + yajl_tok yajl_lex_lex(yajl_lexer lexer, const unsigned char * jsonText, size_t jsonTextLen, size_t * offset, @@ -579,59 +615,42 @@ yajl_lex_lex(yajl_lexer lexer, const unsigned char * jsonText, startOffset++; break; case 't': { - const char * want = "rue"; - do { - if (*offset >= jsonTextLen) { - tok = yajl_tok_eof; - goto lexed; - } - c = readChar(lexer, jsonText, offset); - if (c != *want) { - unreadChar(lexer, offset); - lexer->error = yajl_lex_invalid_string; - tok = yajl_tok_error; - goto lexed; - } - } while (*(++want)); + LEX_WANT("rue"); tok = yajl_tok_bool; goto lexed; } case 'f': { - const char * want = "alse"; - do { - if (*offset >= jsonTextLen) { - tok = yajl_tok_eof; - goto lexed; - } - c = readChar(lexer, jsonText, offset); - if (c != *want) { - unreadChar(lexer, offset); - lexer->error = yajl_lex_invalid_string; - tok = yajl_tok_error; - goto lexed; - } - } while (*(++want)); + LEX_WANT("alse"); tok = yajl_tok_bool; goto lexed; } case 'n': { - const char * want = "ull"; - do { - if (*offset >= jsonTextLen) { - tok = yajl_tok_eof; - goto lexed; - } - c = readChar(lexer, jsonText, offset); - if (c != *want) { - unreadChar(lexer, offset); - lexer->error = yajl_lex_invalid_string; - tok = yajl_tok_error; - goto lexed; - } - } while (*(++want)); + LEX_WANT("ull"); tok = yajl_tok_null; goto lexed; } + case 'I': { + LEX_WANT("nfinity"); + if (!lexer->allowJson5) { + unreadChar(lexer, offset); + lexer->error = yajl_lex_unallowed_special_number; + tok = yajl_tok_error; + } else { + tok = yajl_tok_double; + } + goto lexed; + } + case 'N': { + LEX_WANT("aN"); + if (!lexer->allowJson5) { + unreadChar(lexer, offset); + lexer->error = yajl_lex_unallowed_special_number; + tok = yajl_tok_error; + } else { + tok = yajl_tok_double; + } + goto lexed; + } case '"': { tok = yajl_lex_string(lexer, (const unsigned char *) jsonText, jsonTextLen, offset); @@ -757,7 +776,7 @@ yajl_lex_error_to_string(yajl_lex_error error) "decimal point."; case yajl_lex_missing_integer_after_minus: return "malformed number, a digit is required after the " - "minus sign."; + "plus/minus sign."; case yajl_lex_unallowed_comment: return "probable comment found in input text, comments are " "not enabled."; @@ -765,6 +784,8 @@ yajl_lex_error_to_string(yajl_lex_error error) return "malformed number, a hex digit is required after the 0x/0X."; case yajl_lex_unallowed_hex_integer: return "probable hex number found, JSON5 is not enabled."; + case yajl_lex_unallowed_special_number: + return "special number Infinity or NaN found, JSON5 is not enabled."; } return "unknown error code"; } diff --git a/modules/libcom/src/yajl/yajl_lex.h b/modules/libcom/src/yajl/yajl_lex.h index 87b7069f3..c5eec8f00 100644 --- a/modules/libcom/src/yajl/yajl_lex.h +++ b/modules/libcom/src/yajl/yajl_lex.h @@ -103,6 +103,7 @@ typedef enum { yajl_lex_unallowed_comment, yajl_lex_missing_hex_digit_after_0x, yajl_lex_unallowed_hex_integer, + yajl_lex_unallowed_special_number, } yajl_lex_error; const char * yajl_lex_error_to_string(yajl_lex_error error); diff --git a/modules/libcom/test/yajlTestCases.pm b/modules/libcom/test/yajlTestCases.pm index 0a89c97dc..4d5b65d49 100644 --- a/modules/libcom/test/yajlTestCases.pm +++ b/modules/libcom/test/yajlTestCases.pm @@ -13,7 +13,7 @@ sub cases { -5 ], input => [ - "[ .1e2, 10., +3.141569, -.1e4]", + "[ .1e2, 10., +3.141569, -.1e4, NaN, Infinity, +Infinity, -Infinity ]", "" ], gives => [ @@ -22,6 +22,10 @@ sub cases { "double: 10", "double: 3.14157", "double: -1000", + "double: NaN", + "double: Infinity", + "double: Infinity", + "double: -Infinity", "array close ']'", "memory leaks:\t0" ] @@ -2798,6 +2802,18 @@ sub cases { "memory leaks:\t0" ] }, + { + name => "infinity", + opts => [], + input => [ + "Infinity", + "" + ], + gives => [ + "lexical error: special number Infinity or NaN found, JSON5 is not enabled.", + "memory leaks:\t0" + ] + }, { name => "integers", opts => [], @@ -2884,7 +2900,7 @@ sub cases { "string: 'blue'", "string: 'baby where are you?'", "string: 'oh boo hoo!'", - "lexical error: malformed number, a digit is required after the minus sign.", + "lexical error: malformed number, a digit is required after the plus/minus sign.", "memory leaks:\t0" ] }, @@ -2922,6 +2938,18 @@ sub cases { "memory leaks:\t0" ] }, + { + name => "minus_infinity", + opts => [], + input => [ + "-Infinity", + "" + ], + gives => [ + "lexical error: special number Infinity or NaN found, JSON5 is not enabled.", + "memory leaks:\t0" + ] + }, { name => "missing_integer_after_decimal_point", opts => [], @@ -2962,6 +2990,18 @@ sub cases { "memory leaks:\t0" ] }, + { + name => "nan", + opts => [], + input => [ + "NaN", + "" + ], + gives => [ + "lexical error: special number Infinity or NaN found, JSON5 is not enabled.", + "memory leaks:\t0" + ] + }, { name => "non_utf8_char_in_string", opts => [], diff --git a/modules/libcom/test/yajl_test.c b/modules/libcom/test/yajl_test.c index 178ca6e04..079366680 100644 --- a/modules/libcom/test/yajl_test.c +++ b/modules/libcom/test/yajl_test.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -85,7 +86,15 @@ static int test_yajl_integer(void *ctx, long long integerVal) static int test_yajl_double(void *ctx, double doubleVal) { - printf("double: %g\n", doubleVal); + if (isnan(doubleVal)) { + printf("double: NaN\n"); + } + else if (isinf(doubleVal)) { + printf("double: %sInfinity\n", doubleVal > 0 ? "" : "-"); + } + else { + printf("double: %g\n", doubleVal); + } return 1; }