quill
Codec.h
1 
7 #pragma once
8 
9 #include "quill/bundled/fmt/base.h"
10 
11 #include "quill/core/Attributes.h"
12 #include "quill/core/DynamicFormatArgStore.h"
13 #include "quill/core/InlinedVector.h"
14 
15 #include <cstddef>
16 #include <cstdint>
17 #include <cstring>
18 #include <limits>
19 #include <string>
20 #include <string_view>
21 #include <type_traits>
22 
23 QUILL_BEGIN_NAMESPACE
24 
25 namespace detail
26 {
27 
28 #if defined(_WIN32)
29  #if defined(__MINGW32__)
30  #pragma GCC diagnostic push
31  #pragma GCC diagnostic ignored "-Wredundant-decls"
32  #endif
33 
35 QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED extern std::string utf8_encode(std::wstring_view str);
36 QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED extern std::string utf8_encode(std::byte const* data,
37  size_t wide_str_len);
38 
39  #if defined(__MINGW32__)
40  #pragma GCC diagnostic pop
41  #endif
42 #endif
43 
47 template <class T>
49 {
50  typedef std::remove_cv_t<std::remove_reference_t<T>> type;
51 };
52 
53 template <class T>
54 using remove_cvref_t = typename remove_cvref<T>::type;
55 
56 template <class>
57 constexpr bool always_false_v = false;
58 
59 template <typename Arg>
60 void codec_not_found_for_type()
61 {
62  static_assert(
63  always_false_v<Arg>,
64  "\n"
65  "+------------------------------------------------------------------------------+\n"
66  "| Missing Codec for Type 'Arg' |\n"
67  "+------------------------------------------------------------------------------+\n"
68  "\n"
69  "Error: A codec for the specified type 'Arg' is not available.\n"
70  "\n"
71  "Possible solutions:\n"
72  "1. If Arg is an STL type:\n"
73  " - Ensure you have included the necessary headers for the specific STL type you are using "
74  "from the quill/std folder.\n"
75  "\n"
76  "2. If Arg is a user-defined type:\n"
77  " - Use either 'DeferredFormatCodec' or 'DirectFormatCodec'.\n"
78  " - Define a custom Codec for your type.\n"
79  " - Consider converting the value to a string before logging.\n"
80  "\n"
81  "Note: The specific type of 'Arg' can be found in the compiler error message.\n"
82  " Look for the instantiation of 'codec_not_found_for_type<Arg>' in the error output.\n"
83  " The compiler should indicate what type 'Arg' represents in this instance.\n"
84  "\n"
85  "For more information see https://quillcpp.readthedocs.io/en/latest/cheat_sheet.html\n");
86 }
87 
88 /***/
89 QUILL_NODISCARD inline size_t safe_strnlen(char const* str, size_t maxlen) noexcept
90 {
91  if (!str)
92  {
93  return 0;
94  }
95 
96 #if defined(__GNUC__) && !defined(__clang__)
97  // Suppress false positive in GCC - memchr safely stops at null terminator
98  #pragma GCC diagnostic push
99  #if __GNUC__ >= 13
100  #pragma GCC diagnostic ignored "-Wstringop-overread"
101  #endif
102 
103  // Suppress during LTO analysis
104  asm volatile("" : "+r"(maxlen) : : "memory");
105 #endif
106 
107  auto end = static_cast<char const*>(std::memchr(str, '\0', maxlen));
108 
109 #if defined(__GNUC__) && !defined(__clang__)
110  #pragma GCC diagnostic pop
111 #endif
112 
113  return end ? static_cast<size_t>(end - str) : maxlen;
114 }
115 
116 /***/
117 QUILL_NODISCARD inline size_t safe_strnlen(char const* str) noexcept
118 {
119 #if defined(__i386__) || defined(__arm__)
120  // On i386, armel and armhf std::memchr "max number of bytes to examine" set to maximum size of
121  // unsigned int which does not compile
122  // Currently Debian package is using architecture `any` which includes them
123  static constexpr int32_t max_len = std::numeric_limits<int32_t>::max();
124 #else
125  static constexpr uint32_t max_len = std::numeric_limits<uint32_t>::max();
126 #endif
127 
128  return safe_strnlen(str, max_len);
129 }
131 template <typename T>
132 struct is_std_string : std::false_type
133 {
134 };
135 
136 template <typename Allocator>
137 struct is_std_string<std::basic_string<char, std::char_traits<char>, Allocator>> : std::true_type
138 {
139 };
140 } // namespace detail
141 
143 template <typename Arg, typename = void>
144 struct Codec
145 {
146  /***/
147  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static size_t compute_encoded_size(
148  QUILL_MAYBE_UNUSED detail::SizeCacheVector& conditional_arg_size_cache, QUILL_MAYBE_UNUSED Arg const& arg) noexcept
149  {
150  if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>,
151  std::is_same<Arg, void const*>, std::is_same<Arg, void*>>)
152  {
153  return sizeof(Arg);
154  }
155  else if constexpr (std::conjunction_v<std::is_array<Arg>, std::is_same<detail::remove_cvref_t<std::remove_extent_t<Arg>>, char>>)
156  {
157  size_t constexpr N = std::extent_v<Arg>;
158  size_t len = detail::safe_strnlen(arg, N) + 1u;
159  if (QUILL_UNLIKELY(len > std::numeric_limits<uint32_t>::max()))
160  {
161  len = std::numeric_limits<uint32_t>::max();
162  }
163  return conditional_arg_size_cache.push_back(static_cast<uint32_t>(len));
164  }
165  else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>>)
166  {
167  // for c strings we do an additional check for nullptr
168  // include one extra for the zero termination
169  size_t len = detail::safe_strnlen(arg) + 1u;
170  if (QUILL_UNLIKELY(len > std::numeric_limits<uint32_t>::max()))
171  {
172  len = std::numeric_limits<uint32_t>::max();
173  }
174  return conditional_arg_size_cache.push_back(static_cast<uint32_t>(len));
175  }
176  else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
177  {
178  // for std::string we also need to store the size in order to correctly retrieve it
179  // the reason for this is that if we create e.g:
180  // std::string msg = fmtquill::format("{} {} {} {} {}", (char)0, (char)0, (char)0, (char)0,
181  // "sssssssssssssssssssssss"); then strlen(msg.data()) = 0 but msg.size() = 31
182  return sizeof(uint32_t) + static_cast<uint32_t>(arg.length());
183  }
184  else
185  {
186  detail::codec_not_found_for_type<Arg>();
187  return 0;
188  }
189  }
190 
191  /***/
192  QUILL_ATTRIBUTE_HOT static void encode(std::byte*& buffer,
193  QUILL_MAYBE_UNUSED detail::SizeCacheVector const& conditional_arg_size_cache,
194  QUILL_MAYBE_UNUSED uint32_t& conditional_arg_size_cache_index,
195  Arg const& arg) noexcept
196  {
197  if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>,
198  std::is_same<Arg, void const*>, std::is_same<Arg, void*>>)
199  {
200  std::memcpy(buffer, &arg, sizeof(Arg));
201  buffer += sizeof(Arg);
202  }
203  else if constexpr (std::conjunction_v<std::is_array<Arg>, std::is_same<detail::remove_cvref_t<std::remove_extent_t<Arg>>, char>>)
204  {
205  size_t constexpr N = std::extent_v<Arg>;
206  uint32_t const len = conditional_arg_size_cache[conditional_arg_size_cache_index++];
207 
208  // Local copy improves generated code (avoids aliasing penalties)
209  std::byte* buf_ptr = buffer;
210 
211  if (QUILL_UNLIKELY(len > N))
212  {
213  // no '\0' in c array
214  QUILL_ASSERT(len == N + 1, "Invalid length for c array in Codec::encode(), expected N + 1");
215  std::memcpy(buf_ptr, arg, N);
216  buf_ptr[N] = std::byte{'\0'};
217  }
218  else
219  {
220  std::memcpy(buf_ptr, arg, len);
221  }
222 
223  buffer = buf_ptr + len;
224  }
225  else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>>)
226  {
227  // null terminator is included in the len for c style strings
228  uint32_t const len = conditional_arg_size_cache[conditional_arg_size_cache_index++];
229 
230  // Local copy improves generated code (avoids aliasing penalties)
231  std::byte* buf_ptr = buffer;
232 
233  if (QUILL_LIKELY(arg != nullptr))
234  {
235  // avoid gcc warning, even when size == 0
236  std::memcpy(buf_ptr, arg, len - 1);
237  }
238 
239  buf_ptr[len - 1] = std::byte{'\0'};
240  buffer = buf_ptr + len;
241  }
242  else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
243  {
244  // for std::string we store the size first, in order to correctly retrieve it
245  // Copy the length first and then the actual string
246  auto const len = static_cast<uint32_t>(arg.length());
247 
248  // Local copy improves generated code (avoids aliasing penalties)
249  std::byte* buf_ptr = buffer;
250  std::memcpy(buf_ptr, &len, sizeof(len));
251  buf_ptr += sizeof(len);
252 
253  // Only copy if length > 0 to avoid UBSAN with nullptr in memcpy
254  if (QUILL_LIKELY(len != 0))
255  {
256  std::memcpy(buf_ptr, arg.data(), len);
257  }
258 
259  buffer = buf_ptr + len;
260  }
261  else
262  {
263  detail::codec_not_found_for_type<Arg>();
264  }
265  }
266 
267  // We use two separate functions, decode_arg and decode_and_store_arg, because there are
268  // scenarios where we need to decode an argument without storing it in args_store, such as when
269  // dealing with nested types. Storing the argument requires a fmtquill formatter, so having
270  // two distinct functions allows us to avoid this requirement in cases where only decoding is needed.
271 
272  /***/
273  static auto decode_arg(std::byte*& buffer)
274  {
275  if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>,
276  std::is_same<Arg, void const*>, std::is_same<Arg, void*>>)
277  {
278  Arg arg;
279  std::memcpy(&arg, buffer, sizeof(Arg));
280  buffer += sizeof(Arg);
281  return arg;
282  }
283  else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>,
284  std::conjunction<std::is_array<Arg>, std::is_same<detail::remove_cvref_t<std::remove_extent_t<Arg>>, char>>>)
285  {
286  // c strings or char array
287  auto arg = reinterpret_cast<char const*>(buffer);
288  // for c_strings we add +1 to the length as we also want to copy the null terminated char
289  buffer += detail::safe_strnlen(arg) + 1u;
290  return arg;
291  }
292  else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
293  {
294  // for std::string we first need to retrieve the length
295  uint32_t len;
296  std::memcpy(&len, buffer, sizeof(len));
297  buffer += sizeof(len);
298  auto const arg = std::string_view{reinterpret_cast<char const*>(buffer), len};
299  buffer += len;
300  return arg;
301  }
302  else
303  {
304  detail::codec_not_found_for_type<Arg>();
305  return 0;
306  }
307  }
308 
309  /***/
310  static void decode_and_store_arg(std::byte*& buffer, DynamicFormatArgStore* args_store)
311  {
312  if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>,
313  std::is_same<Arg, void const*>, std::is_same<Arg, void*>>)
314  {
315  args_store->push_back(decode_arg(buffer));
316  }
317  else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>,
318  std::conjunction<std::is_array<Arg>, std::is_same<detail::remove_cvref_t<std::remove_extent_t<Arg>>, char>>>)
319  {
320  // c strings or char array
321  char const* arg = decode_arg(buffer);
322 
323  // pass the string_view to args_store to avoid the dynamic allocation
324  // we pass fmtquill::string_view since fmt/base.h includes a formatter for that type.
325  // for std::string_view we would need fmt/format.h
326  args_store->push_back(fmtquill::string_view{arg, detail::safe_strnlen(arg)});
327  }
328  else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
329  {
330  std::string_view arg = decode_arg(buffer);
331 
332  // pass the string_view to args_store to avoid the dynamic allocation
333  // we pass fmtquill::string_view since fmt/base.h includes a formatter for that type.
334  // for std::string_view we would need fmt/format.h
335  args_store->push_back(fmtquill::string_view{arg.data(), arg.size()});
336  }
337  else
338  {
339  detail::codec_not_found_for_type<Arg>();
340  }
341  }
342 };
343 
344 namespace detail
345 {
353 template <typename... Args>
354 QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t compute_encoded_size_and_cache_string_lengths(
355  QUILL_MAYBE_UNUSED SizeCacheVector& conditional_arg_size_cache, Args const&... args)
356 {
357  if constexpr (!std::conjunction_v<std::disjunction<std::is_arithmetic<remove_cvref_t<Args>>, std::is_same<remove_cvref_t<Args>, void const*>,
358  std::is_same<remove_cvref_t<Args>, void*>, is_std_string<remove_cvref_t<Args>>,
359  std::is_same<remove_cvref_t<Args>, std::string_view>>...>)
360  {
361  // Clear the cache if any argument type is not one of:
362  // arithmetic, void*, std::string, or std::string_view.
363  conditional_arg_size_cache.clear();
364  }
365 
366  size_t total_sum{0};
367  // Avoid using a fold expression with '+ ...' because we require a guaranteed evaluation
368  // order to ensure that each argument is processed in sequence. This is essential for
369  // correctly populating the conditional_arg_size_cache
370  ((total_sum += Codec<remove_cvref_t<Args>>::compute_encoded_size(conditional_arg_size_cache, args)), ...);
371  return total_sum;
372 }
373 
380 template <typename... Args>
381 QUILL_ATTRIBUTE_HOT void encode(std::byte*& buffer,
382  SizeCacheVector const& conditional_arg_size_cache, Args&&... args)
383 {
384  QUILL_MAYBE_UNUSED uint32_t conditional_arg_size_cache_index{0};
385  (Codec<remove_cvref_t<Args>>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
386  static_cast<decltype(args)&&>(args)),
387  ...);
388 }
389 
390 template <typename... Args>
391 void decode_and_store_arg(std::byte*& buffer, QUILL_MAYBE_UNUSED DynamicFormatArgStore* args_store)
392 {
393  (Codec<Args>::decode_and_store_arg(buffer, args_store), ...);
394 }
395 
399 using FormatArgsDecoder = void (*)(std::byte*& data, DynamicFormatArgStore& args_store);
400 
401 template <typename... Args>
402 void decode_and_store_args(std::byte*& buffer, DynamicFormatArgStore& args_store)
403 {
404  decode_and_store_arg<Args...>(buffer, &args_store);
405 }
406 } // namespace detail
407 
409 /***/
410 template <typename... TMembers>
411 size_t compute_total_encoded_size(detail::SizeCacheVector& conditional_arg_size_cache, TMembers const&... members)
412 {
413  size_t total_size{0};
414  ((total_size += Codec<detail::remove_cvref_t<TMembers>>::compute_encoded_size(conditional_arg_size_cache, members)),
415  ...);
416  return total_size;
417 }
418 
419 /***/
420 template <typename... TMembers>
421 void encode_members(std::byte*& buffer, detail::SizeCacheVector const& conditional_arg_size_cache,
422  uint32_t& conditional_arg_size_cache_index, TMembers const&... members)
423 {
424  ((Codec<detail::remove_cvref_t<TMembers>>::encode(buffer, conditional_arg_size_cache,
425  conditional_arg_size_cache_index, members)),
426  ...);
427 }
428 
429 /***/
430 template <typename T, typename... TMembers>
431 void decode_members(std::byte*& buffer, T&, TMembers&... members)
432 {
433  // T& arg is not used but if we remove it, it will crash all users who are passing the extra argument without a compile time error
434  ((members = Codec<detail::remove_cvref_t<TMembers>>::decode_arg(buffer)), ...);
435 }
436 
437 QUILL_END_NAMESPACE
Definition: UserDefinedDirectFormatFuzzer.cpp:81
void(*)(std::byte *&data, DynamicFormatArgStore &args_store) FormatArgsDecoder
Decode functions.
Definition: Codec.h:399
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t compute_encoded_size_and_cache_string_lengths(QUILL_MAYBE_UNUSED SizeCacheVector &conditional_arg_size_cache, Args const &... args)
Calculates the total size required to encode the provided arguments.
Definition: Codec.h:354
Similar to fmt::dynamic_arg_store but better suited to our needs e.g does not include <functional> an...
Definition: DynamicFormatArgStore.h:77
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:24
typename = void for specializations with enable_if
Definition: Codec.h:144
QUILL_ATTRIBUTE_HOT void encode(std::byte *&buffer, SizeCacheVector const &conditional_arg_size_cache, Args &&... args)
Encodes multiple arguments into a buffer.
Definition: Codec.h:381
C++14 implementation of C++20&#39;s remove_cvref.
Definition: Codec.h:48
std string detection, ignoring the Allocator type
Definition: Codec.h:132
A contiguous memory buffer with an optional growing ability.
Definition: base.h:1777
void push_back(T &&arg)
Adds an argument for later passing to a formatting function.
Definition: DynamicFormatArgStore.h:116