From c8bac41340c752069cbe6788111610e2e385a5ce Mon Sep 17 00:00:00 2001 From: Marcin Kowalczyk Date: Wed, 6 Nov 2024 16:13:44 +0100 Subject: [PATCH] Replace `assert_internal::GetStreamable()` with public `riegeli::Debug()`, extensible using `RiegeliDebug()` FTADLE. Use it for printing of values under an assertion and to replace quoted `absl::CHexEscape()`. Extract some operations on `std::ostream` to `stream_utils.h`. PiperOrigin-RevId: 693716729 --- riegeli/base/BUILD | 27 ++ riegeli/base/assert.h | 23 +- riegeli/base/chain.cc | 29 +- riegeli/base/chain_base.h | 8 + riegeli/base/chain_details.h | 10 + riegeli/base/debug.cc | 260 ++++++++++++ riegeli/base/debug.h | 377 ++++++++++++++++++ riegeli/base/intrusive_shared_ptr.h | 6 + riegeli/base/shared_ptr.h | 6 + riegeli/base/stream_utils.cc | 35 ++ riegeli/base/stream_utils.h | 142 +++++++ riegeli/containers/BUILD | 2 + .../containers/chunked_sorted_string_set.cc | 8 +- .../containers/linear_sorted_string_set.cc | 20 +- riegeli/csv/BUILD | 2 + riegeli/csv/csv_reader.cc | 30 +- riegeli/csv/csv_writer.cc | 25 +- 17 files changed, 913 insertions(+), 97 deletions(-) create mode 100644 riegeli/base/debug.cc create mode 100644 riegeli/base/debug.h create mode 100644 riegeli/base/stream_utils.cc create mode 100644 riegeli/base/stream_utils.h diff --git a/riegeli/base/BUILD b/riegeli/base/BUILD index 6ab5929d..81a72d99 100644 --- a/riegeli/base/BUILD +++ b/riegeli/base/BUILD @@ -41,6 +41,31 @@ cc_library( ], ) +cc_library( + name = "stream_utils", + srcs = ["stream_utils.cc"], + hdrs = ["stream_utils.h"], + deps = [ + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "debug", + srcs = ["debug.cc"], + hdrs = ["debug.h"], + deps = [ + ":stream_utils", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:cord", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], +) + cc_library( name = "assert", srcs = [ @@ -49,6 +74,7 @@ cc_library( ], hdrs = ["assert.h"], deps = [ + ":debug", "@com_google_absl//absl/base:core_headers", ], ) @@ -466,6 +492,7 @@ cc_library( ":memory_estimator", ":new_aligned", ":shared_ptr", + ":stream_utils", ":string_utils", ":to_string_view", "@com_google_absl//absl/base:core_headers", diff --git a/riegeli/base/assert.h b/riegeli/base/assert.h index a6f662dd..1a921cb4 100644 --- a/riegeli/base/assert.h +++ b/riegeli/base/assert.h @@ -16,7 +16,6 @@ #define RIEGELI_BASE_ASSERT_H_ #include -#include #include // IWYU pragma: export #include @@ -25,6 +24,7 @@ #include "absl/base/attributes.h" #include "absl/base/optimization.h" +#include "riegeli/base/debug.h" #include "riegeli/base/port.h" namespace riegeli { @@ -95,30 +95,11 @@ class CheckResult { const char* message_ = nullptr; }; -// For showing a value in a failure message involving a comparison, if needed -// then converts the value to a different type with the appropriate behavior of -// `operator<<`: characters are shown as integers. - -inline int GetStreamable(char value) { return value; } -inline int GetStreamable(signed char value) { return value; } -inline unsigned GetStreamable(unsigned char value) { return value; } -inline int GetStreamable(wchar_t value) { return value; } -#if __cpp_char8_t -inline unsigned GetStreamable(char8_t value) { return value; } -#endif -inline uint16_t GetStreamable(char16_t value) { return value; } -inline uint32_t GetStreamable(char32_t value) { return value; } - -template -inline const T& GetStreamable(const T& value) { - return value; -} - template ABSL_ATTRIBUTE_COLD const char* FormatCheckOpMessage(const char* message, const A& a, const B& b) { std::ostringstream stream; - stream << message << " (" << GetStreamable(a) << " vs. " << GetStreamable(b) + stream << message << " (" << riegeli::Debug(a) << " vs. " << riegeli::Debug(b) << ")"; // Do not bother with freeing this string: the program will soon terminate. return (new std::string(stream.str()))->c_str(); diff --git a/riegeli/base/chain.cc b/riegeli/base/chain.cc index 633d96c7..68915d6b 100644 --- a/riegeli/base/chain.cc +++ b/riegeli/base/chain.cc @@ -44,6 +44,7 @@ #include "riegeli/base/memory_estimator.h" #include "riegeli/base/new_aligned.h" #include "riegeli/base/ownership.h" +#include "riegeli/base/stream_utils.h" #include "riegeli/base/string_utils.h" namespace riegeli { @@ -62,16 +63,6 @@ constexpr Chain::BlockPtrPtr Chain::BlockIterator::kEndShortData; namespace { -void WritePadding(std::ostream& dest, size_t length) { - char buffer[64]; - std::memset(buffer, dest.fill(), sizeof(buffer)); - while (length > sizeof(buffer)) { - dest.write(buffer, std::streamsize{sizeof(buffer)}); - length -= sizeof(buffer); - } - dest.write(buffer, IntCast(length)); -} - // Stores an `absl::Cord` which must be flat, i.e. // `src.TryFlat() != absl::nullopt`. // @@ -2112,25 +2103,11 @@ StrongOrdering Chain::Compare(const Chain& a, absl::string_view b) { } void Chain::Output(std::ostream& dest) const { - std::ostream::sentry sentry(dest); - if (sentry) { - size_t left_pad = 0; - size_t right_pad = 0; - if (IntCast(dest.width()) > size()) { - const size_t pad = IntCast(dest.width()) - size(); - if ((dest.flags() & dest.adjustfield) == dest.left) { - right_pad = pad; - } else { - left_pad = pad; - } - } - if (left_pad > 0) WritePadding(dest, left_pad); + WriteWithPadding(dest, size(), [&] { for (const absl::string_view fragment : blocks()) { dest.write(fragment.data(), IntCast(fragment.size())); } - if (right_pad > 0) WritePadding(dest, right_pad); - dest.width(0); - } + }); } void Chain::VerifyInvariants() const { diff --git a/riegeli/base/chain_base.h b/riegeli/base/chain_base.h index 5a63ce31..4504af4b 100644 --- a/riegeli/base/chain_base.h +++ b/riegeli/base/chain_base.h @@ -418,6 +418,12 @@ class Chain : public WithCompare { return dest; } + // Support `riegeli::Debug()`. + template + friend void RiegeliDebug(const Chain& src, DebugStream& dest) { + src.Debug(dest); + } + // Support `absl::Format(&chain, format, args...)`. friend void AbslFormatFlush(Chain* dest, absl::string_view src) { dest->Append(src); @@ -594,6 +600,8 @@ class Chain : public WithCompare { template void Stringify(Sink& dest) const; void Output(std::ostream& dest) const; + template + void Debug(DebugStream& dest) const; BlockPtrs block_ptrs_; diff --git a/riegeli/base/chain_details.h b/riegeli/base/chain_details.h index f993d64a..a915abc7 100644 --- a/riegeli/base/chain_details.h +++ b/riegeli/base/chain_details.h @@ -1256,6 +1256,16 @@ void Chain::Stringify(Sink& dest) const { for (const absl::string_view block : blocks()) dest.Append(block); } +template +void Chain::Debug(DebugStream& dest) const { + dest.DebugStringQuote(); + typename DebugStream::EscapeState escape_state; + for (const absl::string_view fragment : blocks()) { + dest.DebugStringFragment(fragment, escape_state); + } + dest.DebugStringQuote(); +} + } // namespace riegeli #endif // RIEGELI_BASE_CHAIN_DETAILS_H_ diff --git a/riegeli/base/debug.cc b/riegeli/base/debug.cc new file mode 100644 index 00000000..dff7b667 --- /dev/null +++ b/riegeli/base/debug.cc @@ -0,0 +1,260 @@ +// Copyright 2024 Google LLC +// +// Licensed 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 "riegeli/base/debug.h" + +#include + +#include + +#include "absl/base/attributes.h" +#include "absl/meta/type_traits.h" +#include "absl/strings/cord.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/span.h" + +namespace riegeli { + +namespace { + +inline void WriteHex1(uint8_t src, DebugStream& dest) { + dest.Write(static_cast(src + (src < 10 ? '0' : 'a' - 10))); +} + +inline void WriteHex2(uint8_t src, DebugStream& dest) { + WriteHex1(static_cast(src >> 4), dest); + WriteHex1(static_cast(src & 0x0f), dest); +} + +inline void WriteHex4(uint16_t src, DebugStream& dest) { + WriteHex2(static_cast(src >> 8), dest); + WriteHex2(static_cast(src & 0xff), dest); +} + +inline void WriteHex8(uint32_t src, DebugStream& dest) { + WriteHex4(static_cast(src >> 16), dest); + WriteHex4(static_cast(src & 0xffff), dest); +} + +template +debug_internal::LastEscape WriteChar(CharType src, DebugStream& dest) { + if (src >= 32 && src <= 126) { + if (src == quote || src == '\\') dest.Write('\\'); + dest.Write(static_cast(src)); + return debug_internal::LastEscape::kNormal; + } + switch (src) { + case '\0': + dest.Write("\\0"); + return debug_internal::LastEscape::kZero; + case '\t': + dest.Write("\\t"); + return debug_internal::LastEscape::kNormal; + case '\n': + dest.Write("\\n"); + return debug_internal::LastEscape::kNormal; + case '\r': + dest.Write("\\r"); + return debug_internal::LastEscape::kNormal; + default: { + const auto unsigned_src = static_cast(src); + if (unsigned_src <= 0xff) { + dest.Write("\\x"); + WriteHex2(static_cast(unsigned_src), dest); + return debug_internal::LastEscape::kHex; + } + if (unsigned_src <= 0xffff) { + dest.Write("\\u"); + WriteHex4(static_cast(unsigned_src), dest); + return debug_internal::LastEscape::kNormal; + } + dest.Write("\\U"); + WriteHex8(unsigned_src, dest); + return debug_internal::LastEscape::kNormal; + } + } +} + +template +debug_internal::LastEscape WriteStringFragment( + absl::Span src, DebugStream& dest, + debug_internal::LastEscape last_escape = + debug_internal::LastEscape::kNormal) { + for (const CharType ch : src) { + switch (last_escape) { + case debug_internal::LastEscape::kNormal: + break; + case debug_internal::LastEscape::kZero: + if (ch >= '0' && ch <= '7') { + // Ensure that the next character is not interpreted as a part of the + // previous octal escape sequence. + dest.Write("00"); + } + break; + case debug_internal::LastEscape::kHex: + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F')) { + // Ensure that the next character is not interpreted as a part of the + // previous hex escape sequence. + dest.Write("\" \""); + } + break; + } + last_escape = WriteChar<'"', IntType>(ch, dest); + } + return last_escape; +} + +} // namespace + +void DebugStream::DebugStringFragment(absl::string_view src, + EscapeState& escape_state) { + escape_state.last_escape_ = WriteStringFragment( + absl::MakeConstSpan(src), *this, escape_state.last_escape_); +} + +void RiegeliDebug(bool src, DebugStream& dest) { + dest.Write(src ? absl::string_view("true") : absl::string_view("false")); +} + +void RiegeliDebug(char src, DebugStream& dest) { + dest.Write('\''); + WriteChar<'\'', uint8_t>(src, dest); + dest.Write('\''); +} + +void RiegeliDebug(wchar_t src, DebugStream& dest) { + dest.Write('\''); + WriteChar<'\'', + absl::conditional_t>( + src, dest); + dest.Write('\''); +} + +#if __cpp_char8_t +void RiegeliDebug(char8_t src, DebugStream& dest) { + dest.Write('\''); + WriteChar<'\'', uint8_t>(src, dest); + dest.Write('\''); +} +#endif // __cpp_char8_t + +void RiegeliDebug(char16_t src, DebugStream& dest) { + dest.Write('\''); + WriteChar<'\'', uint16_t>(src, dest); + dest.Write('\''); +} + +void RiegeliDebug(char32_t src, DebugStream& dest) { + dest.Write('\''); + WriteChar<'\'', uint32_t>(src, dest); + dest.Write('\''); +} + +void RiegeliDebug(absl::string_view src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment(absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} + +#if __cpp_lib_string_view + +void RiegeliDebug(std::wstring_view src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment< + absl::conditional_t>( + absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} + +#if __cpp_char8_t +void RiegeliDebug(std::u8string_view src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment(absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} +#endif // __cpp_char8_t + +void RiegeliDebug(std::u16string_view src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment(absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} + +void RiegeliDebug(std::u32string_view src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment(absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} + +#else // !__cpp_lib_string_view + +void RiegeliDebug(const std::wstring& src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment< + absl::conditional_t>( + absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} + +#if __cpp_char8_t +void RiegeliDebug(const std::u8string& src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment(absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} +#endif // __cpp_char8_t + +void RiegeliDebug(const std::u16string& src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment(absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} + +void RiegeliDebug(const std::u32string& src, DebugStream& dest) { + dest.DebugStringQuote(); + WriteStringFragment(absl::MakeConstSpan(src), dest); + dest.DebugStringQuote(); +} + +#endif // !__cpp_lib_string_view + +void RiegeliDebug(const absl::Cord& src, DebugStream& dest) { + dest.DebugStringQuote(); + DebugStream::EscapeState escape_state; + for (const absl::string_view fragment : src.Chunks()) { + dest.DebugStringFragment(fragment, escape_state); + } + dest.DebugStringQuote(); +} + +void RiegeliDebug(const void* src, DebugStream& dest) { + if (src == nullptr) { + dest.Write("nullptr"); + } else { + dest << src; + } +} + +void RiegeliDebug(ABSL_ATTRIBUTE_UNUSED std::nullptr_t src, DebugStream& dest) { + dest.Write("nullptr"); +} + +void RiegeliDebug(ABSL_ATTRIBUTE_UNUSED absl::nullopt_t src, + DebugStream& dest) { + dest.Write("nullopt"); +} + +} // namespace riegeli diff --git a/riegeli/base/debug.h b/riegeli/base/debug.h new file mode 100644 index 00000000..92f71ea5 --- /dev/null +++ b/riegeli/base/debug.h @@ -0,0 +1,377 @@ +// Copyright 2024 Google LLC +// +// Licensed 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. + +#ifndef RIEGELI_BASE_DEBUG_H_ +#define RIEGELI_BASE_DEBUG_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/attributes.h" +#include "absl/meta/type_traits.h" +#include "absl/strings/cord.h" +#include "absl/strings/has_absl_stringify.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "riegeli/base/stream_utils.h" + +namespace riegeli { + +class DebugStream; + +namespace debug_internal { + +template +struct HasRiegeliDebug : std::false_type {}; + +template +struct HasRiegeliDebug< + T, absl::void_t(), + std::declval()))>> + : std::true_type {}; + +template +struct HasDebugString : std::false_type {}; + +template +struct HasDebugString().DebugString()), + absl::string_view>::value>> : std::true_type {}; + +template +struct HasOperatorOutput : std::false_type {}; + +template +struct HasOperatorOutput() + << std::declval())>> + : std::true_type {}; + +} // namespace debug_internal + +// `SupportsDebug::value` is `true` if `T` supports `riegeli::Debug()`: +// writing the value in a format suitable for error messages. +// +// The value is generally written in a way which reflects as much as is compared +// by `operator==`, without indicating the type nor internal structure, using +// syntax similar to C++ expressions. +template +struct SupportsDebug + : absl::disjunction< + debug_internal::HasRiegeliDebug, debug_internal::HasDebugString, + absl::HasAbslStringify, debug_internal::HasOperatorOutput> {}; + +// To customize `riegeli::Debug()` for a class `T`, define a free function +// `friend void RiegeliDebug(const T& src, DebugStream& dest)` as a friend of +// `T` inside class definition or in the same namespace as `T`, so that it can +// be found via ADL. `DebugStream` in the parameter type can also be a template +// parameter to reduce library dependencies. +// +// `riegeli::Debug(src)` uses the first defined form among the following: +// * `RiegeliDebug(src, dest)` +// * `src.DebugString()` +// * `dest << src` +// * `AbslStringify(dest, src)` +class DebugStream { + public: + // A default-constructible type used to maintain the state between calls to + // `DebugStringFragment()`. + class EscapeState; + + // Will write to `dest`. + explicit DebugStream(std::ostream* dest ABSL_ATTRIBUTE_LIFETIME_BOUND) + : dest_(dest) {} + + DebugStream(const DebugStream& that) = default; + DebugStream& operator=(const DebugStream& that) = default; + + // Writes a character using `std::ostream::write()`. + void Write(char src) { dest_->write(&src, 1); } + + // Writes a string using `std::ostream::write()`. + void Write(absl::string_view src) { + dest_->write(src.data(), static_cast(src.size())); + } + + // Writes a value formatted using `operator<<`. + // + // Using stream manipulators is supported, but if the stream state is not + // reset to the default before calling `Debug()`, then the results can be + // inconsistent, depending on the type being written. + template + DebugStream& operator<<(T&& src) { + *dest_ << std::forward(src); + return *this; + } + + // Writes a value in a format suitable for error messages. This calls the + // first defined form among the following: + // * `RiegeliDebug(src, *this)` + // * `Write(src.DebugString())` + // * `AbslStringify(sink, src)` for `OStreamAbslStringifySink(dest)` + // * `*dest << src` + // + // This is used to implement `riegeli::Debug()`, and to write subobjects by + // implementations of `RiegeliDebug()` for objects containing them. + template ::value, int> = 0> + void Debug(const T& src) { + RiegeliDebug(src, *this); + } + template < + typename T, + std::enable_if_t< + absl::conjunction>, + debug_internal::HasDebugString>::value, + int> = 0> + void Debug(const T& src) { + Write(src.DebugString()); + } + template < + typename T, + std::enable_if_t< + absl::conjunction>, + absl::negation>, + debug_internal::HasOperatorOutput>::value, + int> = 0> + void Debug(const T& src) { + *dest_ << src; + } + template < + typename T, + std::enable_if_t>, + absl::negation>, + absl::negation>, + absl::HasAbslStringify>::value, + int> = 0> + void Debug(const T& src) { + OStreamAbslStringifySink sink(dest_); + AbslStringify(sink, src); + } + + // To implement `RiegeliDebug()` for string-like types which are not + // represented as one fragment, the following pattern can be used: + // + // ``` + // dest.DebugStringQuote(); + // EscapeState escape_state; + // for (const absl::string_view fragment : fragments) { + // dest.DebugStringFragment(fragment, escape_state); + // } + // dest.DebugStringQuote(); + // ``` + // + // If the representation is always flat, relying on `Debug()` for + // `absl::string_view` is sufficient. + void DebugStringQuote() { Write('"'); } + void DebugStringFragment(absl::string_view src, EscapeState& escape_state); + + private: + std::ostream* dest_; +}; + +// The following overloads cover supported types which do not define +// `RiegeliDebug()` themselves. + +// `bool` is written as `true` or `false`. +void RiegeliDebug(bool src, DebugStream& dest); + +// Non-bool and non-character numeric types, including `signed char` and +// `unsigned char`, are written as numbers. +inline void RiegeliDebug(signed char src, DebugStream& dest) { + dest << int{src}; +} +inline void RiegeliDebug(unsigned char src, DebugStream& dest) { + dest << unsigned{src}; +} +inline void RiegeliDebug(short src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(unsigned short src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(int src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(unsigned src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(long src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(unsigned long src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(long long src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(unsigned long long src, DebugStream& dest) { + dest << src; +} +inline void RiegeliDebug(float src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(double src, DebugStream& dest) { dest << src; } +inline void RiegeliDebug(long double src, DebugStream& dest) { dest << src; } + +// Character types are written in C++ character literal format. +void RiegeliDebug(char src, DebugStream& dest); +void RiegeliDebug(wchar_t src, DebugStream& dest); +#if __cpp_char8_t +void RiegeliDebug(char8_t src, DebugStream& dest); +#endif +void RiegeliDebug(char16_t src, DebugStream& dest); +void RiegeliDebug(char32_t src, DebugStream& dest); + +// Enumeration types are written like their underlying types. +template ::value, int> = 0> +void RiegeliDebug(T src, DebugStream& dest) { + dest.Debug(static_cast>(src)); +} + +// `absl::string_view` is written in C++ string literal format. +// +// This covers types implicitly convertible to `absl::string_view` like +// `std::string` and `CompactString`. +void RiegeliDebug(absl::string_view src, DebugStream& dest); + +#if __cpp_lib_string_view +void RiegeliDebug(std::wstring_view src, DebugStream& dest); +#if __cpp_char8_t +void RiegeliDebug(std::u8string_view src, DebugStream& dest); +#endif +void RiegeliDebug(std::u16string_view src, DebugStream& dest); +void RiegeliDebug(std::u32string_view src, DebugStream& dest); +#else // !__cpp_lib_string_view +void RiegeliDebug(const std::wstring& src, DebugStream& dest); +#if __cpp_char8_t +void RiegeliDebug(const std::u8string& src, DebugStream& dest); +#endif +void RiegeliDebug(const std::u16string& src, DebugStream& dest); +void RiegeliDebug(const std::u32string& src, DebugStream& dest); +#endif // !__cpp_lib_string_view + +// `absl::Cord` is written in C++ string literal format. +void RiegeliDebug(const absl::Cord& src, DebugStream& dest); + +// A null pointer is written as "nullptr". Other data pointers, including char +// pointers, as well as function pointers, are written using `operator<<` for +// `const void*`. +void RiegeliDebug(std::nullptr_t src, DebugStream& dest); +void RiegeliDebug(const void* src, DebugStream& dest); +template ::value, int> = 0> +void RiegeliDebug(T* src, DebugStream& dest) { + dest.Debug(reinterpret_cast(src)); +} + +// `std::unique_ptr` and `std::shared_ptr` are written like pointers. +template +void RiegeliDebug(const std::unique_ptr& src, DebugStream& dest) { + dest.Debug(src.get()); +} +template +void RiegeliDebug(const std::shared_ptr& src, DebugStream& dest) { + dest.Debug(src.get()); +} + +// `absl::optional` values are written as "nullopt" when absent, or as the +// underlying data wrapped in braces when present. +void RiegeliDebug(absl::nullopt_t src, DebugStream& dest); +template ::value, int> = 0> +void RiegeliDebug(const absl::optional& src, DebugStream& dest) { + if (src == absl::nullopt) { + dest.Debug(absl::nullopt); + } else { + dest.Write('{'); + dest.Debug(*src); + dest.Write('}'); + } +} + +// The type returned by `riegeli::Debug()`. +template +class DebugType { + public: + template < + typename DependentT = T, + std::enable_if_t::value, int> = 0> + explicit DebugType(const T& src) : src_(src) {} + template < + typename DependentT = T, + std::enable_if_t::value, int> = 0> + explicit DebugType(T&& src) : src_(std::forward(src)) {} + + DebugType(const DebugType& that) = default; + DebugType& operator=(const DebugType& that) = default; + + DebugType(DebugType&& that) = default; + DebugType& operator=(DebugType&& that) = default; + + explicit operator std::string() const { + return (std::ostringstream() << *this).str(); + } + + template + friend void AbslStringify(Sink& dest, const DebugType& src) { + AbslStringifyOStream(&dest) << src; + } + + friend std::ostream& operator<<(std::ostream& dest, const DebugType& src) { + DebugStream(&dest).Debug(src.src_); + return dest; + } + + private: + T src_; +}; + +// Support CTAD. +#if __cpp_deduction_guides +template +explicit DebugType(T&& src) -> DebugType>; +#endif + +// `riegeli::Debug()` wraps an object such that it is formatted using +// `DebugStream::Debug()` when explicitly converted to `std::string` or written +// using `AbslStringify()` or `operator<<`. +// +// `riegeli::Debug()` does not own the object, even if it involves temporaries, +// hence it should be stringified by the same expression which constructed it, +// so that the temporaries outlive its usage. For storing a `DebugType` in a +// variable or returning it from a function, construct `DebugType` directly. +template ::value, int> = 0> +inline DebugType Debug(const T& src ABSL_ATTRIBUTE_LIFETIME_BOUND) { + return DebugType(src); +} + +// Implementation details follow. + +namespace debug_internal { + +enum class LastEscape { + kNormal, // The following conditions do not apply. + kZero, // The last character was `\0`. + kHex, // The last character was written with `\x`. +}; + +} // namespace debug_internal + +class DebugStream::EscapeState { + public: + EscapeState() = default; + + EscapeState(const EscapeState& that) = default; + EscapeState& operator=(const EscapeState& that) = default; + + private: + friend class DebugStream; + + debug_internal::LastEscape last_escape_ = debug_internal::LastEscape::kNormal; +}; + +} // namespace riegeli + +#endif // RIEGELI_BASE_DEBUG_H_ diff --git a/riegeli/base/intrusive_shared_ptr.h b/riegeli/base/intrusive_shared_ptr.h index 64799ea6..cfca4ec6 100644 --- a/riegeli/base/intrusive_shared_ptr.h +++ b/riegeli/base/intrusive_shared_ptr.h @@ -309,6 +309,12 @@ class [](void* ptr) { Unref(static_cast(ptr)); }); } + // Support `riegeli::Debug()`. + template + friend void RiegeliDebug(const IntrusiveSharedPtr& src, DebugStream& dest) { + dest.Debug(src.get()); + } + // Support `MemoryEstimator`. template friend void RiegeliRegisterSubobjects(const IntrusiveSharedPtr* self, diff --git a/riegeli/base/shared_ptr.h b/riegeli/base/shared_ptr.h index 1117c03c..6f50c5f5 100644 --- a/riegeli/base/shared_ptr.h +++ b/riegeli/base/shared_ptr.h @@ -243,6 +243,12 @@ class [](void* ptr) { SharedPtr::DeleteReleased(static_cast(ptr)); }); } + // Support `riegeli::Debug()`. + template + friend void RiegeliDebug(const SharedPtr& src, DebugStream& dest) { + dest.Debug(src.get()); + } + // Support `MemoryEstimator`. template friend void RiegeliRegisterSubobjects(const SharedPtr* self, diff --git a/riegeli/base/stream_utils.cc b/riegeli/base/stream_utils.cc new file mode 100644 index 00000000..156a5185 --- /dev/null +++ b/riegeli/base/stream_utils.cc @@ -0,0 +1,35 @@ +// Copyright 2017 Google LLC +// +// Licensed 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 "riegeli/base/stream_utils.h" + +#include + +#include +#include +#include + +namespace riegeli { + +void WritePadding(std::ostream& dest, size_t length, char fill) { + char buffer[64]; + std::memset(buffer, fill, sizeof(buffer)); + while (length > sizeof(buffer)) { + dest.write(buffer, std::streamsize{sizeof(buffer)}); + length -= sizeof(buffer); + } + dest.write(buffer, static_cast(length)); +} + +} // namespace riegeli diff --git a/riegeli/base/stream_utils.h b/riegeli/base/stream_utils.h new file mode 100644 index 00000000..2b7221c3 --- /dev/null +++ b/riegeli/base/stream_utils.h @@ -0,0 +1,142 @@ +// Copyright 2017 Google LLC +// +// Licensed 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. + +#ifndef RIEGELI_BASE_STREAM_UTILS_H_ +#define RIEGELI_BASE_STREAM_UTILS_H_ + +#include + +#include +#include +#include +#include +#include + +#include "absl/base/attributes.h" +#include "absl/strings/string_view.h" + +namespace riegeli { + +// Writes `length` copies of `fill` to `dest`. +void WritePadding(std::ostream& dest, size_t length, char fill); + +// Writes a value to `dest`, including padding configured in `dest`. +// Resets `dest.width()` to 0 afterwards. +// +// `length` is the number of characters in the value. `callback()` is called +// to write the value; it should use unformatted output, i.e. `dest.write()`. +template +void WriteWithPadding(std::ostream& dest, size_t length, Callback&& callback) { + std::ostream::sentry sentry(dest); + if (sentry) { + size_t left_pad = 0; + size_t right_pad = 0; + if (dest.width() > 0 && static_cast(dest.width()) > length) { + const size_t pad = static_cast(dest.width()) - length; + if ((dest.flags() & dest.adjustfield) == dest.left) { + right_pad = pad; + } else { + left_pad = pad; + } + } + if (left_pad > 0) WritePadding(dest, left_pad, dest.fill()); + std::forward(callback)(); + if (right_pad > 0) WritePadding(dest, right_pad, dest.fill()); + dest.width(0); + } +} + +// Adapts `std::ostream` to a sink for `AbslStringify()`. +class OStreamAbslStringifySink { + public: + explicit OStreamAbslStringifySink( + std::ostream* dest ABSL_ATTRIBUTE_LIFETIME_BOUND) + : dest_(dest) {} + + OStreamAbslStringifySink(const OStreamAbslStringifySink& that) = default; + OStreamAbslStringifySink& operator=(const OStreamAbslStringifySink& that) = + default; + + void Append(size_t length, char fill) { WritePadding(*dest_, length, fill); } + void Append(absl::string_view src) { + dest_->write(src.data(), static_cast(src.size())); + } + friend void AbslFormatFlush(OStreamAbslStringifySink* dest, + absl::string_view src) { + dest->Append(src); + } + + private: + std::ostream* dest_; +}; + +// Adapts a sink for `AbslStringify()` to `std::ostream`. +template +class AbslStringifyOStream : public std::ostream { + public: + explicit AbslStringifyOStream(Sink* dest ABSL_ATTRIBUTE_LIFETIME_BOUND) + : std::ostream(&streambuf_), streambuf_(dest) {} + + AbslStringifyOStream(AbslStringifyOStream&& that) noexcept + : std::ostream(static_cast(that)), + streambuf_(std::move(that.streambuf_)) { + set_rdbuf(&streambuf_); + } + AbslStringifyOStream& operator=(AbslStringifyOStream&& that) noexcept { + std::ostream::operator=(static_cast(that)); + streambuf_ = std::move(that.streambuf_); + return *this; + } + + private: + class AbslStringifyStreambuf; + + AbslStringifyStreambuf streambuf_; +}; + +// Implementation details follow. + +template +class AbslStringifyOStream::AbslStringifyStreambuf + : public std::streambuf { + public: + explicit AbslStringifyStreambuf(Sink* dest ABSL_ATTRIBUTE_LIFETIME_BOUND) + : dest_(dest) {} + + AbslStringifyStreambuf(const AbslStringifyStreambuf& that) = default; + AbslStringifyStreambuf& operator=(const AbslStringifyStreambuf& that) = + default; + + protected: + int overflow(int src) override { + if (src != traits_type::eof()) { + const char ch = static_cast(src); + dest_->Append(absl::string_view(&ch, 1)); + } + return traits_type::not_eof(src); + } + + std::streamsize xsputn(const char* src, std::streamsize length) override { + assert(length >= 0); + dest_->Append(absl::string_view(src, static_cast(length))); + return length; + } + + private: + Sink* dest_; +}; + +} // namespace riegeli + +#endif // RIEGELI_BASE_STREAM_UTILS_H_ diff --git a/riegeli/containers/BUILD b/riegeli/containers/BUILD index 639bac1f..0467a955 100644 --- a/riegeli/containers/BUILD +++ b/riegeli/containers/BUILD @@ -14,6 +14,7 @@ cc_library( "//riegeli/base:assert", "//riegeli/base:compact_string", "//riegeli/base:compare", + "//riegeli/base:debug", "//riegeli/base:dependency", "//riegeli/base:iterable", "//riegeli/base:memory_estimator", @@ -43,6 +44,7 @@ cc_library( "//riegeli/base:binary_search", "//riegeli/base:compact_string", "//riegeli/base:compare", + "//riegeli/base:debug", "//riegeli/base:dependency", "//riegeli/base:iterable", "//riegeli/base:memory_estimator", diff --git a/riegeli/containers/chunked_sorted_string_set.cc b/riegeli/containers/chunked_sorted_string_set.cc index bb7d392d..085c0e31 100644 --- a/riegeli/containers/chunked_sorted_string_set.cc +++ b/riegeli/containers/chunked_sorted_string_set.cc @@ -26,13 +26,13 @@ #include "absl/base/optimization.h" #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "riegeli/base/arithmetic.h" #include "riegeli/base/assert.h" #include "riegeli/base/binary_search.h" #include "riegeli/base/compare.h" +#include "riegeli/base/debug.h" #include "riegeli/base/memory_estimator.h" #include "riegeli/bytes/reader.h" #include "riegeli/bytes/writer.h" @@ -293,9 +293,9 @@ absl::StatusOr ChunkedSortedStringSet::Builder::InsertNextImpl( if (ABSL_PREDICT_FALSE(current_builder_.size() == chunk_size_)) { if (ABSL_PREDICT_FALSE(element <= current_builder_.last())) { if (ABSL_PREDICT_TRUE(element == current_builder_.last())) return false; - return absl::FailedPreconditionError(absl::StrCat( - "Elements are not sorted: new \"", absl::CHexEscape(element), - "\" < last \"", absl::CHexEscape(last()), "\"")); + return absl::FailedPreconditionError( + absl::StrCat("Elements are not sorted: new ", riegeli::Debug(element), + " < last ", riegeli::Debug(last()))); } AddChunk(); } diff --git a/riegeli/containers/linear_sorted_string_set.cc b/riegeli/containers/linear_sorted_string_set.cc index 16d26a4b..9de238d7 100644 --- a/riegeli/containers/linear_sorted_string_set.cc +++ b/riegeli/containers/linear_sorted_string_set.cc @@ -28,7 +28,6 @@ #include "absl/numeric/bits.h" #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" @@ -36,6 +35,7 @@ #include "riegeli/base/assert.h" #include "riegeli/base/compact_string.h" #include "riegeli/base/compare.h" +#include "riegeli/base/debug.h" #include "riegeli/base/memory_estimator.h" #include "riegeli/bytes/compact_string_writer.h" #include "riegeli/bytes/reader.h" @@ -343,9 +343,9 @@ absl::Status LinearSortedStringSet::DecodeImpl(Reader& src, ABSL_PREDICT_FALSE(absl::string_view(ptr, current_length) <= *current_if_validated)) { return src.AnnotateStatus(absl::InvalidArgumentError(absl::StrCat( - "Elements are not sorted and unique: new \"", - absl::CHexEscape(absl::string_view(ptr, current_length)), - "\" <= last \"", absl::CHexEscape(*current_if_validated), "\""))); + "Elements are not sorted and unique: new ", + riegeli::Debug(absl::string_view(ptr, current_length)), + " <= last ", riegeli::Debug(*current_if_validated)))); } current_if_validated_and_shared.clear(); current_if_validated = absl::string_view(ptr, current_length); @@ -383,12 +383,12 @@ absl::Status LinearSortedStringSet::DecodeImpl(Reader& src, current_if_validated->data() + shared_length, current_if_validated->size() - shared_length))) { return src.AnnotateStatus(absl::InvalidArgumentError(absl::StrCat( - "Elements are not sorted and unique: new \"", - absl::CHexEscape( + "Elements are not sorted and unique: new ", + riegeli::Debug( absl::StrCat(absl::string_view(current_if_validated->data(), shared_length), absl::string_view(ptr, unshared_length))), - "\" <= last \"", absl::CHexEscape(*current_if_validated), "\""))); + " <= last ", riegeli::Debug(*current_if_validated)))); } // The unshared part of the next element will be written here. char* current_unshared; @@ -689,9 +689,9 @@ absl::StatusOr LinearSortedStringSet::Builder::InsertNextImpl( last_.size() - shared_length); if (ABSL_PREDICT_FALSE(unshared_element <= unshared_last) && !empty()) { if (ABSL_PREDICT_TRUE(unshared_element == unshared_last)) return false; - return absl::FailedPreconditionError(absl::StrCat( - "Elements are not sorted: new \"", absl::CHexEscape(element), - "\" < last \"", absl::CHexEscape(last()), "\"")); + return absl::FailedPreconditionError( + absl::StrCat("Elements are not sorted: new ", riegeli::Debug(element), + " < last ", riegeli::Debug(last()))); } if (shared_length == 1) { // If only the first byte is shared, write the element fully unshared. diff --git a/riegeli/csv/BUILD b/riegeli/csv/BUILD index 439da392..fe0c0abd 100644 --- a/riegeli/csv/BUILD +++ b/riegeli/csv/BUILD @@ -13,6 +13,7 @@ cc_library( ":csv_record", "//riegeli/base:arithmetic", "//riegeli/base:assert", + "//riegeli/base:debug", "//riegeli/base:dependency", "//riegeli/base:initializer", "//riegeli/base:object", @@ -38,6 +39,7 @@ cc_library( ":csv_record", "//riegeli/base:arithmetic", "//riegeli/base:assert", + "//riegeli/base:debug", "//riegeli/base:dependency", "//riegeli/base:initializer", "//riegeli/base:iterable", diff --git a/riegeli/csv/csv_reader.cc b/riegeli/csv/csv_reader.cc index fc32fc20..b99d29fa 100644 --- a/riegeli/csv/csv_reader.cc +++ b/riegeli/csv/csv_reader.cc @@ -24,13 +24,13 @@ #include "absl/base/optimization.h" #include "absl/status/status.h" -#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/span.h" #include "riegeli/base/arithmetic.h" #include "riegeli/base/assert.h" +#include "riegeli/base/debug.h" #include "riegeli/base/maker.h" #include "riegeli/base/status.h" #include "riegeli/bytes/reader.h" @@ -41,14 +41,6 @@ namespace riegeli { -namespace { - -std::string ShowEscaped(char ch) { - return absl::StrCat("'", absl::CHexEscape(absl::string_view(&ch, 1)), "'"); -} - -} // namespace - void CsvReaderBase::Initialize(Reader* src, Options&& options) { RIEGELI_ASSERT(src != nullptr) << "Failed precondition of CsvReader: null Reader pointer"; @@ -68,20 +60,20 @@ void CsvReaderBase::Initialize(Reader* src, Options&& options) { *options.comment() == '\r')) { Fail(absl::InvalidArgumentError( absl::StrCat("Comment character conflicts with record separator: ", - ShowEscaped(*options.comment())))); + riegeli::Debug(*options.comment())))); return; } if (ABSL_PREDICT_FALSE(options.field_separator() == '\n' || options.field_separator() == '\r')) { Fail(absl::InvalidArgumentError( absl::StrCat("Field separator conflicts with record separator: ", - ShowEscaped(options.field_separator())))); + riegeli::Debug(options.field_separator())))); return; } if (ABSL_PREDICT_FALSE(options.field_separator() == options.comment())) { Fail(absl::InvalidArgumentError( absl::StrCat("Field separator conflicts with comment character: ", - ShowEscaped(options.field_separator())))); + riegeli::Debug(options.field_separator())))); return; } if (options.quote() != absl::nullopt) { @@ -89,19 +81,19 @@ void CsvReaderBase::Initialize(Reader* src, Options&& options) { *options.quote() == '\r')) { Fail(absl::InvalidArgumentError( absl::StrCat("Quote character conflicts with record separator: ", - ShowEscaped(*options.quote())))); + riegeli::Debug(*options.quote())))); return; } if (ABSL_PREDICT_FALSE(*options.quote() == options.comment())) { Fail(absl::InvalidArgumentError( absl::StrCat("Quote character conflicts with comment character: ", - ShowEscaped(*options.quote())))); + riegeli::Debug(*options.quote())))); return; } if (ABSL_PREDICT_FALSE(*options.quote() == options.field_separator())) { Fail(absl::InvalidArgumentError( absl::StrCat("Quote character conflicts with field separator: ", - ShowEscaped(*options.quote())))); + riegeli::Debug(*options.quote())))); return; } } @@ -110,25 +102,25 @@ void CsvReaderBase::Initialize(Reader* src, Options&& options) { *options.escape() == '\r')) { Fail(absl::InvalidArgumentError( absl::StrCat("Escape character conflicts with record separator: ", - ShowEscaped(*options.escape())))); + riegeli::Debug(*options.escape())))); return; } if (ABSL_PREDICT_FALSE(*options.escape() == options.comment())) { Fail(absl::InvalidArgumentError( absl::StrCat("Escape character conflicts with comment character: ", - ShowEscaped(*options.escape())))); + riegeli::Debug(*options.escape())))); return; } if (ABSL_PREDICT_FALSE(*options.escape() == options.field_separator())) { Fail(absl::InvalidArgumentError( absl::StrCat("Escape character conflicts with field separator: ", - ShowEscaped(*options.escape())))); + riegeli::Debug(*options.escape())))); return; } if (ABSL_PREDICT_FALSE(*options.escape() == options.quote())) { Fail(absl::InvalidArgumentError( absl::StrCat("Escape character conflicts with quote character: ", - ShowEscaped(*options.escape())))); + riegeli::Debug(*options.escape())))); return; } } diff --git a/riegeli/csv/csv_writer.cc b/riegeli/csv/csv_writer.cc index 8f364db8..e04d3123 100644 --- a/riegeli/csv/csv_writer.cc +++ b/riegeli/csv/csv_writer.cc @@ -18,17 +18,16 @@ #include #include -#include #include #include "absl/base/optimization.h" #include "absl/status/status.h" -#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "riegeli/base/arithmetic.h" #include "riegeli/base/assert.h" +#include "riegeli/base/debug.h" #include "riegeli/base/status.h" #include "riegeli/bytes/writer.h" #include "riegeli/csv/csv_record.h" @@ -37,14 +36,6 @@ namespace riegeli { -namespace { - -std::string ShowEscaped(char ch) { - return absl::StrCat("'", absl::CHexEscape(absl::string_view(&ch, 1)), "'"); -} - -} // namespace - void CsvWriterBase::Initialize(Writer* dest, Options&& options) { RIEGELI_ASSERT(dest != nullptr) << "Failed precondition of CsvWriter: null Writer pointer"; @@ -68,20 +59,20 @@ void CsvWriterBase::Initialize(Writer* dest, Options&& options) { *options.comment() == '\r')) { Fail(absl::InvalidArgumentError( absl::StrCat("Comment character conflicts with record separator: ", - ShowEscaped(*options.comment())))); + riegeli::Debug(*options.comment())))); return; } if (ABSL_PREDICT_FALSE(options.field_separator() == '\n' || options.field_separator() == '\r')) { Fail(absl::InvalidArgumentError( absl::StrCat("Field separator conflicts with record separator: ", - ShowEscaped(options.field_separator())))); + riegeli::Debug(options.field_separator())))); return; } if (ABSL_PREDICT_FALSE(options.field_separator() == options.comment())) { Fail(absl::InvalidArgumentError( absl::StrCat("Field separator conflicts with comment character: ", - ShowEscaped(options.field_separator())))); + riegeli::Debug(options.field_separator())))); return; } if (options.quote() != absl::nullopt) { @@ -89,19 +80,19 @@ void CsvWriterBase::Initialize(Writer* dest, Options&& options) { *options.quote() == '\r')) { Fail(absl::InvalidArgumentError( absl::StrCat("Quote character conflicts with record separator: ", - ShowEscaped(*options.quote())))); + riegeli::Debug(*options.quote())))); return; } if (ABSL_PREDICT_FALSE(*options.quote() == options.comment())) { Fail(absl::InvalidArgumentError( absl::StrCat("Quote character conflicts with comment character: ", - ShowEscaped(*options.quote())))); + riegeli::Debug(*options.quote())))); return; } if (ABSL_PREDICT_FALSE(*options.quote() == options.field_separator())) { Fail(absl::InvalidArgumentError( absl::StrCat("Quote character conflicts with field separator: ", - ShowEscaped(*options.quote())))); + riegeli::Debug(*options.quote())))); return; } } @@ -214,7 +205,7 @@ bool CsvWriterBase::WriteField(Writer& dest, absl::string_view field) { absl::StrCat("If quoting is turned off, special characters inside " "fields are not " "expressible: ", - ShowEscaped(field[i])))); + riegeli::Debug(field[i])))); } return WriteQuoted(dest, field, i); }