mimic++ v9.2.1
Loading...
Searching...
No Matches
Stacktrace.hpp
Go to the documentation of this file.
1// Copyright Dominic (DNKpp) Koepke 2024 - 2025.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at
4// https://www.boost.org/LICENSE_1_0.txt)
5
6#ifndef MIMICPP_UTILITIES_STACKTRACE_HPP
7#define MIMICPP_UTILITIES_STACKTRACE_HPP
8
9#pragma once
10
11#include "mimic++/Fwd.hpp"
19
20#ifndef MIMICPP_DETAIL_IS_MODULE
21 #include <algorithm>
22 #include <functional> // std::invoke
23 #include <limits>
24 #include <memory>
25 #include <ranges>
26 #include <stdexcept>
27 #include <type_traits>
28 #include <utility>
29#endif
30
32{
38 struct find_stacktrace_backend;
39}
40
42{
90
95 struct find_backend;
96
101 template <typename Backend>
102 struct backend_traits;
103
108 template <typename T>
109 concept backend =
110 std::semiregular<std::remove_cvref_t<T>>
111 && requires(backend_traits<std::remove_cvref_t<T>> traits, std::remove_cvref_t<T> const& backend, std::size_t const value) {
112 { decltype(traits)::current(value) } -> std::convertible_to<std::remove_cvref_t<T>>;
113 { decltype(traits)::current(value, value) } -> std::convertible_to<std::remove_cvref_t<T>>;
114 { decltype(traits)::size(backend) } -> std::convertible_to<std::size_t>;
115 { decltype(traits)::empty(backend) } -> util::boolean_testable;
116 { decltype(traits)::description(backend, value) } -> std::convertible_to<std::string>;
117 { decltype(traits)::source_file(backend, value) } -> std::convertible_to<std::string>;
118 { decltype(traits)::source_line(backend, value) } -> std::convertible_to<std::size_t>;
119 };
120
126 {
127 };
128
132}
133
134template <>
136{
137 [[nodiscard]]
138 static constexpr NullBackend current([[maybe_unused]] std::size_t const skip, [[maybe_unused]] std::size_t const max) noexcept
139 {
140 return NullBackend{};
141 }
142
143 [[nodiscard]]
144 static constexpr NullBackend current([[maybe_unused]] std::size_t const skip) noexcept
145 {
146 return NullBackend{};
147 }
148
149 [[nodiscard]]
150 static constexpr std::size_t size([[maybe_unused]] NullBackend const& stacktrace) noexcept
151 {
152 return 0u;
153 }
154
155 [[nodiscard]]
156 static constexpr bool empty([[maybe_unused]] NullBackend const& stacktrace) noexcept
157 {
158 return true;
159 }
160
161 static std::string description([[maybe_unused]] NullBackend const& stacktrace, [[maybe_unused]] std::size_t const at)
162 {
163 raise_unsupported_operation();
164 }
165
166 static std::string source_file([[maybe_unused]] NullBackend const& stacktrace, [[maybe_unused]] std::size_t const at)
167 {
168 raise_unsupported_operation();
169 }
170
171 [[nodiscard]]
172 static std::size_t source_line([[maybe_unused]] NullBackend const& stacktrace, [[maybe_unused]] std::size_t const at)
173 {
174 raise_unsupported_operation();
175 }
176
177private:
178 [[noreturn]]
179 static void raise_unsupported_operation()
180 {
181 throw std::runtime_error{"stacktrace::NullBackend doesn't support this operation."};
182 }
183};
184
185static_assert(
187 "stacktrace::NullBackend does not satisfy the stacktrace::backend concept");
188
190{
196 {
197 private:
198 class Concept
199 {
200 public:
201 [[nodiscard]]
202 constexpr virtual bool empty() const = 0;
203
204 [[nodiscard]]
205 constexpr virtual std::size_t size() const = 0;
206
207 [[nodiscard]]
208 constexpr virtual std::string description(std::size_t at) const = 0;
209
210 [[nodiscard]]
211 constexpr virtual std::string source_file(std::size_t at) const = 0;
212
213 [[nodiscard]]
214 constexpr virtual std::size_t source_line(std::size_t at) const = 0;
215
216 protected:
217 virtual ~Concept() = default;
218 Concept() = default;
219 Concept(Concept const&) = default;
220 Concept& operator=(Concept const&) = default;
221 Concept(Concept&&) = default;
222 Concept& operator=(Concept&&) = default;
223 };
224
225 template <typename Backend>
226 class Model final
227 : public Concept
228 {
229 public:
230 using BackendTraits = stacktrace::backend_traits<Backend>;
231
232 [[nodiscard]]
233 explicit Model(Backend&& backend) noexcept(std::is_nothrow_move_constructible_v<Backend>)
234 : m_Backend{std::move(backend)}
235 {
236 }
237
238 [[nodiscard]]
239 constexpr bool empty() const override
240 {
241 return std::invoke(BackendTraits::empty, m_Backend);
242 }
243
244 [[nodiscard]]
245 constexpr std::size_t size() const override
246 {
247 return std::invoke(BackendTraits::size, m_Backend);
248 }
249
250 [[nodiscard]]
251 constexpr std::string description(std::size_t const at) const override
252 {
253 return std::invoke(BackendTraits::description, m_Backend, at);
254 }
255
256 [[nodiscard]]
257 constexpr std::string source_file(std::size_t const at) const override
258 {
259 return std::invoke(BackendTraits::source_file, m_Backend, at);
260 }
261
262 [[nodiscard]]
263 constexpr std::size_t source_line(std::size_t const at) const override
264 {
265 return std::invoke(BackendTraits::source_line, m_Backend, at);
266 }
267
268 private:
269 Backend m_Backend;
270 };
271
272 public:
276 ~Stacktrace() = default;
277
282 : Stacktrace{stacktrace::NullBackend{}}
283 {
284 }
285
290 template <typename Backend>
291 requires(!std::same_as<Stacktrace, std::remove_cvref_t<Backend>>)
293 [[nodiscard]]
294 explicit Stacktrace(Backend backend)
295 : m_Backend{std::make_shared<Model<Backend>>(std::move(backend))}
296 {
297 }
298
302 Stacktrace(Stacktrace const&) = default;
303
307 Stacktrace& operator=(Stacktrace const&) = default;
308
312 [[nodiscard]]
313 Stacktrace(Stacktrace&&) = default;
314
319
324 [[nodiscard]]
325 std::size_t size() const
326 {
327 return backend().size();
328 }
329
334 [[nodiscard]]
335 bool empty() const
336 {
337 return backend().empty();
338 }
339
345 [[nodiscard]]
346 std::string description(std::size_t const at) const
347 {
348 return backend().description(at);
349 }
350
356 [[nodiscard]]
357 std::string source_file(std::size_t const at) const
358 {
359 return backend().source_file(at);
360 }
361
367 [[nodiscard]]
368 std::size_t source_line(std::size_t const at) const
369 {
370 return backend().source_line(at);
371 }
372
373 [[nodiscard]]
374 friend bool operator==(Stacktrace const& lhs, Stacktrace const& rhs)
375 {
376 return lhs.size() == rhs.size()
377 && std::ranges::all_of(
378 std::views::iota(0u, lhs.size()),
379 [&](std::size_t const index) {
380 return lhs.description(index) == rhs.description(index)
381 && lhs.source_file(index) == rhs.source_file(index)
382 && lhs.source_line(index) == rhs.source_line(index);
383 });
384 }
385
386 private:
387 std::shared_ptr<Concept> m_Backend;
388
389 [[nodiscard]]
390 Concept const& backend() const noexcept
391 {
392 MIMICPP_ASSERT(m_Backend, "Invalid state");
393
394 return *m_Backend;
395 }
396 };
397}
398
399namespace mimicpp::util::stacktrace::detail
400{
401 template <typename T>
402 concept backend_finder = requires {
403 typename T::type;
404 };
405
406 // ReSharper disable CppFunctionIsNotImplemented
407 template <template <typename> typename Traits, backend_finder FindBackend = custom::find_stacktrace_backend>
408 Traits<typename FindBackend::type> find_traits_impl([[maybe_unused]] priority_tag<1u>);
409
410 // When users explicitly register a custom stacktrace, we should not accidentally fall back to another backend.
411#ifndef MIMICPP_CONFIG_EXPERIMENTAL_USE_CUSTOM_STACKTRACE
412 template <template <typename> typename Traits, backend_finder FindBackend = stacktrace::find_backend>
413 Traits<typename FindBackend::type> find_traits_impl([[maybe_unused]] priority_tag<0u>);
414#endif
415 // ReSharper restore CppFunctionIsNotImplemented
416
417 template <template <typename> typename Traits>
418 auto find_traits()
419 {
420 return find_traits_impl<Traits>(priority_tag<1u>{});
421 }
422
423 [[nodiscard]]
424 inline std::size_t calc_skip(std::size_t const skip)
425 {
426 std::size_t const baseSkip = settings::stacktrace_base_skip().load();
427 MIMICPP_ASSERT(skip < std::numeric_limits<std::size_t>::max() - 1u, "Skip is too high.");
428 MIMICPP_ASSERT(skip < std::numeric_limits<std::size_t>::max() - baseSkip, "Skip + base-skip is too high.");
429
430 return skip + 1u + baseSkip;
431 }
432
433 struct current_fn
434 {
435 template <typename... Canary, template <typename> typename TraitsTemplate = backend_traits>
436 [[nodiscard]]
437 Stacktrace operator()(std::size_t const skip, std::size_t const max) const
438 {
439 using Traits = decltype(find_traits<TraitsTemplate>());
440
441 return Stacktrace{Traits::current(calc_skip(skip), max)};
442 }
443
444 template <typename... Canary, template <typename> typename TraitsTemplate = backend_traits>
445 [[nodiscard]]
446 Stacktrace operator()(std::size_t const skip) const
447 {
448 using Traits = decltype(find_traits<TraitsTemplate>());
449
450 return Stacktrace{Traits::current(calc_skip(skip))};
451 }
452
453 template <typename... Canary, template <typename> typename TraitsTemplate = backend_traits>
454 [[nodiscard]]
455 Stacktrace operator()() const
456 {
457 using Traits = decltype(find_traits<TraitsTemplate>());
458
459 return Stacktrace{Traits::current(calc_skip(0u))};
460 }
461 };
462}
463
465{
472 [[maybe_unused]]
473 inline constexpr detail::current_fn current{};
474}
475
476namespace mimicpp::util::stacktrace::detail
477{
478 template <print_iterator OutIter>
479 constexpr OutIter print_entry(OutIter out, Stacktrace const& stacktrace, std::size_t const index)
480 {
481 MIMICPP_ASSERT(index < stacktrace.size(), "Index out of bounds.");
482
483 return printing::detail::print_source_location(
484 std::move(out),
485 stacktrace.source_file(index),
486 stacktrace.source_line(index),
487 stacktrace.description(index));
488 }
489}
490
491template <>
492struct mimicpp::printing::detail::state::common_type_printer<mimicpp::util::Stacktrace>
493{
494 template <print_iterator OutIter>
495 static constexpr OutIter print(OutIter out, util::Stacktrace const& stacktrace)
496 {
497 if (stacktrace.empty())
498 {
499 return format::format_to(std::move(out), "empty");
500 }
501
502 for (std::size_t const i : std::views::iota(0u, stacktrace.size()))
503 {
504 out = format::format_to(std::move(out), "#{} ", i);
505 out = util::stacktrace::detail::print_entry(
506 std::move(out),
507 stacktrace,
508 i);
509 out = format::format_to(std::move(out), "\n");
510 }
511
512 return out;
513 }
514};
515
516#if MIMICPP_CONFIG_EXPERIMENTAL_USE_CXX23_STACKTRACE
518 #define MIMICPP_DETAIL_HAS_WORKING_STACKTRACE_BACKEND 1
519#elif MIMICPP_CONFIG_EXPERIMENTAL_USE_BOOST_STACKTRACE
521 #define MIMICPP_DETAIL_HAS_WORKING_STACKTRACE_BACKEND 1
522#elif MIMICPP_CONFIG_EXPERIMENTAL_USE_CPPTRACE
524 #define MIMICPP_DETAIL_HAS_WORKING_STACKTRACE_BACKEND 1
525#else
527{
529 {
531 };
532}
533#endif
534
535#endif
#define MIMICPP_ASSERT(condition, msg)
Definition Config.hpp:51
#define MIMICPP_DETAIL_MODULE_EXPORT
Definition Config.hpp:19
~Stacktrace()=default
Defaulted destructor.
std::size_t source_line(std::size_t const at) const
Queries the underlying stacktrace-backend for the source-line of the selected stacktrace-entry.
Definition Stacktrace.hpp:368
std::string source_file(std::size_t const at) const
Queries the underlying stacktrace-backend for the source-file of the selected stacktrace-entry.
Definition Stacktrace.hpp:357
Stacktrace(Stacktrace &&)=default
Defaulted move-constructor.
Stacktrace()
Default constructor.
Definition Stacktrace.hpp:281
friend bool operator==(Stacktrace const &lhs, Stacktrace const &rhs)
Definition Stacktrace.hpp:374
std::string description(std::size_t const at) const
Queries the underlying stacktrace-backend for the description of the selected stacktrace-entry.
Definition Stacktrace.hpp:346
Stacktrace(Stacktrace const &)=default
Defaulted copy-constructor.
bool empty() const
Queries the underlying stacktrace-backend whether it's empty.
Definition Stacktrace.hpp:335
std::size_t size() const
Queries the underlying stacktrace-backend for its size.
Definition Stacktrace.hpp:325
Stacktrace(Backend backend)
Constructor storing the given stacktrace-backend.
Definition Stacktrace.hpp:294
Stacktrace & operator=(Stacktrace const &)=default
Defaulted copy-assignment-operator.
Stacktrace & operator=(Stacktrace &&)=default
Defaulted move-assignment-operator.
The fallback stacktrace-backend.
Definition Stacktrace.hpp:126
Determines, whether B behaves as a the builtin type bool.
Definition Concepts.hpp:66
Checks whether the given type satisfies the requirements of a stacktrace backend.
Definition Stacktrace.hpp:109
constexpr printing::PrintFn print
Functional object, converting the given object to its textual representation.
Definition Print.hpp:183
std::atomic_size_t & stacktrace_base_skip() noexcept
Controls whether the base stacktrace-skip value.
Definition Settings.hpp:46
constexpr detail::current_fn current
Function object, which generates the current-stacktrace.
Definition Stacktrace.hpp:473
Definition Common.hpp:23
Definition Fwd.hpp:447
Definition Fwd.hpp:445
Definition Call.hpp:24
static constexpr bool empty(NullBackend const &stacktrace) noexcept
Definition Stacktrace.hpp:156
static std::size_t source_line(NullBackend const &stacktrace, std::size_t const at)
Definition Stacktrace.hpp:172
static constexpr NullBackend current(std::size_t const skip) noexcept
Definition Stacktrace.hpp:144
static constexpr NullBackend current(std::size_t const skip, std::size_t const max) noexcept
Definition Stacktrace.hpp:138
static std::string source_file(NullBackend const &stacktrace, std::size_t const at)
Definition Stacktrace.hpp:166
static std::string description(NullBackend const &stacktrace, std::size_t const at)
Definition Stacktrace.hpp:161
static constexpr std::size_t size(NullBackend const &stacktrace) noexcept
Definition Stacktrace.hpp:150
Trait type for stacktrace backends.
Definition Fwd.hpp:453
Definition Stacktrace.hpp:529
NullBackend type
Definition Stacktrace.hpp:530