| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| #include <gtest/gtest.h> |
| #include <tvm/ffi/container/array.h> |
| #include <tvm/ffi/container/map.h> |
| #include <tvm/ffi/extra/json.h> |
| #include <tvm/ffi/extra/structural_equal.h> |
| |
| #include <cmath> |
| |
| namespace { |
| |
| using namespace tvm::ffi; |
| |
| inline bool FastMathSafeIsNaN(double x) { |
| #ifdef __FAST_MATH__ |
| // Bit-level NaN detection (IEEE 754 double) |
| // IEEE 754 standard: https://en.wikipedia.org/wiki/IEEE_754 |
| // NaN is encoded as all 1s in the exponent and non-zero in the mantissa |
| static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double size"); |
| uint64_t bits = *reinterpret_cast<const uint64_t*>(&x); |
| uint64_t exponent = (bits >> 52) & 0x7FF; |
| uint64_t mantissa = bits & 0xFFFFFFFFFFFFFull; |
| return (exponent == 0x7FF) && (mantissa != 0); |
| #else |
| // Safe to use std::isnan when fast-math is off |
| return std::isnan(x); |
| #endif |
| } |
| |
| inline bool FastMathSafeIsInf(double x) { |
| #ifdef __FAST_MATH__ |
| // IEEE 754 standard: https://en.wikipedia.org/wiki/IEEE_754 |
| // Inf is encoded as all 1s in the exponent and zero in the mantissa |
| static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double size"); |
| uint64_t bits = *reinterpret_cast<const uint64_t*>(&x); |
| uint64_t exponent = (bits >> 52) & 0x7FF; |
| uint64_t mantissa = bits & 0xFFFFFFFFFFFFFull; |
| // inf is encoded as all 1s in the exponent and zero in the mantissa |
| return (exponent == 0x7FF) && (mantissa == 0); |
| #else |
| return std::isinf(x); |
| #endif |
| } |
| |
| TEST(JSONParser, BoolNull) { |
| // boolean value |
| EXPECT_EQ(json::Parse("true").cast<bool>(), true); |
| EXPECT_EQ(json::Parse("false").cast<bool>(), false); |
| EXPECT_EQ(json::Parse("null"), nullptr); |
| } |
| |
| TEST(JSONParser, WrongBoolNull) { |
| String error_msg; |
| EXPECT_EQ(json::Parse("nul", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| EXPECT_EQ(json::Parse("fals", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| EXPECT_EQ(json::Parse("\n\nfx", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 3 column 1 (char 2)"); |
| EXPECT_EQ(json::Parse("fx", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| EXPECT_EQ(json::Parse("n1", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| EXPECT_EQ(json::Parse("t1", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| EXPECT_EQ(json::Parse("f1", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| } |
| |
| TEST(JSONParser, Number) { |
| // number |
| EXPECT_EQ(json::Parse("123").cast<int64_t>(), 123); |
| EXPECT_EQ(json::Parse("-124").cast<int64_t>(), -124); |
| EXPECT_EQ(json::Parse("123.456").cast<double>(), 123.456); |
| // parsing scientific notation |
| EXPECT_EQ(json::Parse("1.456e12").cast<double>(), 1.456e12); |
| // NaN |
| EXPECT_EQ(FastMathSafeIsNaN(json::Parse("NaN").cast<double>()), true); |
| // Infinity |
| EXPECT_EQ(FastMathSafeIsInf(json::Parse("Infinity").cast<double>()), true); |
| // -Infinity |
| EXPECT_EQ(FastMathSafeIsInf(-json::Parse("-Infinity").cast<double>()), true); |
| |
| // Test zero variants |
| EXPECT_EQ(json::Parse("0").cast<int64_t>(), 0); |
| EXPECT_EQ(json::Parse("-0").cast<double>(), -0.0); |
| EXPECT_EQ(json::Parse("0.0").cast<double>(), 0.0); |
| |
| // Test very large numbers |
| EXPECT_EQ(json::Parse("9223372036854775807").cast<int64_t>(), |
| std::numeric_limits<int64_t>::max()); |
| EXPECT_EQ(json::Parse("-9223372036854775808").cast<int64_t>(), |
| std::numeric_limits<int64_t>::min()); |
| |
| // Test very small decimals |
| EXPECT_EQ(json::Parse("1e-10").cast<double>(), 1e-10); |
| EXPECT_EQ(json::Parse("-1e-10").cast<double>(), -1e-10); |
| |
| // Test scientific notation edge cases |
| EXPECT_EQ(json::Parse("1E+10").cast<double>(), 1E+10); |
| EXPECT_EQ(json::Parse("1e+10").cast<double>(), 1e+10); |
| EXPECT_EQ(json::Parse("1E-10").cast<double>(), 1E-10); |
| EXPECT_EQ(json::Parse("123.456E+10").cast<double>(), 123.456E+10); |
| } |
| |
| TEST(JSONParser, WrongNumber) { |
| String error_msg; |
| EXPECT_EQ(json::Parse("123.456.789", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| |
| // Test invalid number formats |
| EXPECT_EQ(json::Parse("123e", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| EXPECT_EQ(json::Parse("123e+", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| EXPECT_EQ(json::Parse("123E-", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| } |
| |
| TEST(JSONParser, String) { |
| EXPECT_EQ(json::Parse("\"hello\"").cast<String>(), "hello"); |
| EXPECT_EQ(json::Parse("\n\t \"hello\"\n\r").cast<String>(), "hello"); |
| EXPECT_EQ(json::Parse("\"hello\\nworld\"").cast<String>(), "hello\nworld"); |
| EXPECT_EQ(json::Parse("\"\"").cast<String>(), ""); |
| // test escape characters |
| EXPECT_EQ(json::Parse("\"\\ta\\n\\/\\f\\\"\\\\\"").cast<String>(), "\ta\n/\f\"\\"); |
| // test unicode code point |
| EXPECT_EQ(json::Parse("\"\\u0041\"").cast<String>(), "A"); |
| // test unicode surrogate pair |
| EXPECT_EQ(json::Parse("\"\\uD83D\\uDE04hello\"").cast<String>(), u8"\U0001F604hello"); |
| } |
| |
| TEST(JSONParser, WrongString) { |
| String error_msg; |
| EXPECT_EQ(json::Parse("\"hello", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Unterminated string starting at: line 1 column 1 (char 0)"); |
| |
| EXPECT_EQ(json::Parse("\"hello\x01\"", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Invalid control character at: line 1 column 7 (char 6)"); |
| |
| EXPECT_EQ(json::Parse("\"hello\\uxx\"", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Invalid \\uXXXX escape: line 1 column 8 (char 7)"); |
| |
| EXPECT_EQ(json::Parse("\"hello\\uDC00\\uDE04\"", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Invalid surrogate pair of \\uXXXX escapes: line 1 column 8 (char 7)"); |
| |
| EXPECT_EQ(json::Parse("\"hello\\uD800\"", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Invalid surrogate pair of \\uXXXX escapes: line 1 column 8 (char 7)"); |
| |
| EXPECT_EQ(json::Parse("\"hello\\uD800\\uxx\"", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Invalid \\uXXXX escape: line 1 column 15 (char 14)"); |
| |
| EXPECT_EQ(json::Parse("\"hello\\a\"", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Invalid \\escape: line 1 column 8 (char 7)"); |
| } |
| |
| TEST(JSONParser, Array) { |
| EXPECT_TRUE(StructuralEqual()(json::Parse("[]"), json::Array{})); |
| |
| EXPECT_TRUE(StructuralEqual()(json::Parse("[1, 2,\n\t\"a\"]"), json::Array{1, 2, "a"})); |
| } |
| |
| TEST(JSONParser, WrongArray) { |
| String error_msg; |
| |
| EXPECT_EQ(json::Parse("]", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| |
| EXPECT_EQ(json::Parse("[1,]", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 4 (char 3)"); |
| |
| EXPECT_EQ(json::Parse("[", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 2 (char 1)"); |
| |
| EXPECT_EQ(json::Parse("[1a", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting ',' delimiter: line 1 column 3 (char 2)"); |
| |
| EXPECT_EQ(json::Parse("[1,2,3", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting ',' delimiter: line 1 column 7 (char 6)"); |
| |
| EXPECT_EQ(json::Parse("[1] a", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Extra data: line 1 column 6 (char 5)"); |
| } |
| |
| TEST(JSONParser, Object) { |
| EXPECT_TRUE(StructuralEqual()(json::Parse("{}"), json::Object{})); |
| |
| EXPECT_TRUE(StructuralEqual()(json::Parse("{\"a\": 1, \n\"b\": \t\"c\"} "), |
| json::Object{{"a", 1}, {"b", "c"}})); |
| } |
| |
| TEST(JSONParser, ObjectOrderPreserving) { |
| auto obj = json::Parse(R"({"c": 1, "a": 2, "b": 3} )"); |
| json::Array keys; |
| for (auto& [key, value] : obj.cast<json::Object>()) { |
| keys.push_back(key); |
| } |
| EXPECT_TRUE(StructuralEqual()(keys, json::Array{"c", "a", "b"})); |
| } |
| |
| TEST(JSONParser, WrongObject) { |
| String error_msg; |
| EXPECT_EQ(json::Parse("{\"a\":", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 6 (char 5)"); |
| |
| EXPECT_EQ(json::Parse("{", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, |
| "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"); |
| |
| // Test incomplete structures |
| EXPECT_EQ(json::Parse("{\"incomplete\"", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting ':' delimiter: line 1 column 14 (char 13)"); |
| } |
| |
| TEST(JSONParser, NestedObject) { |
| EXPECT_TRUE( |
| StructuralEqual()(json::Parse("{\"a\": \t{\"b\": 1}, \n\"c\": [1, 2, 3]}"), |
| json::Object{{"a", json::Object{{"b", 1}}}, {"c", json::Array{1, 2, 3}}})); |
| |
| EXPECT_TRUE(StructuralEqual()( |
| json::Parse("{\"a\": \t{\"b\": 1}, \n\"c\": [1, null, Infinity]}"), |
| json::Object{{"a", json::Object{{"b", 1}}}, |
| {"c", json::Array{1, nullptr, std::numeric_limits<double>::infinity()}}})); |
| |
| EXPECT_TRUE(StructuralEqual()( |
| json::Parse("[{}, {\"a\": [1.1, 1000000]}]"), |
| json::Array{json::Object{}, json::Object{{"a", json::Array{1.1, 1000000}}}})); |
| } |
| |
| TEST(JSONParser, WrongNestedObject) { |
| String error_msg; |
| EXPECT_EQ(json::Parse("{\"a\":\n\n[1]", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting ',' delimiter: line 3 column 4 (char 10)"); |
| |
| EXPECT_EQ(json::Parse("{\"a\":\n\n[abc]}", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 3 column 2 (char 8)"); |
| } |
| |
| // edge cases |
| TEST(JSONParser, WhitespaceHandling) { |
| // Test various whitespace characters |
| EXPECT_EQ(json::Parse(" \t\n\r true \t\n\r ").cast<bool>(), true); |
| EXPECT_EQ(json::Parse("\n\n\n123\n\n\n").cast<int64_t>(), 123); |
| EXPECT_EQ(json::Parse(" \"hello world\" ").cast<String>(), "hello world"); |
| |
| // Test whitespace in arrays and objects |
| EXPECT_TRUE(StructuralEqual()(json::Parse(" [ 1 , 2 , 3 ] "), json::Array{1, 2, 3})); |
| |
| EXPECT_TRUE(StructuralEqual()(json::Parse(" { \"a\" : 1 , \"b\" : 2 } "), |
| json::Object{{"a", 1}, {"b", 2}})); |
| } |
| |
| TEST(JSONParser, WrongEmptyAndMinimalInputs) { |
| String error_msg; |
| // Test empty string |
| EXPECT_EQ(json::Parse("", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)"); |
| |
| // Test only whitespace |
| EXPECT_EQ(json::Parse(" \t\n ", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Expecting value: line 2 column 5 (char 9)"); |
| } |
| |
| TEST(JSONParser, UnicodeEdgeCases) { |
| // Test various unicode characters |
| EXPECT_EQ(json::Parse("\"\\u0000\"").cast<String>(), std::string("\0", 1)); |
| // replace using \U to avoid encoding issues |
| EXPECT_EQ(json::Parse("\"\\u00FF\"").cast<String>(), u8"\U000000FF"); |
| EXPECT_EQ(json::Parse("\"\\u4E2D\\u6587\"").cast<String>(), u8"\U00004E2D\U00006587"); |
| |
| // Test multiple surrogate pairs |
| EXPECT_EQ(json::Parse("\"\\uD83D\\uDE00\\uD83D\\uDE01\"").cast<String>(), |
| u8"\U0001F600\U0001F601"); |
| } |
| |
| TEST(JSONParser, RawUTF8Bytes) { |
| // Regression test: raw UTF-8 bytes (>= 0x80) must not be rejected as control characters. |
| // This failed when the parser used signed char comparison: *cur_ < ' ' |
| EXPECT_EQ(json::Parse("\"\xE4\xB8\xAD\xE6\x96\x87\"").cast<String>(), "\xE4\xB8\xAD\xE6\x96\x87"); |
| } |
| |
| TEST(JSONParser, LargeInputs) { |
| // Test large array |
| std::string large_array = "["; |
| for (int i = 0; i < 1000; ++i) { |
| if (i > 0) large_array += ","; |
| large_array += std::to_string(i); |
| } |
| large_array += "]"; |
| |
| auto result = json::Parse(large_array); |
| EXPECT_TRUE(result != nullptr); |
| EXPECT_EQ(result.cast<json::Array>().size(), 1000); |
| |
| // Test large object |
| std::string large_object = "{"; |
| for (int i = 0; i < 500; ++i) { |
| if (i > 0) large_object += ","; |
| large_object += "\"key" + std::to_string(i) + "\":" + std::to_string(i); |
| } |
| large_object += "}"; |
| |
| result = json::Parse(large_object); |
| EXPECT_TRUE(result != nullptr); |
| EXPECT_EQ(result.cast<json::Object>().size(), 500); |
| } |
| |
| TEST(JSONParser, MixedDataTypes) { |
| // Test complex nested structure with all data types |
| std::string complex_json = R"({ |
| "null_value": null, |
| "boolean_true": true, |
| "boolean_false": false, |
| "integer": 42, |
| "negative_integer": -42, |
| "float": 3.14159, |
| "scientific": 1.23e-4, |
| "string": "hello world", |
| "unicode_string": "Hello \u4e16\u754c \ud83c\udf0d", |
| "empty_string": "", |
| "empty_array": [], |
| "empty_object": {}, |
| "number_array": [1, 2, 3, 4, 5], |
| "mixed_array": [1, "two", true, null, 3.14], |
| "nested_object": { |
| "level1": { |
| "level2": { |
| "data": [1, 2, {"nested_array": [true, false]}] |
| } |
| } |
| } |
| })"; |
| |
| auto result = json::Parse(complex_json); |
| |
| // Create expected structure for comparison |
| json::Object expected{ |
| {"null_value", nullptr}, |
| {"boolean_true", true}, |
| {"boolean_false", false}, |
| {"integer", 42}, |
| {"negative_integer", -42}, |
| {"float", 3.14159}, |
| {"scientific", 1.23e-4}, |
| {"string", "hello world"}, |
| {"unicode_string", u8"Hello \U00004E16\U0000754C \U0001F30D"}, |
| {"empty_string", ""}, |
| {"empty_array", json::Array{}}, |
| {"empty_object", json::Object{}}, |
| {"number_array", json::Array{1, 2, 3, 4, 5}}, |
| {"mixed_array", json::Array{1, "two", true, nullptr, 3.14}}, |
| {"nested_object", |
| json::Object{ |
| {"level1", |
| json::Object{ |
| {"level2", |
| json::Object{ |
| {"data", |
| json::Array{1, 2, |
| json::Object{{"nested_array", json::Array{true, false}}}}}}}}}}}}; |
| |
| EXPECT_TRUE(StructuralEqual()(result, expected)); |
| } |
| |
| TEST(JSONParser, WrongExtraData) { |
| String error_msg; |
| |
| EXPECT_EQ(json::Parse("truee", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Extra data: line 1 column 5 (char 4)"); |
| |
| EXPECT_EQ(json::Parse("true false", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Extra data: line 1 column 6 (char 5)"); |
| |
| EXPECT_EQ(json::Parse("123 456", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Extra data: line 1 column 5 (char 4)"); |
| |
| EXPECT_EQ(json::Parse("\"hello\" \"world\"", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Extra data: line 1 column 9 (char 8)"); |
| |
| EXPECT_EQ(json::Parse("{} []", &error_msg), nullptr); |
| EXPECT_EQ(error_msg, "Extra data: line 1 column 4 (char 3)"); |
| } |
| } // namespace |