mirror of
https://github.com/nlohmann/json.git
synced 2026-11-13 15:35:23 +00:00
A BON8 string ends at the first byte that cannot continue it. When a
string ends a value and is not terminated by 0xFF, that byte belongs to
whatever follows the value, but the reader has already taken it from the
input and keeps it in its push-back buffer. With strict = true the
end-of-input check reports it; with strict = false the buffer was
discarded, so a following value in a stream lost its first byte or bytes
without an error ({0x61, 0x90} read "a" and then dropped the 0 that
follows).
Report parse_error.110 "string at the end of the value is not terminated
by 0xFF" when the push-back buffer is not empty once a BON8 value is
complete and strict is false, as bon8.md requires the final 0xFF. Output
of to_bon8 always has it, so canonical input is not affected.
Fixes #6110.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1155 lines
64 KiB
C++
1155 lines
64 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
|
|
// line *before* including json.hpp, since the library #undefs it once the header
|
|
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
|
|
// tests of deprecated functions are skipped if these functions are deleted
|
|
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
|
|
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
|
#endif
|
|
|
|
#include <nlohmann/json.hpp>
|
|
using nlohmann::json;
|
|
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
|
#endif
|
|
|
|
#include <cmath>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "make_test_data_available.hpp"
|
|
#include "round_trip_corpus.hpp"
|
|
#include "test_utils.hpp"
|
|
#include "sax_countdown.hpp"
|
|
using utils::SaxCountdown;
|
|
|
|
namespace
|
|
{
|
|
using bytes = std::vector<std::uint8_t>;
|
|
|
|
/// @return the string with the given bytes
|
|
std::string str(const bytes& b)
|
|
{
|
|
// converted one by one: constructing the string from the byte range
|
|
// converts implicitly, which -fsanitize=integer reports for bytes >= 0x80
|
|
std::string result;
|
|
result.reserve(b.size());
|
|
for (const auto c : b)
|
|
{
|
|
result.push_back(static_cast<char>(c));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// check that @a j is serialized to @a expected and that @a expected is read back as @a j
|
|
void check_bon8(const json& j, const bytes& expected)
|
|
{
|
|
CAPTURE(j)
|
|
CHECK(json::to_bon8(j) == expected);
|
|
|
|
const json decoded = json::from_bon8(expected);
|
|
CHECK(decoded == j);
|
|
// integers are not read back as floats and vice versa
|
|
CHECK(decoded.type() == j.type());
|
|
|
|
// a stream is read byte by byte rather than in bulk
|
|
std::istringstream stream(str(expected));
|
|
CHECK(json::from_bon8(stream) == decoded);
|
|
}
|
|
|
|
/// @return @a b followed by @a tail
|
|
bytes concat(bytes b, const bytes& tail)
|
|
{
|
|
b.insert(b.end(), tail.begin(), tail.end());
|
|
return b;
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("BON8")
|
|
{
|
|
SECTION("individual values")
|
|
{
|
|
SECTION("discarded")
|
|
{
|
|
// a discarded value cannot be serialized to BON8
|
|
const json j = json::value_t::discarded;
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
|
}
|
|
|
|
SECTION("discarded values nested in a container")
|
|
{
|
|
const json discarded = json::value_t::discarded;
|
|
|
|
SECTION("in a small array")
|
|
{
|
|
const json j = {1, discarded};
|
|
#if JSON_DIAGNOSTICS
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BON8", json::type_error&);
|
|
#else
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
|
#endif
|
|
}
|
|
|
|
SECTION("as a small object value")
|
|
{
|
|
json j;
|
|
j["a"] = discarded;
|
|
#if JSON_DIAGNOSTICS
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/a) cannot serialize discarded value to BON8", json::type_error&);
|
|
#else
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
|
#endif
|
|
}
|
|
|
|
SECTION("in a large array")
|
|
{
|
|
const json j = {1, 2, 3, 4, 5, discarded};
|
|
#if JSON_DIAGNOSTICS
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/5) cannot serialize discarded value to BON8", json::type_error&);
|
|
#else
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
|
#endif
|
|
}
|
|
|
|
SECTION("as a large object value")
|
|
{
|
|
json j = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", 5}};
|
|
j["f"] = discarded;
|
|
#if JSON_DIAGNOSTICS
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/f) cannot serialize discarded value to BON8", json::type_error&);
|
|
#else
|
|
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
SECTION("null")
|
|
{
|
|
check_bon8(nullptr, {0xFA});
|
|
}
|
|
|
|
SECTION("boolean")
|
|
{
|
|
check_bon8(true, {0xF9});
|
|
check_bon8(false, {0xF8});
|
|
}
|
|
|
|
SECTION("integers")
|
|
{
|
|
// test vectors from HikoGUI (src/hikogui/codec/BON8_tests.cpp,
|
|
// Copyright Take Vos 2021, Boost Software License 1.0), which
|
|
// cover the first and last value of each encoding
|
|
SECTION("positive")
|
|
{
|
|
check_bon8(0u, {0x90});
|
|
check_bon8(39u, {0xB7});
|
|
check_bon8(40u, {0xC2, 0x00});
|
|
check_bon8(3879u, {0xDF, 0x7F});
|
|
check_bon8(3880u, {0xE0, 0x00, 0x00});
|
|
check_bon8(528167u, {0xEF, 0x7F, 0xFF});
|
|
check_bon8(528168u, {0xF0, 0x00, 0x00, 0x00});
|
|
check_bon8(67637031u, {0xF7, 0x7F, 0xFF, 0xFF});
|
|
check_bon8(67637032u, {0x8C, 0x04, 0x08, 0x0F, 0x28});
|
|
check_bon8(2147483647u, {0x8C, 0x7F, 0xFF, 0xFF, 0xFF});
|
|
check_bon8(2147483648u, {0x8D, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00});
|
|
check_bon8(9223372036854775807u, {0x8D, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
|
}
|
|
|
|
SECTION("negative")
|
|
{
|
|
check_bon8(-1, {0xB8});
|
|
check_bon8(-10, {0xC1});
|
|
check_bon8(-11, {0xC2, 0xC0});
|
|
check_bon8(-1930, {0xDF, 0xFF});
|
|
check_bon8(-1931, {0xE0, 0xC0, 0x00});
|
|
check_bon8(-264074, {0xEF, 0xFF, 0xFF});
|
|
check_bon8(-264075, {0xF0, 0xC0, 0x00, 0x00});
|
|
check_bon8(-33818506, {0xF7, 0xFF, 0xFF, 0xFF});
|
|
check_bon8(-33818507, {0x8C, 0xFD, 0xFB, 0xF8, 0x75});
|
|
check_bon8(-2147483648LL, {0x8C, 0x80, 0x00, 0x00, 0x00});
|
|
check_bon8(-2147483649LL, {0x8D, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF});
|
|
check_bon8((std::numeric_limits<std::int64_t>::min)(), {0x8D, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
|
}
|
|
|
|
SECTION("values inside the ranges")
|
|
{
|
|
check_bon8(1u, {0x91});
|
|
check_bon8(100u, {0xC2, 0x3C});
|
|
check_bon8(1000u, {0xC9, 0x40});
|
|
check_bon8(100000u, {0xE2, 0x77, 0x78});
|
|
check_bon8(1000000u, {0xF0, 0x07, 0x33, 0x18});
|
|
check_bon8(-9, {0xC0});
|
|
check_bon8(-100, {0xC3, 0xD9});
|
|
check_bon8(-100000, {0xE5, 0xFF, 0x15});
|
|
check_bon8(-1000000, {0xF0, 0xCB, 0x3A, 0xB5});
|
|
}
|
|
|
|
SECTION("every integer from -300000 to 600000")
|
|
{
|
|
for (std::int64_t i = -300000; i <= 600000; ++i)
|
|
{
|
|
const json j = i;
|
|
const auto packed = json::to_bon8(j);
|
|
CHECK(packed.size() == (i >= -10 && i <= 39 ? 1u : i >= -1930 && i <= 3879 ? 2u : i >= -264074 && i <= 528167 ? 3u : 4u));
|
|
const json decoded = json::from_bon8(packed);
|
|
if (decoded != j)
|
|
{
|
|
CAPTURE(i)
|
|
CHECK(decoded == j);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("signed values are read back as unsigned when not negative")
|
|
{
|
|
const json j = json::from_bon8(json::to_bon8(json(static_cast<std::int64_t>(1000))));
|
|
CHECK(j.is_number_unsigned());
|
|
CHECK(j == 1000);
|
|
}
|
|
|
|
SECTION("unsigned integers above int64")
|
|
{
|
|
json _;
|
|
const json j = 9223372036854775808u;
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(j), "[json.exception.out_of_range.407] integer number 9223372036854775808 cannot be represented by BON8 as it does not fit int64", json::out_of_range&);
|
|
}
|
|
}
|
|
|
|
SECTION("floating-point numbers")
|
|
{
|
|
SECTION("one-byte values")
|
|
{
|
|
check_bon8(-1.0, {0xFB});
|
|
check_bon8(0.0, {0xFC});
|
|
check_bon8(1.0, {0xFD});
|
|
}
|
|
|
|
SECTION("binary32")
|
|
{
|
|
check_bon8(2.0, {0x8E, 0x40, 0x00, 0x00, 0x00});
|
|
check_bon8(0.5, {0x8E, 0x3F, 0x00, 0x00, 0x00});
|
|
check_bon8(-1.5, {0x8E, 0xBF, 0xC0, 0x00, 0x00});
|
|
check_bon8(3.4028234663852886e38, {0x8E, 0x7F, 0x7F, 0xFF, 0xFF});
|
|
}
|
|
|
|
SECTION("binary64")
|
|
{
|
|
check_bon8(100000000.1, {0x8F, 0x41, 0x97, 0xD7, 0x84, 0x00, 0x66, 0x66, 0x66});
|
|
check_bon8(3.14159, {0x8F, 0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E});
|
|
check_bon8(1e300, {0x8F, 0x7E, 0x37, 0xE4, 0x3C, 0x88, 0x00, 0x75, 0x9C});
|
|
}
|
|
|
|
SECTION("-0.0 is written as binary32")
|
|
{
|
|
const json j = -0.0;
|
|
CHECK(json::to_bon8(j) == bytes{0x8E, 0x80, 0x00, 0x00, 0x00});
|
|
const json decoded = json::from_bon8(json::to_bon8(j));
|
|
CHECK(decoded.get<double>() == 0.0);
|
|
CHECK(std::signbit(decoded.get<double>()));
|
|
}
|
|
|
|
SECTION("infinity is written as binary32")
|
|
{
|
|
check_bon8(std::numeric_limits<double>::infinity(), {0x8E, 0x7F, 0x80, 0x00, 0x00});
|
|
check_bon8(-std::numeric_limits<double>::infinity(), {0x8E, 0xFF, 0x80, 0x00, 0x00});
|
|
}
|
|
|
|
SECTION("NaN is written as binary32 0x7F800001")
|
|
{
|
|
const json j = std::numeric_limits<double>::quiet_NaN();
|
|
CHECK(json::to_bon8(j) == bytes{0x8E, 0x7F, 0x80, 0x00, 0x01});
|
|
CHECK(std::isnan(json::from_bon8(json::to_bon8(j)).get<double>()));
|
|
}
|
|
}
|
|
|
|
SECTION("strings")
|
|
{
|
|
SECTION("empty string")
|
|
{
|
|
check_bon8("", {0xFF});
|
|
}
|
|
|
|
SECTION("ASCII")
|
|
{
|
|
check_bon8("a", {'a', 0xFF});
|
|
check_bon8("This is a string.", concat(bytes{'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.'}, {0xFF}));
|
|
check_bon8(str({0x00}), {0x00, 0xFF});
|
|
check_bon8(str({0x7F}), {0x7F, 0xFF});
|
|
}
|
|
|
|
SECTION("multi-byte UTF-8")
|
|
{
|
|
check_bon8("\xC2\xA3", {0xC2, 0xA3, 0xFF}); // U+00A3
|
|
check_bon8("\xEF\xB8\xBB", {0xEF, 0xB8, 0xBB, 0xFF}); // U+FE3B
|
|
check_bon8("\xF0\x9F\x80\x84", {0xF0, 0x9F, 0x80, 0x84, 0xFF}); // U+1F004
|
|
check_bon8("\xC2\x80", {0xC2, 0x80, 0xFF}); // U+0080
|
|
check_bon8("\xDF\xBF", {0xDF, 0xBF, 0xFF}); // U+07FF
|
|
check_bon8("\xE0\xA0\x80", {0xE0, 0xA0, 0x80, 0xFF}); // U+0800
|
|
check_bon8("\xED\x9F\xBF", {0xED, 0x9F, 0xBF, 0xFF}); // U+D7FF
|
|
check_bon8("\xEE\x80\x80", {0xEE, 0x80, 0x80, 0xFF}); // U+E000
|
|
check_bon8("\xF0\x90\x80\x80", {0xF0, 0x90, 0x80, 0x80, 0xFF}); // U+10000
|
|
check_bon8("\xF4\x8F\xBF\xBF", {0xF4, 0x8F, 0xBF, 0xBF, 0xFF}); // U+10FFFF
|
|
check_bon8("a\xC2\xA3" "b", {'a', 0xC2, 0xA3, 'b', 0xFF});
|
|
}
|
|
|
|
SECTION("invalid UTF-8 cannot be written")
|
|
{
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0x80", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({'a', 0xC0, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xC0", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xE0, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xE0", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xED, 0xA0, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xF4, 0x90, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xF4", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xF5, 0x80, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xF5", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xC2, 'a'})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC2", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({'a', 0xE2, 0x82})), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xE2", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(json::object({{str({0xFF}), 1}})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
|
// after a run of ASCII characters that is checked 8 bytes at a time
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(std::string(17, 'a') + str({0xC0})), "[json.exception.type_error.316] invalid UTF-8 byte at index 17: 0xC0", json::type_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::to_bon8(std::string(8, 'a') + "\xC3\xA4" + std::string(8, 'a') + str({0xE2, 0x82})), "[json.exception.type_error.316] invalid UTF-8 byte at index 18: 0xE2", json::type_error&);
|
|
}
|
|
}
|
|
|
|
SECTION("arrays")
|
|
{
|
|
check_bon8(json::array(), {0x80});
|
|
check_bon8({false}, {0x81, 0xF8});
|
|
check_bon8({false, nullptr}, {0x82, 0xF8, 0xFA});
|
|
check_bon8({false, nullptr, true}, {0x83, 0xF8, 0xFA, 0xF9});
|
|
check_bon8({false, nullptr, true, 1.0}, {0x84, 0xF8, 0xFA, 0xF9, 0xFD});
|
|
check_bon8({false, nullptr, true, 1.0, json::array(), 0.0}, {0x85, 0xF8, 0xFA, 0xF9, 0xFD, 0x80, 0xFC, 0xFE});
|
|
// an array that starts with a number: -1, 0, and 1 have bytes of their own
|
|
check_bon8({-1.0, 0.0, 1.0}, {0x83, 0xFB, 0xFC, 0xFD});
|
|
check_bon8({1.0, -1.0, -0.0}, {0x83, 0xFD, 0xFB, 0x8E, 0x80, 0x00, 0x00, 0x00});
|
|
check_bon8({{{1}}}, {0x81, 0x81, 0x81, 0x91});
|
|
check_bon8({{{"foo"}}}, {0x81, 0x81, 0x81, 'f', 'o', 'o', 0xFF});
|
|
check_bon8({{{""}}}, {0x81, 0x81, 0x81, 0xFF});
|
|
|
|
SECTION("large array")
|
|
{
|
|
json j = json::array();
|
|
bytes expected = {0x85};
|
|
for (int i = 0; i < 1000; ++i)
|
|
{
|
|
j.push_back(nullptr);
|
|
expected.push_back(0xFA);
|
|
}
|
|
expected.push_back(0xFE);
|
|
check_bon8(j, expected);
|
|
}
|
|
}
|
|
|
|
SECTION("objects")
|
|
{
|
|
check_bon8(json::object(), {0x86});
|
|
check_bon8({{"foo", nullptr}}, {0x87, 'f', 'o', 'o', 0xFA});
|
|
check_bon8({{"", true}, {"foo", nullptr}}, {0x88, 0xFF, 0xF9, 'f', 'o', 'o', 0xFA});
|
|
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}}, {0x89, 'a', 0x91, 'b', 0x92, 'c', 0x93});
|
|
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}, {0x8A, 'a', 0x91, 'b', 0x92, 'c', 0x93, 'd', 0x94});
|
|
const json five = {{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}, {"five", 5}};
|
|
check_bon8(five, {0x8B, 'f', 'i', 'v', 'e', 0x95, 'f', 'o', 'u', 'r', 0x94, 'o', 'n', 'e', 0x91, 't', 'h', 'r', 'e', 'e', 0x93, 't', 'w', 'o', 0x92, 0xFE});
|
|
}
|
|
|
|
SECTION("binary values are written as arrays of integers")
|
|
{
|
|
CHECK(json::to_bon8(json::binary({})) == bytes{0x80});
|
|
CHECK(json::to_bon8(json::binary({0x00, 0x27, 0x28, 0xFF})) == bytes{0x84, 0x90, 0xB7, 0xC2, 0x00, 0xC3, 0x57});
|
|
CHECK(json::to_bon8(json::binary({1, 2, 3, 4, 5}, 42)) == bytes{0x85, 0x91, 0x92, 0x93, 0x94, 0x95, 0xFE});
|
|
CHECK(json::to_bon8({"a", json::binary({1})}) == bytes{0x82, 'a', 0x81, 0x91});
|
|
CHECK(json::from_bon8(json::to_bon8(json::binary({1, 2, 3, 4, 5}))) == json({1, 2, 3, 4, 5}));
|
|
}
|
|
}
|
|
|
|
SECTION("examples from the specification")
|
|
{
|
|
check_bon8("ab", {'a', 'b', 0xFF});
|
|
check_bon8({"ab", "bc"}, {0x82, 'a', 'b', 0xFF, 'b', 'c', 0xFF});
|
|
check_bon8({"a", "b", "c", "d", "e"}, {0x85, 'a', 0xFF, 'b', 0xFF, 'c', 0xFF, 'd', 0xFF, 'e', 0xFE});
|
|
check_bon8({{"ab", 1}, {"bc", 2}}, {0x88, 'a', 'b', 0x91, 'b', 'c', 0x92});
|
|
// the specification prints this example without the end-of-string
|
|
// markers after "b" and "c", which its own rules require: without
|
|
// them, the bytes read as the single string "bcd"
|
|
check_bon8({{"a", {"b", "c"}}, {"d", 1}}, {0x88, 'a', 0x82, 'b', 0xFF, 'c', 0xFF, 'd', 0x91});
|
|
check_bon8({{"", 1}, {"a", 2}}, {0x88, 0xFF, 0x91, 'a', 0x92});
|
|
}
|
|
|
|
SECTION("examples from the discussion of the specification (#2980)")
|
|
{
|
|
check_bon8({{"a", ""}, {"c", "d"}}, {0x88, 'a', 0xFF, 0xFF, 'c', 0xFF, 'd', 0xFF});
|
|
}
|
|
|
|
SECTION("end of strings")
|
|
{
|
|
SECTION("a string ends at the first byte of an integer")
|
|
{
|
|
check_bon8({{"a", 100}}, {0x87, 'a', 0xC2, 0x3C});
|
|
check_bon8({{"a", -9}}, {0x87, 'a', 0xC0});
|
|
check_bon8({{"a", -10}}, {0x87, 'a', 0xC1});
|
|
check_bon8({{"a", 5}}, {0x87, 'a', 0x95});
|
|
check_bon8({{"a", 100000}}, {0x87, 'a', 0xE2, 0x77, 0x78});
|
|
check_bon8({{"a", -1000000}}, {0x87, 'a', 0xF0, 0xCB, 0x3A, 0xB5});
|
|
check_bon8({{"a", 67637032}}, {0x87, 'a', 0x8C, 0x04, 0x08, 0x0F, 0x28});
|
|
check_bon8({{"a", 2147483648}}, {0x87, 'a', 0x8D, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00});
|
|
check_bon8({"\xC2\xA3", 100}, {0x82, 0xC2, 0xA3, 0xC2, 0x3C});
|
|
check_bon8({"\xF0\x9F\x80\x84", -1000000}, {0x82, 0xF0, 0x9F, 0x80, 0x84, 0xF0, 0xCB, 0x3A, 0xB5});
|
|
}
|
|
|
|
SECTION("a string ends at the first byte of other values")
|
|
{
|
|
check_bon8({"a", 2.0}, {0x82, 'a', 0x8E, 0x40, 0x00, 0x00, 0x00});
|
|
check_bon8({"a", 0.1}, {0x82, 'a', 0x8F, 0x3F, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A});
|
|
check_bon8({"a", nullptr, "b", true, "c", false}, {0x85, 'a', 0xFA, 'b', 0xF9, 'c', 0xF8, 0xFE});
|
|
check_bon8({"a", -1.0, "b", 0.0, "c", 1.0}, {0x85, 'a', 0xFB, 'b', 0xFC, 'c', 0xFD, 0xFE});
|
|
check_bon8({"a", json::array()}, {0x82, 'a', 0x80});
|
|
check_bon8({"a", json::object()}, {0x82, 'a', 0x86});
|
|
check_bon8({{"a", {1, 2, 3, 4, 5}}}, {0x87, 'a', 0x85, 0x91, 0x92, 0x93, 0x94, 0x95, 0xFE});
|
|
check_bon8({{"a", {{"b", 1}, {"c", 2}, {"d", 3}, {"e", 4}, {"f", 5}}}}, {0x87, 'a', 0x8B, 'b', 0x91, 'c', 0x92, 'd', 0x93, 'e', 0x94, 'f', 0x95, 0xFE});
|
|
}
|
|
|
|
SECTION("a string ends at the end of its container")
|
|
{
|
|
check_bon8({1, 2, 3, 4, "e"}, {0x85, 0x91, 0x92, 0x93, 0x94, 'e', 0xFE});
|
|
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", "f"}}, {0x8B, 'a', 0x91, 'b', 0x92, 'c', 0x93, 'd', 0x94, 'e', 0xFF, 'f', 0xFE});
|
|
check_bon8({{1, 2, 3, 4, "e"}, 1}, {0x82, 0x85, 0x91, 0x92, 0x93, 0x94, 'e', 0xFE, 0x91});
|
|
check_bon8({{1, 2, 3, 4, "\xC2\xA3"}, 100}, {0x82, 0x85, 0x91, 0x92, 0x93, 0x94, 0xC2, 0xA3, 0xFE, 0xC2, 0x3C});
|
|
}
|
|
|
|
SECTION("a string that another string follows is terminated")
|
|
{
|
|
check_bon8({"s", "s"}, {0x82, 's', 0xFF, 's', 0xFF});
|
|
check_bon8({"", "s"}, {0x82, 0xFF, 's', 0xFF});
|
|
check_bon8({"s", ""}, {0x82, 's', 0xFF, 0xFF});
|
|
check_bon8({"", ""}, {0x82, 0xFF, 0xFF});
|
|
check_bon8({{"a", "b"}, {"c", "d"}}, {0x88, 'a', 0xFF, 'b', 0xFF, 'c', 0xFF, 'd', 0xFF});
|
|
check_bon8({{"a", ""}}, {0x87, 'a', 0xFF, 0xFF});
|
|
check_bon8({{"", ""}}, {0x87, 0xFF, 0xFF});
|
|
}
|
|
|
|
SECTION("a container that ends with a string and that a string follows")
|
|
{
|
|
check_bon8({{"a"}, "b"}, {0x82, 0x81, 'a', 0xFF, 'b', 0xFF});
|
|
check_bon8({{{{"a"}}}, "b"}, {0x82, 0x81, 0x81, 0x81, 'a', 0xFF, 'b', 0xFF});
|
|
check_bon8({{"a", {"x"}}, {"b", 1}}, {0x88, 'a', 0x81, 'x', 0xFF, 'b', 0x91});
|
|
check_bon8({{"a", {{"b", "c"}}}, {"d", 1}}, {0x88, 'a', 0x87, 'b', 0xFF, 'c', 0xFF, 'd', 0x91});
|
|
check_bon8({{"a"}, {"b"}}, {0x82, 0x81, 'a', 0x81, 'b', 0xFF});
|
|
}
|
|
|
|
SECTION("a string that ends the message is terminated")
|
|
{
|
|
check_bon8({"a"}, {0x81, 'a', 0xFF});
|
|
check_bon8({{"a", "b"}}, {0x87, 'a', 0xFF, 'b', 0xFF});
|
|
check_bon8({{{"a"}}}, {0x81, 0x81, 0x81, 'a', 0xFF});
|
|
}
|
|
}
|
|
|
|
SECTION("non-canonical input is accepted")
|
|
{
|
|
// an integer with a longer encoding than necessary
|
|
CHECK(json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00, 0x01}) == 1);
|
|
CHECK(json::from_bon8(bytes{0x8D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) == -1);
|
|
// a float with a longer encoding than necessary
|
|
CHECK(json::from_bon8(bytes{0x8F, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) == 1.0);
|
|
CHECK(json::from_bon8(bytes{0x8E, 0x00, 0x00, 0x00, 0x00}) == 0.0);
|
|
// an unsized container with few elements
|
|
CHECK(json::from_bon8(bytes{0x85, 0x91, 0x92, 0xFE}) == json({1, 2}));
|
|
CHECK(json::from_bon8(bytes{0x85, 0xFE}) == json::array());
|
|
CHECK(json::from_bon8(bytes{0x8B, 'a', 0x91, 0xFE}) == json({{"a", 1}}));
|
|
// unsorted keys
|
|
CHECK(json::from_bon8(bytes{0x88, 'b', 0x91, 'a', 0x92}) == json({{"a", 2}, {"b", 1}}));
|
|
// an end-of-string marker that is not needed
|
|
CHECK(json::from_bon8(bytes{0x82, 'a', 0xFF, 0x91}) == json({"a", 1}));
|
|
CHECK(json::from_bon8(bytes{0x87, 'a', 0xFF, 0x91}) == json({{"a", 1}}));
|
|
}
|
|
|
|
SECTION("types of values read")
|
|
{
|
|
CHECK(json::from_bon8(bytes{0x90}).is_number_unsigned());
|
|
CHECK(json::from_bon8(bytes{0xC2, 0x00}).is_number_unsigned());
|
|
CHECK(json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00, 0x00}).is_number_unsigned());
|
|
CHECK(json::from_bon8(bytes{0xB8}).is_number_integer());
|
|
CHECK(!json::from_bon8(bytes{0xB8}).is_number_unsigned());
|
|
CHECK(json::from_bon8(bytes{0xC2, 0xC0}).is_number_integer());
|
|
CHECK(json::from_bon8(bytes{0xFC}).is_number_float());
|
|
CHECK(json::from_bon8(bytes{0x8E, 0x40, 0x00, 0x00, 0x00}).is_number_float());
|
|
CHECK(json::from_bon8(bytes{0xFF}).is_string());
|
|
}
|
|
|
|
SECTION("errors")
|
|
{
|
|
json _;
|
|
|
|
SECTION("empty input")
|
|
{
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes()), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_bon8(bytes(), true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("too short numbers")
|
|
{
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8C}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8E, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8F, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xC2}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE0, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x82, 'a', 0xF0, 0xC0}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
|
}
|
|
|
|
SECTION("unterminated strings")
|
|
{
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{'a'}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 'b'}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x9F, 0x80, 0x84}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x9F, 0x80}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a'}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
}
|
|
|
|
SECTION("input that ends after a UTF-8 lead byte")
|
|
{
|
|
// the lead byte begins either a character or an integer; both are
|
|
// incomplete, so the lead byte must not end the string before it
|
|
for (const bool strict :
|
|
{
|
|
true, false
|
|
})
|
|
{
|
|
CAPTURE(strict)
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xE2}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xF0}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 0xC3, 0xA9, 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x88, 'a', 0x91, 0xE2}, strict), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
|
|
}
|
|
}
|
|
|
|
SECTION("a message that is cut off is not read as a shorter value")
|
|
{
|
|
const json values = {"\xC3\xA9", "a\xE2\x82\xAC", "\xF0\x9F\x98\x80\xC3\xA9", {"a\xC3\xA9"}, {{"\xC3\xA9", "\xE2\x82\xAC"}}, {{"a", {"b\xC3\xA9", 1}}}};
|
|
for (const auto& j : values)
|
|
{
|
|
const bytes message = json::to_bon8(j);
|
|
for (std::size_t length = 0; length < message.size(); ++length)
|
|
{
|
|
CAPTURE(j)
|
|
CAPTURE(length)
|
|
bytes prefix = message;
|
|
prefix.resize(length);
|
|
CHECK(json::from_bon8(prefix, false, false).is_discarded());
|
|
// a stream is read byte by byte rather than in bulk
|
|
std::istringstream stream(str(prefix));
|
|
CHECK(json::from_bon8(stream, false, false).is_discarded());
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("invalid UTF-8")
|
|
{
|
|
// overlong
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE0, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x80, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
|
// surrogate
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xED, 0xA0, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0xA0", json::parse_error&);
|
|
// above U+10FFFF
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF4, 0x90, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x90", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF5, 0x80, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
|
// missing continuation byte
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE2, 0x82, 'a', 0xFF}), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x61", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xE2, 0x82, 'b'}), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x62", json::parse_error&);
|
|
}
|
|
|
|
SECTION("end-of-container marker where a value is expected")
|
|
{
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xFE}), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 0xFE}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8B, 'a', 0xFE}), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
|
}
|
|
|
|
SECTION("keys that are not strings")
|
|
{
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0x91, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0x91", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xFA, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xFA", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0x80, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0x80", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xFE}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xFE", json::parse_error&);
|
|
// an integer that begins with a UTF-8 lead byte
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xC2, 0x00, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xC2", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x88, 'a', 0x91, 0xF0, 0xC0, 0x00, 0x00, 0x91}), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BON8 key: expected a string; last byte: 0xF0", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
|
|
}
|
|
|
|
SECTION("unterminated containers")
|
|
{
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x82, 0x91}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x85, 0x91}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8B, 'a', 0x91}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a'}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a', 0xFF}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
|
}
|
|
|
|
SECTION("strict mode")
|
|
{
|
|
const bytes vec = {0x90, 0x90};
|
|
CHECK(json::from_bon8(vec, false) == 0);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 value: expected end of input; last byte: 0x90", json::parse_error&);
|
|
CHECK(json::from_bon8(vec, true, false).is_discarded());
|
|
|
|
// the byte after a string that ends a container: without strict, the
|
|
// missing 0xFF is reported, as the byte has been taken from the input
|
|
const bytes vec2 = {0x81, 'a', 0x90};
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec2, false), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: string at the end of the value is not terminated by 0xFF; last byte: 0x90", json::parse_error&);
|
|
CHECK(json::from_bon8(vec2, false, false).is_discarded());
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec2), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: expected end of input; last byte: 0x90", json::parse_error&);
|
|
|
|
const bytes vec3 = {0x81, 'a', 0xC2, 0x00};
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec3, false), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: string at the end of the value is not terminated by 0xFF; last byte: 0xC2", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec3), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: expected end of input; last byte: 0xC2", json::parse_error&);
|
|
|
|
// with a terminated string, strict = false leaves the next value in the stream
|
|
std::istringstream terminated(std::string("\x61\xFF\x90", 3));
|
|
CHECK(json::from_bon8(terminated, false) == "a");
|
|
CHECK(terminated.tellg() == 2);
|
|
CHECK(json::from_bon8(terminated, false) == 0);
|
|
|
|
// without the terminator, the next value would be lost
|
|
std::istringstream unterminated(std::string("\x61\x90", 2));
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(unterminated, false), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 value: string at the end of the value is not terminated by 0xFF; last byte: 0x90", json::parse_error&);
|
|
}
|
|
}
|
|
|
|
SECTION("SAX aborts")
|
|
{
|
|
SECTION("start_array(len)")
|
|
{
|
|
const bytes v = {0x83, 0x91, 0x92, 0x93};
|
|
SaxCountdown scp(0);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
|
}
|
|
|
|
SECTION("start_array()")
|
|
{
|
|
const bytes v = {0x85, 0x91, 0xFE};
|
|
SaxCountdown scp(0);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
|
}
|
|
|
|
SECTION("end_array()")
|
|
{
|
|
const bytes v = {0x85, 0x91, 0xFE};
|
|
SaxCountdown scp(2);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
|
}
|
|
|
|
SECTION("start_object(len)")
|
|
{
|
|
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
|
SaxCountdown scp(0);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
|
}
|
|
|
|
SECTION("key()")
|
|
{
|
|
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
|
SaxCountdown scp(1);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
|
}
|
|
|
|
SECTION("end_object()")
|
|
{
|
|
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
|
SaxCountdown scp(3);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
|
}
|
|
|
|
SECTION("values")
|
|
{
|
|
const std::vector<bytes> values =
|
|
{
|
|
{0xFA}, {0xF9}, {0x91}, {0xB8}, {0xC2, 0x00}, {0xC2, 0xC0}, {0xE0, 0x00, 0x00},
|
|
{0x8C, 0, 0, 0, 1}, {0x8D, 0, 0, 0, 0, 0, 0, 0, 1}, {0xFB}, {0xFC}, {0xFD},
|
|
{0x8E, 0x40, 0, 0, 0}, {0x8F, 0x40, 0, 0, 0, 0, 0, 0, 0}, {'a', 0xFF}, {0xFF}
|
|
};
|
|
for (const auto& v : values)
|
|
{
|
|
SaxCountdown scp(0);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// the test catches the exceptions of invalid input
|
|
#if !defined(JSON_NOEXCEPTION)
|
|
TEST_CASE("BON8 strings from contiguous and stream input")
|
|
{
|
|
// contiguous input copies the valid UTF-8 of a string in bulk, a stream
|
|
// is read byte by byte; both must end strings and report errors alike
|
|
const std::string ascii(20, 'a');
|
|
const std::vector<bytes> inputs =
|
|
{
|
|
// the string ends at 0xFF, at a marker, and at an integer
|
|
concat(concat(bytes(ascii.begin(), ascii.end()), {0xC3, 0xA4}), {0xFF}),
|
|
concat(concat({0x82}, bytes(ascii.begin(), ascii.end())), {0x91}),
|
|
concat(concat({0x85}, bytes(ascii.begin(), ascii.end())), {0xFE}),
|
|
concat(concat({0x82}, bytes(ascii.begin(), ascii.end())), {0xC2, 0x05}),
|
|
concat(concat({0x87}, bytes(ascii.begin(), ascii.end())), {0xE2, 0x82, 0xAC, 0xF0, 0x05, 0x00, 0x00}),
|
|
// invalid UTF-8 and a premature end after a run of valid characters
|
|
concat(bytes(ascii.begin(), ascii.end()), {0xE0, 0x80, 0x80}),
|
|
concat(bytes(ascii.begin(), ascii.end()), {0xE2, 0x82, 0x2F}),
|
|
concat(bytes(ascii.begin(), ascii.end()), {0xE2, 0x82}),
|
|
bytes(ascii.begin(), ascii.end()),
|
|
// trailing bytes after a string that ends a container
|
|
concat(concat({0x81}, bytes(ascii.begin(), ascii.end())), {0x91}),
|
|
};
|
|
|
|
for (const auto& input : inputs)
|
|
{
|
|
CAPTURE(input)
|
|
std::string from_vector;
|
|
std::string from_stream;
|
|
try
|
|
{
|
|
from_vector = json::from_bon8(input).dump();
|
|
}
|
|
catch (const json::parse_error& e)
|
|
{
|
|
from_vector = e.what();
|
|
}
|
|
try
|
|
{
|
|
std::istringstream stream(str(input));
|
|
from_stream = json::from_bon8(stream).dump();
|
|
}
|
|
catch (const json::parse_error& e)
|
|
{
|
|
from_stream = e.what();
|
|
}
|
|
CHECK(from_vector == from_stream);
|
|
}
|
|
|
|
json _;
|
|
CHECK(json::from_bon8(inputs[0]) == ascii + "\xC3\xA4");
|
|
CHECK(json::from_bon8(inputs[3]) == json({ascii, 45}));
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(inputs[5]), "[json.exception.parse_error.112] parse error at byte 22: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(inputs[6]), "[json.exception.parse_error.112] parse error at byte 23: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x2F", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(inputs[7]), "[json.exception.parse_error.110] parse error at byte 23: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(inputs[9]), "[json.exception.parse_error.110] parse error at byte 22: syntax error while parsing BON8 value: expected end of input; last byte: 0x91", json::parse_error&);
|
|
}
|
|
#endif
|
|
|
|
// use this testcase outside [hide] to run it with Valgrind
|
|
TEST_CASE("BON8 nesting does not consume the call stack")
|
|
{
|
|
// Note that deeply nested values must not be compared, copied or dumped
|
|
// here: those operations are still recursive. Depth is measured by
|
|
// descending instead.
|
|
|
|
SECTION("an unterminated chain is reported, not crashed on")
|
|
{
|
|
json _;
|
|
const bytes input(300000, 0x81);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_bon8(input), "[json.exception.parse_error.110] parse error at byte 300001: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_bon8(input, true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("a well-formed deep value is read through the SAX interface")
|
|
{
|
|
bytes input(300000, 0x85);
|
|
input.push_back(0x91); // innermost value
|
|
input.insert(input.end(), 300000, 0xFE);
|
|
|
|
SaxCountdown accept_all(600001);
|
|
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::bon8));
|
|
}
|
|
|
|
SECTION("a well-formed deep value is read into a value")
|
|
{
|
|
const std::size_t depth = 10000;
|
|
bytes input;
|
|
for (std::size_t i = 0; i < depth; ++i)
|
|
{
|
|
input.push_back(0x87);
|
|
input.push_back('a');
|
|
}
|
|
// the innermost key is followed by a string, so it is terminated
|
|
input.push_back(0xFF);
|
|
input.push_back('b');
|
|
input.push_back(0xFF);
|
|
|
|
json j = json::from_bon8(input);
|
|
|
|
std::size_t measured = 0;
|
|
const json* p = &j;
|
|
while (p->is_object() && !p->empty())
|
|
{
|
|
p = &p->at("a");
|
|
++measured;
|
|
}
|
|
CHECK(measured == depth);
|
|
CHECK(*p == "b");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("single BON8 roundtrip")
|
|
{
|
|
SECTION("sample.json")
|
|
{
|
|
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
|
|
|
|
// parse JSON file
|
|
std::ifstream f_json(filename);
|
|
const json j1 = json::parse(f_json);
|
|
|
|
// parse BON8 file
|
|
auto packed = utils::read_binary_file(filename + ".bon8");
|
|
json j2;
|
|
CHECK_NOTHROW(j2 = json::from_bon8(packed));
|
|
|
|
// compare parsed JSON values
|
|
CHECK(j1 == j2);
|
|
|
|
SECTION("roundtrips")
|
|
{
|
|
SECTION("std::ostringstream")
|
|
{
|
|
std::basic_ostringstream<char> ss;
|
|
json::to_bon8(j1, ss);
|
|
json j3 = json::from_bon8(ss.str());
|
|
CHECK(j1 == j3);
|
|
}
|
|
|
|
SECTION("std::string")
|
|
{
|
|
std::string s;
|
|
json::to_bon8(j1, s);
|
|
json j3 = json::from_bon8(s);
|
|
CHECK(j1 == j3);
|
|
}
|
|
}
|
|
|
|
// check with different start index
|
|
packed.insert(packed.begin(), 5, 0xff);
|
|
CHECK(j1 == json::from_bon8(packed.begin() + 5, packed.end()));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Parse BON8 directly from a file using iterator and sentinel")
|
|
{
|
|
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.bon8";
|
|
std::ifstream file(filename, std::ios::binary);
|
|
const std::istreambuf_iterator<char> first(file);
|
|
const json parsed = json::from_bon8(first, utils::istreambuf_sentinel{});
|
|
CHECK((parsed.is_object() || parsed.is_array()));
|
|
}
|
|
|
|
#if !defined(JSON_NOEXCEPTION) // corpus values that do not survive the round trip are skipped by catching the exception
|
|
TEST_CASE("BON8 round-trip invariants")
|
|
{
|
|
// This checks what the parse_bon8_fuzzer driver checks (see
|
|
// tests/src/fuzzer-parse_bon8.cpp), so that a regression shows up in CI
|
|
// rather than as an OSS-Fuzz report: anything from_bon8() returns (j1)
|
|
// can be serialized, parsed back (j2), and serialized again to reproduce
|
|
// the exact bytes. The stream-versus-contiguous input check the driver
|
|
// also performs is not covered here (see #5601).
|
|
for (const auto& j0 : utils::round_trip_corpus::values())
|
|
{
|
|
json j1;
|
|
try
|
|
{
|
|
// turn the corpus value into a value as from_bon8() returns it
|
|
j1 = json::from_bon8(json::to_bon8(j0));
|
|
}
|
|
catch (const json::exception&)
|
|
{
|
|
// BON8 cannot represent an unsigned integer above INT64_MAX, and
|
|
// the fuzzer driver only ever sees values from_bon8() actually
|
|
// produced, so skip such corpus values here, too
|
|
continue;
|
|
}
|
|
|
|
INFO("j1 = " << j1.dump());
|
|
const std::vector<std::uint8_t> vec = json::to_bon8(j1);
|
|
json j2;
|
|
// anything the library writes must be parsable by the library
|
|
REQUIRE_NOTHROW(j2 = json::from_bon8(vec));
|
|
CHECK(json::to_bon8(j2) == vec);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TEST_CASE("BON8 roundtrips" * doctest::skip())
|
|
{
|
|
SECTION("input from HikoGUI")
|
|
{
|
|
// The .bon8 files were created with the BON8 encoder of HikoGUI
|
|
// (https://github.com/hikoworks/hikogui), the reference implementation
|
|
// of the format. Both implementations sort object keys, so the output
|
|
// is compared byte for byte.
|
|
for (std::string filename :
|
|
{
|
|
TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json",
|
|
TEST_DATA_DIRECTORY "/json.org/1.json",
|
|
TEST_DATA_DIRECTORY "/json.org/2.json",
|
|
TEST_DATA_DIRECTORY "/json.org/3.json",
|
|
TEST_DATA_DIRECTORY "/json.org/4.json",
|
|
TEST_DATA_DIRECTORY "/json.org/5.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json",
|
|
TEST_DATA_DIRECTORY "/json_testsuite/sample.json",
|
|
TEST_DATA_DIRECTORY "/json_tests/pass1.json",
|
|
TEST_DATA_DIRECTORY "/json_tests/pass2.json",
|
|
TEST_DATA_DIRECTORY "/json_tests/pass3.json",
|
|
TEST_DATA_DIRECTORY "/regression/floats.json",
|
|
TEST_DATA_DIRECTORY "/regression/signed_ints.json",
|
|
TEST_DATA_DIRECTORY "/regression/working_file.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty-string.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_false.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_null.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e+1.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e1.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_after_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_one.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_real.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_basic.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty_key.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_simple.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_comments.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_null_escape.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_pi.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_uEscape.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf8.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json"
|
|
})
|
|
{
|
|
CAPTURE(filename)
|
|
|
|
// parse JSON file
|
|
std::ifstream f_json(filename);
|
|
const json j1 = json::parse(f_json);
|
|
|
|
// parse BON8 file
|
|
const auto packed = utils::read_binary_file(filename + ".bon8");
|
|
|
|
{
|
|
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
|
json j2;
|
|
CHECK_NOTHROW(j2 = json::from_bon8(packed));
|
|
CHECK(j1 == j2);
|
|
}
|
|
|
|
{
|
|
INFO_WITH_TEMP(filename + ": std::ifstream");
|
|
std::ifstream f_bon8(filename + ".bon8", std::ios::binary);
|
|
json j2;
|
|
CHECK_NOTHROW(j2 = json::from_bon8(f_bon8));
|
|
CHECK(j1 == j2);
|
|
}
|
|
|
|
{
|
|
INFO_WITH_TEMP(filename + ": iterator pair");
|
|
json j2;
|
|
CHECK_NOTHROW(j2 = json::from_bon8(packed.begin(), packed.end()));
|
|
CHECK(j1 == j2);
|
|
}
|
|
|
|
{
|
|
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
|
|
std::vector<uint8_t> vec;
|
|
json::to_bon8(j1, vec);
|
|
CHECK(vec == packed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("issue #5648 - from_bon8(ptr, len) must read len bytes, not treat ptr as a C string")
|
|
{
|
|
// to_bon8() encodes the integer 40 as the two bytes 0xC2 0x00 (a BON8
|
|
// lead byte followed by a continuation byte of 0x00), so the packed data
|
|
// below contains a 0x00 byte before its end.
|
|
const json j = {{"a", 40}, {"b", "x"}};
|
|
const std::vector<std::uint8_t> packed = json::to_bon8(j);
|
|
bool contains_nul = false;
|
|
for (const auto byte : packed)
|
|
{
|
|
contains_nul |= (byte == 0x00);
|
|
}
|
|
REQUIRE(contains_nul);
|
|
|
|
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
|
// before the fix, from_bon8 had no (ptr, len) overload, so this call
|
|
// bound to from_bon8(InputType&&, bool strict) instead: ptr was read as
|
|
// a NUL-terminated C string (stopping at the embedded 0x00 byte), and
|
|
// len was silently converted to the strict flag. The deprecated
|
|
// overload added for this issue forwards to from_bon8(ptr, ptr + len,
|
|
// ...) instead, like from_cbor's deprecated (ptr, len) overload does.
|
|
json result;
|
|
CHECK_NOTHROW(result = json::from_bon8(packed.data(), packed.size()));
|
|
CHECK(result == j);
|
|
|
|
// len must not collapse into the strict flag either
|
|
CHECK(json::from_bon8(packed.data(), packed.size(), false) == j);
|
|
#endif
|
|
}
|
|
|
|
#ifdef JSON_HAS_CPP_17
|
|
TEST_CASE("BON8 with std::byte")
|
|
{
|
|
SECTION("vector roundtrip")
|
|
{
|
|
const json original =
|
|
{
|
|
{"name", "test"},
|
|
{"value", 42},
|
|
{"array", {1, 2, 3}}
|
|
};
|
|
|
|
const std::vector<uint8_t> temp = json::to_bon8(original);
|
|
std::vector<std::byte> bon8_data(temp.size());
|
|
for (size_t i = 0; i < temp.size(); ++i)
|
|
{
|
|
bon8_data[i] = std::byte(temp[i]);
|
|
}
|
|
|
|
json from_bytes;
|
|
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
|
CHECK(from_bytes == original);
|
|
}
|
|
|
|
SECTION("empty vector")
|
|
{
|
|
const std::vector<std::byte> empty_data;
|
|
CHECK_THROWS_WITH_AS([&]()
|
|
{
|
|
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
|
return true;
|
|
}
|
|
(),
|
|
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
|
json::parse_error&);
|
|
}
|
|
}
|
|
#endif
|