mirror of
https://github.com/nlohmann/json.git
synced 2026-10-05 06:00:29 +00:00
output_vector_adapter, output_stream_adapter and output_string_adapter declared their write_characters(const CharType*, std::size_t) override JSON_HEDLEY_NON_NULL(2), but binary_writer legitimately calls it with a null pointer and length 0 for an empty string or binary value; the type-erased call path only stayed silent under UBSan because the static callee at those call sites is the unattributed virtual base. A nonnull attribute on a definition lets GCC and Clang assume the parameter is non-null inside the function body even when the call is virtual, so this was latent undefined behavior, not just style. Drop the attribute from the three overrides and document the (nullptr, 0) contract on output_adapter_protocol::write_characters. unit-cbor, unit-msgpack, unit-bson and unit-bon8 (which all exercise empty binary/string payloads through the stream and vector/string adapters) pass under -fsanitize=address,undefined,nonnull-attribute. Part of #5709 Signed-off-by: Niels Lohmann <mail@nlohmann.me>
219 lines
6.6 KiB
C++
219 lines
6.6 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <cstddef> // size_t
|
|
#include <memory> // shared_ptr, make_shared
|
|
#include <string> // basic_string
|
|
#include <utility> // move
|
|
#include <vector> // vector
|
|
|
|
#ifndef JSON_NO_IO
|
|
#include <ios> // streamsize
|
|
#include <ostream> // basic_ostream
|
|
#endif // JSON_NO_IO
|
|
|
|
#include <nlohmann/detail/macro_scope.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
|
|
/// abstract output adapter interface
|
|
template<typename CharType> struct output_adapter_protocol
|
|
{
|
|
virtual void write_character(CharType c) = 0;
|
|
/// @param[in] s pointer to the characters to write; binary_writer legitimately
|
|
/// passes a null pointer together with length 0 for an empty
|
|
/// string or binary value, so implementations must tolerate that
|
|
/// @param[in] length number of characters at @a s
|
|
virtual void write_characters(const CharType* s, std::size_t length) = 0;
|
|
virtual ~output_adapter_protocol() = default;
|
|
|
|
output_adapter_protocol() = default;
|
|
output_adapter_protocol(const output_adapter_protocol&) = default;
|
|
output_adapter_protocol(output_adapter_protocol&&) noexcept = default;
|
|
output_adapter_protocol& operator=(const output_adapter_protocol&) = default;
|
|
output_adapter_protocol& operator=(output_adapter_protocol&&) noexcept = default;
|
|
};
|
|
|
|
/// a type to simplify interfaces
|
|
template<typename CharType>
|
|
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
|
|
|
/// @brief non-virtual output sink writing into a std::vector
|
|
///
|
|
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
|
/// passed to binary_writer by value as a template parameter, so
|
|
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
|
/// vtable lookup and no shared_ptr. It is used for the common
|
|
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below
|
|
/// wraps this same sink to provide the virtual interface.
|
|
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
|
class output_vector_sink
|
|
{
|
|
public:
|
|
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
|
: v(vec)
|
|
{}
|
|
|
|
void write_character(CharType c)
|
|
{
|
|
v.push_back(c);
|
|
}
|
|
|
|
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
|
// pointer with length 0 for empty strings/binary values. Appending an empty
|
|
// range is a no-op; the type-erased path tolerates this via the (unattributed)
|
|
// virtual base, and the concrete sink must do the same.
|
|
void write_characters(const CharType* s, std::size_t length)
|
|
{
|
|
v.insert(v.end(), s, s + length);
|
|
}
|
|
|
|
private:
|
|
std::vector<CharType, AllocatorType>& v;
|
|
};
|
|
|
|
/// output adapter for byte vectors
|
|
///
|
|
/// The appending itself lives in output_vector_sink; this class only adds the
|
|
/// virtual output_adapter_protocol interface on top of it, so both the
|
|
/// type-erased and the templated path share one implementation.
|
|
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
|
class output_vector_adapter : public output_adapter_protocol<CharType>
|
|
{
|
|
public:
|
|
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
|
: sink(vec)
|
|
{}
|
|
|
|
void write_character(CharType c) override
|
|
{
|
|
sink.write_character(c);
|
|
}
|
|
|
|
void write_characters(const CharType* s, std::size_t length) override
|
|
{
|
|
sink.write_characters(s, length);
|
|
}
|
|
|
|
private:
|
|
output_vector_sink<CharType, AllocatorType> sink;
|
|
};
|
|
|
|
#ifndef JSON_NO_IO
|
|
/// output adapter for output streams
|
|
template<typename CharType>
|
|
class output_stream_adapter : public output_adapter_protocol<CharType>
|
|
{
|
|
public:
|
|
explicit output_stream_adapter(std::basic_ostream<CharType>& s) noexcept
|
|
: stream(s)
|
|
{}
|
|
|
|
void write_character(CharType c) override
|
|
{
|
|
stream.put(c);
|
|
}
|
|
|
|
void write_characters(const CharType* s, std::size_t length) override
|
|
{
|
|
stream.write(s, static_cast<std::streamsize>(length));
|
|
}
|
|
|
|
private:
|
|
std::basic_ostream<CharType>& stream;
|
|
};
|
|
#endif // JSON_NO_IO
|
|
|
|
/// output adapter for basic_string
|
|
template<typename CharType, typename StringType = std::basic_string<CharType>>
|
|
class output_string_adapter : public output_adapter_protocol<CharType>
|
|
{
|
|
public:
|
|
explicit output_string_adapter(StringType& s) noexcept
|
|
: str(s)
|
|
{}
|
|
|
|
void write_character(CharType c) override
|
|
{
|
|
str.push_back(c);
|
|
}
|
|
|
|
void write_characters(const CharType* s, std::size_t length) override
|
|
{
|
|
str.append(s, length);
|
|
}
|
|
|
|
private:
|
|
StringType& str;
|
|
};
|
|
|
|
/// @brief output sink forwarding to a type-erased output adapter
|
|
///
|
|
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
|
/// also target arbitrary adapters (output streams, strings, user-provided
|
|
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
|
/// through one virtual call, exactly as before; only the concrete sinks above
|
|
/// avoid it.
|
|
template<typename CharType>
|
|
class output_adapter_sink
|
|
{
|
|
public:
|
|
explicit output_adapter_sink(output_adapter_t<CharType> adapter)
|
|
: oa(std::move(adapter))
|
|
{
|
|
JSON_ASSERT(oa);
|
|
}
|
|
|
|
void write_character(CharType c)
|
|
{
|
|
oa->write_character(c);
|
|
}
|
|
|
|
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
|
// the type-erased path already did before this sink existed
|
|
void write_characters(const CharType* s, std::size_t length)
|
|
{
|
|
oa->write_characters(s, length);
|
|
}
|
|
|
|
private:
|
|
output_adapter_t<CharType> oa;
|
|
};
|
|
|
|
template<typename CharType, typename StringType = std::basic_string<CharType>>
|
|
class output_adapter
|
|
{
|
|
public:
|
|
template<typename AllocatorType = std::allocator<CharType>>
|
|
output_adapter(std::vector<CharType, AllocatorType>& vec)
|
|
: oa(std::make_shared<output_vector_adapter<CharType, AllocatorType>>(vec)) {}
|
|
|
|
#ifndef JSON_NO_IO
|
|
output_adapter(std::basic_ostream<CharType>& s)
|
|
: oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}
|
|
#endif // JSON_NO_IO
|
|
|
|
output_adapter(StringType& s)
|
|
: oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}
|
|
|
|
operator output_adapter_t<CharType>()
|
|
{
|
|
return oa;
|
|
}
|
|
|
|
private:
|
|
output_adapter_t<CharType> oa = nullptr;
|
|
};
|
|
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|