| /* |
| * Copyright 2024 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #ifndef CONTRIB_DUMP_MACRO_DUMP_H_ |
| #define CONTRIB_DUMP_MACRO_DUMP_H_ |
| |
| // This header provides 2 convenience macros for writing variables and/or |
| // expressions to logs: DUMP() and DBG(). |
| // |
| // DUMP() converts all the arguments as a string with key-value pairs. |
| // |
| // Example: |
| // ```cpp |
| // std::string GetNameById(int id) { |
| // std::string name = (id == 42 ? "apple" : "banana"); |
| // LOG(INFO) << DUMP(id, name); |
| // return name; |
| // } |
| // GetNameById(42); |
| // ``` |
| // |
| // The above code will print `id = 42, name = "apple"` into log. |
| // |
| // DBG() is a shortcut for printf-style debugging, which prints all the |
| // arguments as key-value pairs with location information into std::cerr. |
| // |
| // Example: |
| // ```cpp |
| // std::string GetNameById(int id) { |
| // std::string name = (id == 42 ? "apple" : "banana"); |
| // DBG(id, name); |
| // return name; |
| // } |
| // GetNameById(42); |
| // ``` |
| // |
| // The above code will print (assuming DBG() is located at line 10): |
| // [example.cc:10:GetNameById] id = 42, name = "apple" |
| |
| #include <unistd.h> |
| |
| #include <cstdint> |
| #include <cstdio> |
| #include <iostream> |
| #include <ranges> // NOLINT(build/include_order) - C++20 header is not recognized yet |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| namespace dbg { |
| |
| namespace impl { |
| |
| // Escapes the string using C-style escape sequences. |
| inline std::string Escape(std::string_view src) { |
| static constexpr char kHexChar[] = "0123456789abcdef"; |
| |
| std::string dst; |
| bool last_hex_escape = false; |
| for (char c : src) { |
| bool is_hex_escape = false; |
| switch (c) { |
| case '\n': |
| dst.append("\\n"); |
| break; |
| case '\r': |
| dst.append("\\r"); |
| break; |
| case '\t': |
| dst.append("\\t"); |
| break; |
| case '\"': |
| dst.append("\\\""); |
| break; |
| case '\'': |
| dst.append("\\'"); |
| break; |
| case '\\': |
| dst.append("\\\\"); |
| break; |
| default: { |
| // Note that if we emit \xNN and the src character after that is a hex |
| // digit then that digit must be escaped too to prevent it being |
| // interpreted as part of the character code by C. |
| auto uc = static_cast<unsigned char>(c); |
| bool ambiguous_hex = last_hex_escape && std::isxdigit(uc); |
| if (std::isprint(uc) && !ambiguous_hex) { |
| dst.push_back(c); |
| } else { |
| dst.append("\\x"); |
| dst.push_back(kHexChar[uc / 16]); |
| dst.push_back(kHexChar[uc % 16]); |
| is_hex_escape = true; |
| } |
| } |
| } |
| last_hex_escape = is_hex_escape; |
| } |
| |
| return dst; |
| } |
| |
| template <typename T> |
| consteval std::string_view PrettyFunctionName() { |
| return __PRETTY_FUNCTION__; |
| } |
| |
| // A helper function for retrieving type name from templated type argument. |
| template <typename T> |
| consteval auto TypeName() { |
| // Using compile-time constant evaluation instead of a hard-coded value here |
| // for robustness and portability. On clang, this looks like: |
| // std::string_view PrettyFunctionName() [T = void] |
| constexpr std::string_view kVoidName = PrettyFunctionName<void>(); |
| constexpr size_t kPrefix = kVoidName.find("void"); |
| static_assert(kPrefix != std::string_view::npos); |
| constexpr size_t kSuffix = kVoidName.size() - kPrefix - 4; |
| |
| std::string_view name = PrettyFunctionName<T>(); |
| return name.substr(kPrefix, name.size() - kPrefix - kSuffix); |
| } |
| |
| // A helper type to utilize overload resolution with lambdas. |
| template <class... Ts> |
| struct Overloads : Ts... { |
| using Ts::operator()...; |
| }; |
| |
| template <typename... Ts> |
| Overloads(Ts...) -> Overloads<Ts...>; |
| |
| // A compile-time boolean constant macro to check whether value is |
| // structured-bindable. Uses Statement Expression extension to create SFINAE |
| // with structured binding. |
| #define DBG_IS_BINDABLE(...) \ |
| decltype((Overloads{ \ |
| [](auto&& x, int) -> decltype(({ \ |
| [[maybe_unused]] auto& [__VA_ARGS__] = x; \ |
| std::true_type{}; \ |
| })) {}, \ |
| [](auto&& x, bool) -> std::false_type {}, \ |
| })(value, 0))::value |
| |
| // A constexpr if branch used in AsTuple(). Note that "else" is required to |
| // correctly discarded the unwanted branches. |
| #define DBG_AS_TUPLE(...) \ |
| /* NOLINTNEXTLINE(readability/braces) */ \ |
| else if constexpr (DBG_IS_BINDABLE(__VA_ARGS__)) { \ |
| auto& [__VA_ARGS__] = value; \ |
| return std::tie(__VA_ARGS__); \ |
| } |
| |
| // Converts a struct value to a tuple. Need to manually expand the members |
| // because structured bindings cannot introduce a pack yet. See |
| // https://wg21.link/P1061 for the proposal to improve this in C++. |
| template <typename T> |
| constexpr auto AsTuple(const T& value) { |
| if constexpr (std::is_empty_v<T>) { |
| return std::tie(); |
| } |
| |
| // Supports up to 15 elements. |
| DBG_AS_TUPLE(e1) |
| DBG_AS_TUPLE(e1, e2) |
| DBG_AS_TUPLE(e1, e2, e3) |
| DBG_AS_TUPLE(e1, e2, e3, e4) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7, e8) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7, e8, e9) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14) |
| DBG_AS_TUPLE(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15) |
| } |
| |
| #undef DBG_AS_TUPLE |
| #undef DBG_IS_BINDABLE |
| |
| // A helper type for templated overloads resolution. |
| template <int N> |
| struct PriorityTag : PriorityTag<N - 1> {}; |
| |
| template <> |
| struct PriorityTag<0> {}; |
| |
| class Printer { |
| public: |
| template <typename T> |
| static std::string Stringify(T&& value) { |
| Printer printer; |
| printer.Print(std::forward<T>(value)); |
| return printer.GetString(); |
| } |
| |
| private: |
| std::string GetString() { return stream_.str(); } |
| |
| template <typename T> |
| void Print(T&& value) { |
| Print(std::forward<T>(value), PriorityTag<10>()); |
| } |
| |
| // TODO(shik): Support bytes with hex output. |
| // TODO(shik): Be more careful with char*, which may not have null terminator |
| // and we should limit the printing length. |
| // TODO(shik): Handle cycle in recursive type. |
| |
| // Boolean value is printed as "true" or "false". |
| template <typename T> |
| requires std::same_as<std::decay_t<T>, bool> |
| void Print(T&& value, PriorityTag<10>) { |
| stream_ << (value ? "true" : "false"); |
| } |
| |
| // nullptr_t is printed as "nullptr". |
| template <typename T> |
| requires std::same_as<std::decay_t<T>, std::nullptr_t> |
| void Print(T&& value, PriorityTag<9>) { |
| stream_ << "nullptr"; |
| } |
| |
| // Char-like value is quoted and escaped as 'x' and '\n'. |
| template <typename T> |
| requires std::same_as<std::decay_t<T>, char> |
| void Print(T&& value, PriorityTag<8>) { |
| std::decay_t<T> ch = value; |
| std::string_view view(&ch, 1); |
| stream_ << "'" << Escape(view) << "'"; |
| } |
| |
| // String-like value is quoted and escaped as "foo\nbar". |
| template <typename T> |
| requires std::convertible_to<T, std::string_view> |
| void Print(T&& str, PriorityTag<7>) { |
| if constexpr (std::is_pointer_v<std::decay_t<T>>) { |
| // It's invalid to construct a std::string_view from nullptr until P0903 |
| // "Define basic_string_view(nullptr)" is accepted. See |
| // https://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p0903r2.pdf |
| if (str == nullptr) { |
| stream_ << "nullptr"; |
| return; |
| } |
| } |
| auto view = static_cast<std::string_view>(str); |
| stream_ << '"' << Escape(view) << '"'; |
| } |
| |
| // Container-like range value is printed as [v1, v2, ...]. |
| template <typename T> |
| requires std::ranges::range<T> |
| void Print(T&& range, PriorityTag<6>) { |
| auto begin = std::ranges::begin(range); |
| auto end = std::ranges::end(range); |
| stream_ << "["; |
| for (auto it = begin; it != end; ++it) { |
| if (it != begin) { |
| stream_ << ", "; |
| } |
| Print(*it); |
| } |
| stream_ << "]"; |
| } |
| |
| // Tuple-like value is printed as (v1, v2, ...). |
| // This is put after container overload to match std::array as a container. |
| template <typename T> |
| requires requires { typename std::tuple_size<std::decay_t<T>>::type; } |
| void Print(T&& tuple, PriorityTag<5>) { |
| stream_ << "("; |
| std::apply( |
| [&](const auto&... element) { |
| size_t idx = 0; |
| ((stream_ << (idx++ > 0 ? ", " : ""), Print(element)), ...); |
| }, |
| std::forward<T>(tuple)); |
| stream_ << ")"; |
| } |
| |
| // Structure-bindable class value is printed as {e1, e2, ...}. |
| template <typename T> |
| requires(std::is_class_v<std::decay_t<T>> && |
| !std::is_same_v<void, decltype(AsTuple(std::declval<T>()))>) |
| void Print(T&& value, PriorityTag<4>) { |
| stream_ << "{"; |
| std::apply( |
| [&](const auto&... element) { |
| size_t idx = 0; |
| ((stream_ << (idx++ > 0 ? ", " : ""), Print(element)), ...); |
| }, |
| AsTuple(std::forward<T>(value))); |
| stream_ << "}"; |
| } |
| |
| // Raw pointer value is printed as "nullptr" or a hexadecimal integer. |
| // Note that we cannot print the pointee, since it's valid to have a raw |
| // pointer that points to one past the last element of the array, which is |
| // invalid to dereference. |
| template <typename T> |
| requires(std::is_pointer_v<std::decay_t<T>>) |
| void Print(T&& ptr, PriorityTag<3>) { |
| if (ptr == nullptr) { |
| stream_ << "nullptr"; |
| } else { |
| stream_ << "0x" << std::hex << reinterpret_cast<uintptr_t>(ptr) |
| << std::dec; |
| } |
| } |
| |
| // Smart pointer such as std::unique_ptr or std::shared_ptr is printed as a |
| // raw pointer with its pointee. |
| template <typename T> |
| requires requires(T ptr) { |
| typename std::decay_t<T>::element_type; |
| { *ptr }; // NOLINT(readability/braces) - `;` is required here. |
| { ptr.get() } -> std::convertible_to<const void*>; |
| } |
| void Print(T&& ptr, PriorityTag<2>) { |
| const void* raw = ptr.get(); |
| Print(raw); |
| if (raw != nullptr) { |
| stream_ << " ("; |
| Print(*ptr); |
| stream_ << ")"; |
| } |
| } |
| |
| // Use operator<< if it's not specialized above. The requires statement is |
| // added to make the error message more readable, otherwise compiler will list |
| // all possible candidate overloads of operator<<. |
| template <typename T> |
| requires requires(std::ostream& os, T value) { |
| { os << value } -> std::convertible_to<std::ostream&>; |
| } |
| void Print(T&& value, PriorityTag<1>) { |
| stream_ << value; |
| } |
| |
| // If the value is unprintable at all, simply shows "<unprintable TypeName>". |
| template <typename T> |
| void Print(T&& value, PriorityTag<0>) { |
| stream_ << "<unprintable " << TypeName<std::decay_t<T>>() << ">"; |
| } |
| |
| std::stringstream stream_; |
| }; |
| |
| inline void DebugPrint(std::string_view file, |
| int line, |
| std::string_view func, |
| std::string_view args) { |
| // ANSI color escape codes are applied for highlighting when stderr is a tty. |
| constexpr char kBrightBlack[] = "\e[1;30m"; |
| constexpr char kBrightWhite[] = "\e[1;37m"; |
| constexpr char kReset[] = "\e[0m"; |
| static bool use_color = [] { return isatty(fileno(stderr)); }(); |
| auto may_colorize = [](const char* ansi) { return use_color ? ansi : ""; }; |
| |
| // Output in the following format: |
| // [file:line:func] k1 = v1, k2 = v2, ... |
| std::cerr << may_colorize(kBrightBlack) << "[" << file << ":" << line << ":" |
| << func << "] " << may_colorize(kBrightWhite) << args |
| << may_colorize(kReset) << std::endl; |
| } |
| |
| template <typename... T> |
| std::string Dump(std::initializer_list<std::string_view> names, T&&... values) { |
| auto value_strings = |
| std::initializer_list<std::string>{Printer::Stringify(values)...}; |
| std::stringstream stream; |
| auto name_it = names.begin(); |
| auto value_it = value_strings.begin(); |
| for (size_t i = 0; i < names.size(); ++i) { |
| stream << (i > 0 ? ", " : "") << *name_it++ << " = " << *value_it++; |
| } |
| return stream.str(); |
| } |
| |
| }; // namespace impl |
| |
| // Converts the value to a pretty-printed string. |
| template <typename T> |
| std::string Stringify(T&& value) { |
| return impl::Printer::Stringify(std::forward<T>(value)); |
| } |
| |
| } // namespace dbg |
| |
| // Recursive macro tricks with C++20 __VA_OPT__. See |
| // https://www.scs.stanford.edu/~dm/blog/va-opt.html for more details. |
| #define DBG_PARENS () |
| |
| // Evaluates (re-scan) the macros at least 4^4 = 256 times. |
| #define DBG_EVAL(...) DBG_EVAL4(DBG_EVAL4(DBG_EVAL4(DBG_EVAL4(__VA_ARGS__)))) |
| #define DBG_EVAL4(...) DBG_EVAL3(DBG_EVAL3(DBG_EVAL3(DBG_EVAL3(__VA_ARGS__)))) |
| #define DBG_EVAL3(...) DBG_EVAL2(DBG_EVAL2(DBG_EVAL2(DBG_EVAL2(__VA_ARGS__)))) |
| #define DBG_EVAL2(...) DBG_EVAL1(DBG_EVAL1(DBG_EVAL1(DBG_EVAL1(__VA_ARGS__)))) |
| #define DBG_EVAL1(...) __VA_ARGS__ |
| |
| // DBG_MAP(fn, a1, a2, a3, ...) => fn(a1), fn(a2), fn(a3), ... |
| #define DBG_MAP(fn, ...) __VA_OPT__(DBG_EVAL(DBG_MAP_IMPL(fn, __VA_ARGS__))) |
| #define DBG_MAP_IMPL(fn, a1, ...) \ |
| fn(a1), __VA_OPT__(DBG_MAP_AGAIN DBG_PARENS(fn, __VA_ARGS__)) |
| #define DBG_MAP_AGAIN() DBG_MAP_IMPL |
| |
| // DBG_STRINGIFY(foo) => "foo" |
| #define DBG_STRINGIFY(x) #x |
| |
| #define DUMP(...) \ |
| dbg::impl::Dump({DBG_MAP(DBG_STRINGIFY, __VA_ARGS__)}, __VA_ARGS__) |
| |
| #define DBG(...) \ |
| dbg::impl::DebugPrint(__FILE__, __LINE__, __FUNCTION__, DUMP(__VA_ARGS__)) |
| |
| #endif // CONTRIB_DUMP_MACRO_DUMP_H_ |