mirror of
https://github.com/nlohmann/json.git
synced 2026-10-14 18:27:13 +00:00
Fuzz the streaming input adapters against the contiguous one
The fast paths for contiguous input (bulk string scanning, multi-byte loads, lazy position tracking) are taken only when parsing from a pointer range; streams and non-contiguous iterators still read byte by byte. The fuzzer drivers parsed only contiguous input, so a difference between the two would ship unnoticed. check_input_adapters() in fuzzer_common.hpp reads the input through a std::istringstream, a std::list iterator range, and (on POSIX) a FILE* from fmemopen, and asserts the same value, or the same exception id, message and byte position, as for the pointer range. All seven drivers call it; the JSON driver checks parse and accept, each with and without ignore_comments. It replaces the stream check of the BON8 driver. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
9 files changed
+264
-22
No files matched your search
@@ -11,6 +11,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert> // assert
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // FILE, fmemopen, fclose
|
||||
#include <list> // list
|
||||
#include <sstream> // istringstream
|
||||
#include <string> // string
|
||||
#include <utility> // forward
|
||||
#include <vector> // vector
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using nlohmann::json; // NOLINT(google-global-names-in-headers): shared by all fuzzer drivers
|
||||
@@ -49,3 +57,89 @@ json parse_without_exceptions(Parse parse, bool& threw)
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// the outcome of reading an input with exceptions: the serialization of the
|
||||
// value, or the id, message, and (for parse errors) byte of the exception
|
||||
struct read_outcome
|
||||
{
|
||||
std::string value{};
|
||||
int id = 0;
|
||||
std::string what{};
|
||||
std::size_t byte = 0;
|
||||
|
||||
bool operator==(const read_outcome& rhs) const
|
||||
{
|
||||
return value == rhs.value && id == rhs.id && what == rhs.what && byte == rhs.byte;
|
||||
}
|
||||
};
|
||||
|
||||
inline std::string outcome_value(const json& j)
|
||||
{
|
||||
return j.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
|
||||
inline std::string outcome_value(bool b)
|
||||
{
|
||||
return b ? "true" : "false";
|
||||
}
|
||||
|
||||
template<typename Read, typename... Args>
|
||||
read_outcome read_with(const Read& read, Args&& ... args)
|
||||
{
|
||||
read_outcome result;
|
||||
try
|
||||
{
|
||||
result.value = outcome_value(read(std::forward<Args>(args)...));
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
result.id = e.id;
|
||||
result.what = e.what();
|
||||
result.byte = e.byte;
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
result.id = e.id;
|
||||
result.what = e.what();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// The contiguous input (a pointer range) takes the bulk fast paths; the other
|
||||
// input adapters read byte by byte. Reads @a data through each of them and
|
||||
// checks that all give the same value, or the same exception with the same
|
||||
// id, message, and byte position.
|
||||
//
|
||||
// @a read is called with either an iterator range or a single input (stream
|
||||
// or FILE*) and returns the parsed value (or the result of accept()).
|
||||
template<typename Read>
|
||||
void check_input_adapters(const std::uint8_t* data, std::size_t size, const Read& read)
|
||||
{
|
||||
const read_outcome expected = read_with(read, data, data + size);
|
||||
|
||||
// stream adapter
|
||||
{
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||
assert(read_with(read, stream) == expected);
|
||||
}
|
||||
|
||||
// iterator adapter on a non-contiguous range
|
||||
{
|
||||
const std::list<std::uint8_t> list(data, data + size);
|
||||
assert(read_with(read, list.begin(), list.end()) == expected);
|
||||
}
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
// FILE* adapter; fmemopen may refuse an empty buffer
|
||||
{
|
||||
std::vector<std::uint8_t> buffer(data, data + size);
|
||||
std::FILE* file = buffer.empty() ? nullptr : fmemopen(buffer.data(), buffer.size(), "rb");
|
||||
if (file != nullptr)
|
||||
{
|
||||
const read_outcome actual = read_with(read, file);
|
||||
std::fclose(file);
|
||||
assert(actual == expected);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user