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