mimic++ v6
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_STACKTRACE_HPP
7#define MIMICPP_STACKTRACE_HPP
8
9#pragma once
10
11#include "mimic++/Fwd.hpp"
12#include "mimic++/Printer.hpp"
13#include "mimic++/Utility.hpp"
14
15#include <algorithm>
16#include <any>
17// ReSharper disable once CppUnusedIncludeDirective
18#include <functional> // std::invoke
19#include <ranges>
20#include <stdexcept>
21#include <type_traits>
22#include <utility>
23#include <variant>
24
25namespace mimicpp::custom
26{
32 struct find_stacktrace_backend;
33}
34
35namespace mimicpp::stacktrace
36{
85
90 struct find_backend;
91
96 template <typename Backend>
98
103 template <typename T>
104 concept backend =
105 std::copyable<T>
106 && requires(backend_traits<std::remove_cvref_t<T>> traits, const std::remove_cvref_t<T>& backend, const std::size_t value) {
107 { decltype(traits)::current(value) } -> std::convertible_to<std::remove_cvref_t<T>>;
108 { decltype(traits)::size(backend) } -> std::convertible_to<std::size_t>;
109 { decltype(traits)::empty(backend) } -> std::convertible_to<bool>;
110 { decltype(traits)::description(backend, value) } -> std::convertible_to<std::string>;
111 { decltype(traits)::source_file(backend, value) } -> std::convertible_to<std::string>;
112 { decltype(traits)::source_line(backend, value) } -> std::convertible_to<std::size_t>;
113 };
114
118}
119
120namespace mimicpp::stacktrace::detail
121{
122 template <typename Backend>
123 [[nodiscard]]
124 std::size_t size(const std::any& backend)
125 {
126 return backend_traits<Backend>::size(
127 std::any_cast<const Backend&>(backend));
128 }
129
130 template <typename Backend>
131 [[nodiscard]]
132 bool empty(const std::any& backend)
133 {
134 return backend_traits<Backend>::empty(
135 std::any_cast<const Backend&>(backend));
136 }
137
138 template <typename Backend>
139 [[nodiscard]]
140 std::string description(const std::any& backend, const std::size_t index)
141 {
142 return backend_traits<Backend>::description(
143 std::any_cast<const Backend&>(backend),
144 index);
145 }
146
147 template <typename Backend>
148 [[nodiscard]]
149 std::string source_file(const std::any& backend, const std::size_t index)
150 {
151 return backend_traits<Backend>::source_file(
152 std::any_cast<const Backend&>(backend),
153 index);
154 }
155
156 template <typename Backend>
157 [[nodiscard]]
158 std::size_t source_line(const std::any& backend, const std::size_t index)
159 {
160 return backend_traits<Backend>::source_line(
161 std::any_cast<const Backend&>(backend),
162 index);
163 }
164}
165
166namespace mimicpp
167{
173 {
174 public:
175 using size_fn = std::size_t (*)(const std::any&);
176 using empty_fn = bool (*)(const std::any&);
177 using description_fn = std::string (*)(const std::any&, std::size_t);
178 using source_file_fn = std::string (*)(const std::any&, std::size_t);
179 using source_line_fn = std::size_t (*)(const std::any&, std::size_t);
180
184 ~Stacktrace() = default;
185
192 template <typename Inner>
193 requires(!std::same_as<Stacktrace, std::remove_cvref_t<Inner>>)
195 [[nodiscard]]
196 explicit constexpr Stacktrace(Inner&& inner)
197 : m_Inner{std::forward<Inner>(inner)},
198 m_SizeFn{&stacktrace::detail::size<std::remove_cvref_t<Inner>>},
199 m_EmptyFn{&stacktrace::detail::empty<std::remove_cvref_t<Inner>>},
200 m_DescriptionFn{&stacktrace::detail::description<std::remove_cvref_t<Inner>>},
201 m_SourceFileFn{&stacktrace::detail::source_file<std::remove_cvref_t<Inner>>},
202 m_SourceLineFn{&stacktrace::detail::source_line<std::remove_cvref_t<Inner>>}
203 {
204 }
205
209 [[nodiscard]]
210 Stacktrace(const Stacktrace&) = default;
211
215 Stacktrace& operator=(const Stacktrace&) = default;
216
220 [[nodiscard]]
221 Stacktrace(Stacktrace&&) = default;
222
227
232 [[nodiscard]]
233 constexpr std::size_t size() const
234 {
235 return std::invoke(m_SizeFn, m_Inner);
236 }
237
242 [[nodiscard]]
243 constexpr bool empty() const
244 {
245 return std::invoke(m_EmptyFn, m_Inner);
246 }
247
253 [[nodiscard]]
254 constexpr std::string description(const std::size_t at) const
255 {
256 return std::invoke(m_DescriptionFn, m_Inner, at);
257 }
258
264 [[nodiscard]]
265 constexpr std::string source_file(const std::size_t at) const
266 {
267 return std::invoke(m_SourceFileFn, m_Inner, at);
268 }
269
275 [[nodiscard]]
276 constexpr std::size_t source_line(const std::size_t at) const
277 {
278 return std::invoke(m_SourceLineFn, m_Inner, at);
279 }
280
281 [[nodiscard]]
282 friend bool operator==(const Stacktrace& lhs, const Stacktrace& rhs)
283 {
284 return lhs.size() == rhs.size()
285 && std::ranges::all_of(
286 std::views::iota(0u, lhs.size()),
287 [&](const std::size_t index) {
288 return lhs.description(index) == rhs.description(index)
289 && lhs.source_file(index) == rhs.source_file(index)
290 && lhs.source_line(index) == rhs.source_line(index);
291 });
292 }
293
294 private:
295 std::any m_Inner;
296 size_fn m_SizeFn;
297 empty_fn m_EmptyFn;
298 description_fn m_DescriptionFn;
299 source_file_fn m_SourceFileFn;
300 source_line_fn m_SourceLineFn;
301 };
302}
303
304namespace mimicpp::stacktrace::detail::current_hook
305{
306 template <typename FindBackend, template <typename> typename Traits>
307 concept existing_backend = requires {
308 {
309 Traits<
310 typename FindBackend::type>::current(std::size_t{})
311 } -> backend;
312 };
313
314 template <
315 template <typename> typename Traits,
316 existing_backend<Traits> FindBackendT = custom::find_stacktrace_backend>
317 [[nodiscard]]
318 constexpr auto current([[maybe_unused]] const priority_tag<2>, const std::size_t skip)
319 {
320 return Traits<
321 typename FindBackendT::type>::current(skip + 1u);
322 }
323
324 template <
325 template <typename> typename Traits,
326 existing_backend<Traits> FindBackendT = find_backend>
327 [[nodiscard]]
328 constexpr auto current([[maybe_unused]] const priority_tag<1>, const std::size_t skip)
329 {
330 return Traits<
331 typename FindBackendT::type>::current(skip + 1u);
332 }
333
334 template <template <typename> typename Traits>
335 constexpr auto current([[maybe_unused]] const priority_tag<0>, [[maybe_unused]] const std::size_t skip)
336 {
337 static_assert(
338 always_false<Traits<void>>{},
339 "mimic++ does not have a registered stacktrace-backend.");
340 }
341
342 constexpr priority_tag<2> maxTag;
343
344 struct current_fn
345 {
346 template <typename... Canary, template <typename> typename Traits = backend_traits>
347 [[nodiscard]]
348 Stacktrace operator()(const std::size_t skip) const
349 {
350 return Stacktrace{
351 current_hook::current<Traits>(maxTag, skip + 1u)};
352 }
353
354 template <typename... Canary, template <typename> typename Traits = backend_traits>
355 [[nodiscard]]
356 Stacktrace operator()() const
357 {
358 return Stacktrace{
359 current_hook::current<Traits>(maxTag, 1u)};
360 }
361 };
362}
363
364namespace mimicpp::stacktrace
365{
366
373 [[maybe_unused]]
374 constexpr detail::current_hook::current_fn current{};
375}
376
377template <>
378class mimicpp::detail::Printer<mimicpp::Stacktrace>
379{
380public:
381 template <print_iterator OutIter>
382 static OutIter print(OutIter out, const Stacktrace& stacktrace)
383 {
384 if (stacktrace.empty())
385 {
386 return format::format_to(
387 std::move(out),
388 "empty");
389 }
390
391 for (const std::size_t i : std::views::iota(0u, stacktrace.size()))
392 {
393 out = format::format_to(
394 std::move(out),
395 "{} [{}], {}\n",
396 stacktrace.source_file(i),
397 stacktrace.source_line(i),
398 stacktrace.description(i));
399 }
400
401 return out;
402 }
403};
404
405namespace mimicpp::stacktrace
406{
412 {
413 };
414}
415
416template <>
418{
419 [[nodiscard]]
420 static NullBackend current([[maybe_unused]] const std::size_t skip) noexcept
421 {
422 return NullBackend{};
423 }
424
425 [[nodiscard]]
426 static constexpr std::size_t size([[maybe_unused]] const NullBackend& backend) noexcept
427 {
428 return 0u;
429 }
430
431 [[nodiscard]]
432 static constexpr bool empty([[maybe_unused]] const NullBackend& backend) noexcept
433 {
434 return true;
435 }
436
437 static std::string description([[maybe_unused]] const NullBackend& backend, [[maybe_unused]] const std::size_t at)
438 {
439 raise_unsupported_operation();
440 }
441
442 static std::string source_file([[maybe_unused]] const NullBackend& backend, [[maybe_unused]] const std::size_t at)
443 {
444 raise_unsupported_operation();
445 }
446
447 [[nodiscard]]
448 static std::size_t source_line([[maybe_unused]] const NullBackend& backend, [[maybe_unused]] const std::size_t at)
449 {
450 raise_unsupported_operation();
451 }
452
453private:
454 [[noreturn]]
455 static void raise_unsupported_operation()
456 {
457 throw std::runtime_error{"stacktrace::NullBackend doesn't support this operation."};
458 }
459};
460
461static_assert(
463 "stacktrace::NullBackend does not satisfy the stacktrace::backend concept");
464
465#if defined(MIMICPP_CONFIG_EXPERIMENTAL_STACKTRACE) \
466 && not defined(NDEBUG)
467
468 #ifdef MIMICPP_CONFIG_EXPERIMENTAL_USE_CPPTRACE
469
470 #if __has_include(<cpptrace/basic.hpp>)
471 #include <cpptrace/basic.hpp>
472 #elif __has_include(<cpptrace/cpptrace.hpp>)
473 // this is necessary for old cpptrace versions.
474 // see: https://github.com/jeremy-rifkin/libassert/issues/110
475 #include <cpptrace/cpptrace.hpp>
476 #else
477 #error "The cpptrace stacktrace backend is explicitly enabled, but the the required include-file can not be found."
478 #endif
479
480namespace mimicpp::stacktrace
481{
482 class CpptraceBackend
483 {
484 public:
485 ~CpptraceBackend() = default;
486
487 [[nodiscard]]
488 explicit CpptraceBackend(cpptrace::raw_trace&& trace) noexcept
489 : m_Trace{std::move(trace)}
490 {
491 }
492
493 CpptraceBackend(const CpptraceBackend&) = default;
494 CpptraceBackend& operator=(const CpptraceBackend&) = default;
495 CpptraceBackend(CpptraceBackend&&) = default;
496 CpptraceBackend& operator=(CpptraceBackend&&) = default;
497
498 [[nodiscard]]
499 const cpptrace::stacktrace& data() const
500 {
501 if (const auto* raw = std::get_if<cpptrace::raw_trace>(&m_Trace))
502 {
503 m_Trace = raw->resolve();
504 }
505
506 return std::get<cpptrace::stacktrace>(m_Trace);
507 }
508
509 private:
510 using TraceT = std::variant<cpptrace::raw_trace, cpptrace::stacktrace>;
511 mutable TraceT m_Trace;
512 };
513}
514
516{
517 using type = CpptraceBackend;
518};
519
520template <>
521struct mimicpp::stacktrace::backend_traits<mimicpp::stacktrace::CpptraceBackend>
522{
523 [[nodiscard]]
524 static CpptraceBackend current(const std::size_t skip)
525 {
526 return CpptraceBackend{cpptrace::generate_raw_trace(skip + 1)};
527 }
528
529 [[nodiscard]]
530 static std::size_t size(const CpptraceBackend& backend)
531 {
532 return backend.data().frames.size();
533 }
534
535 [[nodiscard]]
536 static bool empty(const CpptraceBackend& backend)
537 {
538 return backend.data().empty();
539 }
540
541 [[nodiscard]]
542 static std::string description(const CpptraceBackend& backend, const std::size_t at)
543 {
544 return frame(backend, at).symbol;
545 }
546
547 [[nodiscard]]
548 static std::string source_file(const CpptraceBackend& backend, const std::size_t at)
549 {
550 return frame(backend, at).filename;
551 }
552
553 [[nodiscard]]
554 static std::size_t source_line(const CpptraceBackend& backend, const std::size_t at)
555 {
556 return frame(backend, at).line.value_or(0u);
557 }
558
559 [[nodiscard]]
560 static const cpptrace::stacktrace_frame& frame(const CpptraceBackend& backend, const std::size_t at)
561 {
562 return backend.data().frames.at(at);
563 }
564};
565
566static_assert(
568 "stacktrace::CpptraceBackend does not satisfy the stacktrace::backend concept");
569
570 #define MIMICPP_DETAIL_WORKING_STACKTRACE_BACKEND
571
572 #elif defined(__cpp_lib_stacktrace)
573
574 #include <stacktrace>
575
577{
578 using type = std::stacktrace;
579};
580
581template <typename Allocator>
582struct mimicpp::stacktrace::backend_traits<std::basic_stacktrace<Allocator>>
583{
584 using BackendT = std::basic_stacktrace<Allocator>;
585
586 [[nodiscard]]
587 static BackendT current(const std::size_t skip)
588 {
589 return BackendT::current(skip + 1);
590 }
591
592 [[nodiscard]]
593 static std::size_t size(const BackendT& backend)
594 {
595 return backend.size();
596 }
597
598 [[nodiscard]]
599 static bool empty(const BackendT& backend)
600 {
601 return backend.empty();
602 }
603
604 [[nodiscard]]
605 static std::string description(const BackendT& backend, const std::size_t at)
606 {
607 return entry(backend, at).description();
608 }
609
610 [[nodiscard]]
611 static std::string source_file(const BackendT& backend, const std::size_t at)
612 {
613 return entry(backend, at).source_file();
614 }
615
616 [[nodiscard]]
617 static std::size_t source_line(const BackendT& backend, const std::size_t at)
618 {
619 return entry(backend, at).source_line();
620 }
621
622 [[nodiscard]]
623 static const std::stacktrace_entry& entry(const BackendT& backend, const std::size_t at)
624 {
625 return backend.at(at);
626 }
627};
628
629static_assert(
631 "std::stacktrace does not satisfy the stacktrace::backend concept");
632
633 #define MIMICPP_DETAIL_WORKING_STACKTRACE_BACKEND
634
635 #else
636
637 // neither backend is available, but maybe a custom type?
638 // so, we should not emit an error here.
639
640 #endif
641
642#endif
643
644// This is enabled as fallback solution, when neither std::stacktrace nor cpptrace is available,
645// or MIMICPP_CONFIG_EXPERIMENTAL_STACKTRACE simply not defined.
646#ifndef MIMICPP_DETAIL_WORKING_STACKTRACE_BACKEND
647
652
653#endif
654
655#endif
Stacktrace(const Stacktrace &)=default
Defaulted move-constructor.
friend bool operator==(const Stacktrace &lhs, const Stacktrace &rhs)
Definition Stacktrace.hpp:282
Stacktrace(Stacktrace &&)=default
Defaulted copy-constructor.
constexpr Stacktrace(Inner &&inner)
Constructor storing the given stacktrace-backend type-erased.
Definition Stacktrace.hpp:196
~Stacktrace()=default
Defaulted destructor.
std::size_t(*)(const std::any &) size_fn
Definition Stacktrace.hpp:175
constexpr std::string description(const std::size_t at) const
Queries the underlying stacktrace-backend for the description of the selected stacktrace-entry.
Definition Stacktrace.hpp:254
std::string(*)(const std::any &, std::size_t) description_fn
Definition Stacktrace.hpp:177
constexpr std::size_t size() const
Queries the underlying stacktrace-backend for its size.
Definition Stacktrace.hpp:233
Stacktrace & operator=(const Stacktrace &)=default
Defaulted move-assignment-operator.
std::string(*)(const std::any &, std::size_t) source_file_fn
Definition Stacktrace.hpp:178
constexpr bool empty() const
Queries the underlying stacktrace-backend whether its empty.
Definition Stacktrace.hpp:243
Stacktrace & operator=(Stacktrace &&)=default
Defaulted copy-assignment-operator.
constexpr std::size_t source_line(const std::size_t at) const
Queries the underlying stacktrace-backend for the source-line of the selected stacktrace-entry.
Definition Stacktrace.hpp:276
constexpr std::string source_file(const std::size_t at) const
Queries the underlying stacktrace-backend for the source-file of the selected stacktrace-entry.
Definition Stacktrace.hpp:265
std::size_t(*)(const std::any &, std::size_t) source_line_fn
Definition Stacktrace.hpp:179
bool(*)(const std::any &) empty_fn
Definition Stacktrace.hpp:176
The fallback stacktrace-backend.
Definition Stacktrace.hpp:412
Checks whether the given type satisfies the requirements of a stacktrace backend.
Definition Stacktrace.hpp:104
constexpr detail::current_hook::current_fn current
Function object, which generates the current-stacktrace.
Definition Stacktrace.hpp:374
constexpr detail::PrintFn print
Functional object, converting the given object to its textual representation.
Definition Printer.hpp:590
Definition Common.hpp:19
Definition Fwd.hpp:387
Definition BoostTest.hpp:20
Trait type for stacktrace backends.
Definition Stacktrace.hpp:97
static std::string description(const NullBackend &backend, const std::size_t at)
Definition Stacktrace.hpp:437
static constexpr bool empty(const NullBackend &backend) noexcept
Definition Stacktrace.hpp:432
static constexpr std::size_t size(const NullBackend &backend) noexcept
Definition Stacktrace.hpp:426
static std::string source_file(const NullBackend &backend, const std::size_t at)
Definition Stacktrace.hpp:442
static NullBackend current(const std::size_t skip) noexcept
Definition Stacktrace.hpp:420
static std::size_t source_line(const NullBackend &backend, const std::size_t at)
Definition Stacktrace.hpp:448
Definition Stacktrace.hpp:649
NullBackend type
Definition Stacktrace.hpp:650