From e2c5913a5067c1868f732184944d14d747c68bbe Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 24 Oct 2018 15:43:37 +0200 Subject: [PATCH] :construction: some changes to the BSON code - added fuzz testers - added some reference files - made an exception text more clear --- Makefile | 9 ++ include/nlohmann/detail/meta/type_traits.hpp | 12 +- .../nlohmann/detail/output/binary_writer.hpp | 20 +++- single_include/nlohmann/json.hpp | 32 ++++-- test/Makefile | 6 +- test/data/json.org/1.json.bson | Bin 0 -> 393 bytes test/data/json.org/2.json.bson | Bin 0 -> 216 bytes test/data/json.org/3.json.bson | Bin 0 -> 406 bytes test/data/json.org/4.json.bson | Bin 0 -> 2786 bytes test/data/json.org/5.json.bson | Bin 0 -> 730 bytes test/data/json_tests/pass3.json.bson | Bin 0 -> 123 bytes test/src/fuzzer-parse_bson.cpp | 68 ++++++++++++ test/src/unit-bson.cpp | 103 ++++++++++++++++-- 13 files changed, 218 insertions(+), 32 deletions(-) create mode 100644 test/data/json.org/1.json.bson create mode 100644 test/data/json.org/2.json.bson create mode 100644 test/data/json.org/3.json.bson create mode 100644 test/data/json.org/4.json.bson create mode 100644 test/data/json.org/5.json.bson create mode 100644 test/data/json_tests/pass3.json.bson create mode 100644 test/src/fuzzer-parse_bson.cpp diff --git a/Makefile b/Makefile index 135db65a..b6784151 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ all: @echo "cppcheck - analyze code with cppcheck" @echo "doctest - compile example files and check their output" @echo "fuzz_testing - prepare fuzz testing of the JSON parser" + @echo "fuzz_testing_bson - prepare fuzz testing of the BSON parser" @echo "fuzz_testing_cbor - prepare fuzz testing of the CBOR parser" @echo "fuzz_testing_msgpack - prepare fuzz testing of the MessagePack parser" @echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser" @@ -220,6 +221,14 @@ fuzz_testing: find test/data/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases @echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer" +fuzz_testing_bson: + rm -fr fuzz-testing + mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out + $(MAKE) parse_bson_fuzzer -C test CXX=afl-clang++ + mv test/parse_bson_fuzzer fuzz-testing/fuzzer + find test/data -size -5k -name *.bson | xargs -I{} cp "{}" fuzz-testing/testcases + @echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer" + fuzz_testing_cbor: rm -fr fuzz-testing mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index efe878f6..4c4c4d3d 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -193,13 +193,13 @@ struct is_constructible_object_type_impl < static constexpr bool value = std::is_constructible::value and - std::is_same::value or - (has_from_json::value or - has_non_default_from_json < - BasicJsonType, - typename ConstructibleObjectType::mapped_type >::value); + (has_from_json::value or + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value)); }; template diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index bebfa936..7f9f0be3 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -976,14 +976,22 @@ class binary_writer { switch (j.type()) { - default: - JSON_THROW(type_error::create(317, "JSON value of type " + std::to_string(static_cast(j.type())) + " cannot be serialized to requested format")); - break; - case value_t::discarded: - break; case value_t::object: + { write_bson_object(*j.m_value.object); break; + } + + case value_t::discarded: + { + break; + } + + default: + { + JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()))); + break; + } } } @@ -1009,7 +1017,7 @@ class binary_writer std::memcpy(vec.data(), &n, sizeof(NumberType)); // step 2: write array to output (with possible reordering) - if (is_little_endian && !OutputIsLittleEndian) + if (is_little_endian and not OutputIsLittleEndian) { // reverse byte order prior to conversion if necessary std::reverse(vec.begin(), vec.end()); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 29f0cd0d..0664cc75 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -553,13 +553,13 @@ struct is_constructible_object_type_impl < static constexpr bool value = std::is_constructible::value and - std::is_same::value or - (has_from_json::value or - has_non_default_from_json < - BasicJsonType, - typename ConstructibleObjectType::mapped_type >::value); + (has_from_json::value or + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value)); }; template @@ -9234,14 +9234,22 @@ class binary_writer { switch (j.type()) { - default: - JSON_THROW(type_error::create(317, "JSON value of type " + std::to_string(static_cast(j.type())) + " cannot be serialized to requested format")); - break; - case value_t::discarded: - break; case value_t::object: + { write_bson_object(*j.m_value.object); break; + } + + case value_t::discarded: + { + break; + } + + default: + { + JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()))); + break; + } } } @@ -9267,7 +9275,7 @@ class binary_writer std::memcpy(vec.data(), &n, sizeof(NumberType)); // step 2: write array to output (with possible reordering) - if (is_little_endian && !OutputIsLittleEndian) + if (is_little_endian and not OutputIsLittleEndian) { // reverse byte order prior to conversion if necessary std::reverse(vec.begin(), vec.end()); diff --git a/test/Makefile b/test/Makefile index afbb1ba5..4f00cbc7 100644 --- a/test/Makefile +++ b/test/Makefile @@ -10,6 +10,7 @@ SOURCES = src/unit.cpp \ src/unit-algorithms.cpp \ src/unit-allocator.cpp \ src/unit-alt-string.cpp \ + src/unit-bson.cpp \ src/unit-capacity.cpp \ src/unit-cbor.cpp \ src/unit-class_const_iterator.cpp \ @@ -90,12 +91,15 @@ check: $(OBJECTS) $(TESTCASES) ############################################################################## FUZZER_ENGINE = src/fuzzer-driver_afl.cpp -FUZZERS = parse_afl_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer +FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer fuzzers: $(FUZZERS) parse_afl_fuzzer: $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_json.cpp -o $@ +parse_bson_fuzzer: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bson.cpp -o $@ + parse_cbor_fuzzer: $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_cbor.cpp -o $@ diff --git a/test/data/json.org/1.json.bson b/test/data/json.org/1.json.bson new file mode 100644 index 0000000000000000000000000000000000000000..e14e9b92304d8a37a803af2e6c870496679aefd6 GIT binary patch literal 393 zcmZ9IF;BxV5QPs@#8!!s?HJeqi4Ksm1qDTsQgsLe>oqw-R40yXr=ne$`PW>UHkDYc z``)wP)A{!d;JnnvIkH>W^%VCMRU252lvd8eY{a+5%jFwk6|Pp6H!uZ&BwJz-JkMVq z=fRUWxi!tUh6}N>c#$51`4fIbn(S*b=1@BW*PGk0NsuC6dCjtpQmu!qnj6S*&dD!MMN!TM69v*R`#?sxft=33008zvFbx0z literal 0 HcmV?d00001 diff --git a/test/data/json.org/3.json.bson b/test/data/json.org/3.json.bson new file mode 100644 index 0000000000000000000000000000000000000000..deb7c5391fc76590baf686212ee5a4407c404dc6 GIT binary patch literal 406 zcmZuuJx{|h5Ir0L14v|N@xTBb+H^#Si3NlZLsh|mgq--|SU7eRUnu1t@CW!8{5vKl z&Mgukb$jpb-Mg>%Er4ucwLyY6#zJE`7{~x1c*tz448j95p`10oMNhEHv|@lgZANkMQxLJQ;DFgxGKrJf;K!XekU#&afsF8l;3Ji9>>#uv1$&&oaJG7q zm)PT`wmYmyR@^uEn=nM2cQE2$nq?e1Rasifb9wxaPvo>HPUJ|Q7H5!0`-*UsMv1g^ z>s89&^zX=;lj&YwhXvd-&KtC*A^1$UTkXHk(Nm7JNwUDjBQ_uNr%tp>@OVdSYa-eaP03l zp`ggjNQYz9rJi5PI-L6Q!(bLJrh$S?2P@uHYi6<8^QkAq#7Hxe{FuI`?>wGHHr%GGP z&2pC~namF}E){zh>I<9wG}jY--Hmm&W-N2-xB?2P^8?4;hr{ViDxL^6pGp4p&gsFL z&s<*#Ba$aehPtz!p7QJew>btn_5ti7nc>aYC?@-EDyM5cK;mS4%H52$$pM~DMw=9AQNYnF)i3m{U76}UKexDeAT#NhuUDz;8+j=V>mU$n)6HaT zwAEABeVI(+u6?Db?S$!$o)1R;Y2{FbKSmSwaE0}KQ6U`Y^hhefzM?!9)>UiA4M*eMN8deuyw^wW6K)0J-cL0;q$=GxijEm)%yLM@lrNN{S{5V0 zK7@`NbV?=b&Kz2MWjv79mB!T2swN1QFYWxbAz{N>_`5wES+z5MjT#I@o}zXC-NHy> zJfuO%Fjb*hKLw6Pvz}EJtYrl^@$4{9WiA@PK7!j+n^8?~Lke}521L5)rVAOYyPbg* zob6Bic)mD%(m6gkii{U_lL>3N6+aC$=)i2Ev{sD7gkU+1)AKV&xbQicakMOk3xNjt-A1S&?_C1ACWO1UR1~ zM%4`j$Mf}OKyMd-0;dDgh6L9m9?%+iO+;@)Np6VPEIeVPx4k*wJAgQ?X%X?8R zq66A~H9a+YF+c9JJ8-%?5qNo?TiKSURnVVr7EcJog&O3ewp7?X8ywrJfQW|@-u;c?U}2*kb<Nx)@}vLm*`L#Hy)Tn};vRA>MkKgD zBy_j9mvb0G6vuKGJ3?PbZM4?VJBVLv=nV5!CcU|3%C7c>f%~=%GZ+~_SKzE|f)cJ?2`17T^*ofA z&D_{kwnXtke-v@w)v}aRy@tfEtu0;i1-BE$+Y(=FJ7>VS@29pAhGx!HifP=rst5_; zi2r`SkIPpo>nTph)~(vcw4pUi+Q~Jjr@pAz0_>j!;MyWy2~K34p*9T9i@_vYEXB|| zQXr$!YwZIiduiRy-Z>Z_C^h*Ki+8;2+21Wu0I literal 0 HcmV?d00001 diff --git a/test/data/json_tests/pass3.json.bson b/test/data/json_tests/pass3.json.bson new file mode 100644 index 0000000000000000000000000000000000000000..a9c07cf31a86c952d6916dc358cb3f545c8b3473 GIT binary patch literal 123 zcmYL>F%E-35Cd1_1@tNW0-C%6iV!KZDHgO6Ah?U}_K;9MA9s}YjQtalTlemt7%V$p z>TW6Et2sa9Ls+!|J}((gE;XCh!KeXoTy643s$n+7!r>nIap+b|?lkwpINIeRCTL1} GHl%-k10(+c literal 0 HcmV?d00001 diff --git a/test/src/fuzzer-parse_bson.cpp b/test/src/fuzzer-parse_bson.cpp new file mode 100644 index 00000000..4ba20dff --- /dev/null +++ b/test/src/fuzzer-parse_bson.cpp @@ -0,0 +1,68 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (fuzz test support) +| | |__ | | | | | | version 3.3.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +This file implements a parser test suitable for fuzz testing. Given a byte +array data, it performs the following steps: + +- j1 = from_bson(data) +- vec = to_bson(j1) +- j2 = from_bson(vec) +- assert(j1 == j2) + +The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer +drivers. + +Licensed under the MIT License . +*/ + +#include +#include +#include + +using json = nlohmann::json; + +// see http://llvm.org/docs/LibFuzzer.html +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + try + { + // step 1: parse input + std::vector vec1(data, data + size); + json j1 = json::from_bson(vec1); + + try + { + // step 2: round trip + std::vector vec2 = json::to_bson(j1); + + // parse serialization + json j2 = json::from_bson(vec2); + + // serializations must match + assert(json::to_bson(j2) == vec2); + } + catch (const json::parse_error&) + { + // parsing a CBOR serialization must not fail + assert(false); + } + } + catch (const json::parse_error&) + { + // parse errors are ok, because input may be random bytes + } + catch (const json::type_error&) + { + // type errors can occur during parsing, too + } + catch (const json::out_of_range&) + { + // out of range errors can occur during parsing, too + } + + // return 0 - non-zero return values are reserved for future use + return 0; +} diff --git a/test/src/unit-bson.cpp b/test/src/unit-bson.cpp index 3449b698..ef94a807 100644 --- a/test/src/unit-bson.cpp +++ b/test/src/unit-bson.cpp @@ -49,7 +49,7 @@ TEST_CASE("BSON") { json j = nullptr; REQUIRE_THROWS_AS(json::to_bson(j), json::type_error&); - CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] JSON value of type 0 cannot be serialized to requested format"); + CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is null"); } SECTION("boolean") @@ -58,14 +58,14 @@ TEST_CASE("BSON") { json j = true; REQUIRE_THROWS_AS(json::to_bson(j), json::type_error&); - CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] JSON value of type 4 cannot be serialized to requested format"); + CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is boolean"); } SECTION("false") { json j = false; REQUIRE_THROWS_AS(json::to_bson(j), json::type_error&); - CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] JSON value of type 4 cannot be serialized to requested format"); + CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is boolean"); } } @@ -73,28 +73,28 @@ TEST_CASE("BSON") { json j = 42; REQUIRE_THROWS_AS(json::to_bson(j), json::type_error&); - CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] JSON value of type 5 cannot be serialized to requested format"); + CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is number"); } SECTION("float") { json j = 4.2; REQUIRE_THROWS_AS(json::to_bson(j), json::type_error&); - CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] JSON value of type 7 cannot be serialized to requested format"); + CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is number"); } SECTION("string") { json j = "not supported"; REQUIRE_THROWS_AS(json::to_bson(j), json::type_error&); - CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] JSON value of type 3 cannot be serialized to requested format"); + CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is string"); } SECTION("array") { json j = std::vector {1, 2, 3, 4, 5, 6, 7}; REQUIRE_THROWS_AS(json::to_bson(j), json::type_error&); - CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] JSON value of type 2 cannot be serialized to requested format"); + CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array"); } } @@ -1126,3 +1126,92 @@ TEST_CASE("BSON numerical data") } } } + +TEST_CASE("BSON roundtrips", "[hide]") +{ + SECTION("reference files") + { + for (std::string filename : + { + "test/data/json.org/1.json", + "test/data/json.org/2.json", + "test/data/json.org/3.json", + "test/data/json.org/4.json", + "test/data/json.org/5.json" + }) + { + CAPTURE(filename); + + SECTION(filename + ": std::vector") + { + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse BSON file + std::ifstream f_bson(filename + ".bson", std::ios::binary); + std::vector packed( + (std::istreambuf_iterator(f_bson)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_bson(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + SECTION(filename + ": std::ifstream") + { + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse BSON file + std::ifstream f_bson(filename + ".bson", std::ios::binary); + json j2; + CHECK_NOTHROW(j2 = json::from_bson(f_bson)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + SECTION(filename + ": uint8_t* and size") + { + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse BSON file + std::ifstream f_bson(filename + ".bson", std::ios::binary); + std::vector packed( + (std::istreambuf_iterator(f_bson)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_bson({packed.data(), packed.size()})); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + SECTION(filename + ": output to output adapters") + { + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse BSON file + std::ifstream f_bson(filename + ".bson", std::ios::binary); + std::vector packed( + (std::istreambuf_iterator(f_bson)), + std::istreambuf_iterator()); + + SECTION(filename + ": output adapters: std::vector") + { + std::vector vec; + json::to_bson(j1, vec); + CHECK(vec == packed); + } + } + } + } +}