219 lines
6.8 KiB
C++
219 lines
6.8 KiB
C++
/*
|
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
* that can be found in the LICENSE file.
|
|
*/
|
|
|
|
#ifndef RUNTIME_STACK_TRACE_H
|
|
#define RUNTIME_STACK_TRACE_H
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "Common.h"
|
|
#include "Utils.hpp"
|
|
#include "std_support/Span.hpp"
|
|
|
|
namespace kotlin {
|
|
|
|
namespace internal {
|
|
|
|
NO_INLINE std::vector<void*> GetCurrentStackTrace(size_t skipFrames) noexcept;
|
|
NO_INLINE size_t GetCurrentStackTrace(size_t skipFrames, std_support::span<void*> buffer) noexcept;
|
|
|
|
enum class StackTraceCapacityKind {
|
|
kFixed, kDynamic
|
|
};
|
|
|
|
template <StackTraceCapacityKind kind>
|
|
constexpr size_t GetMaxStackTraceDepth() noexcept {
|
|
#if USE_GCC_UNWIND
|
|
return std::numeric_limits<size_t>::max();
|
|
#else
|
|
switch (kind) {
|
|
case StackTraceCapacityKind::kFixed:
|
|
return 32;
|
|
case StackTraceCapacityKind::kDynamic:
|
|
return 128;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
static constexpr size_t kDynamicCapacity = std::numeric_limits<size_t>::max();
|
|
|
|
template <size_t Capacity = kDynamicCapacity>
|
|
class StackTrace {
|
|
public:
|
|
using Iterator = void* const*;
|
|
|
|
StackTrace() noexcept : size_(0), buffer_{nullptr} {};
|
|
StackTrace(const StackTrace& other) = default;
|
|
StackTrace(StackTrace&& other) noexcept = default;
|
|
|
|
StackTrace& operator=(const StackTrace& other) = default;
|
|
StackTrace& operator=(StackTrace&& other) noexcept = default;
|
|
|
|
size_t size() const noexcept {
|
|
return size_;
|
|
}
|
|
|
|
void* operator[](size_t index) const noexcept {
|
|
return buffer_[index];
|
|
}
|
|
|
|
Iterator begin() const noexcept {
|
|
return buffer_.data();
|
|
}
|
|
|
|
Iterator end() const noexcept {
|
|
return buffer_.data() + size();
|
|
}
|
|
|
|
std_support::span<void* const> data() const noexcept {
|
|
return std_support::span<void* const>(buffer_.data(), size());
|
|
}
|
|
|
|
bool operator==(const StackTrace& other) const noexcept {
|
|
return std::equal(begin(), end(), other.begin(), other.end());
|
|
}
|
|
|
|
bool operator!=(const StackTrace& other) const noexcept {
|
|
return !(*this == other);
|
|
}
|
|
|
|
// Maximal stacktrace depth that can be collected due to implementation limitations.
|
|
// Note that this limitation doesn't take into account the skipFrames parameter.
|
|
// I.e. real size of a returned stacktrace will be limited by (maxDepth - skipFrames).
|
|
static constexpr size_t maxDepth =
|
|
std::min(internal::GetMaxStackTraceDepth<internal::StackTraceCapacityKind::kFixed>(), Capacity);
|
|
|
|
NO_INLINE static StackTrace current(size_t skipFrames, size_t depthLimit) {
|
|
StackTrace result;
|
|
auto fullTraceSize = internal::GetCurrentStackTrace(
|
|
skipFrames + 1, std_support::span<void*>(result.buffer_.data(), result.buffer_.size()));
|
|
result.size_ = std::min(fullTraceSize, depthLimit);
|
|
return result;
|
|
}
|
|
|
|
NO_INLINE static StackTrace current(size_t skipFrames = 0) noexcept {
|
|
// Avoid delegating to current(skipFrames, depth)
|
|
// to have the same number of "service" frames for both overloads.
|
|
StackTrace result;
|
|
result.size_ = internal::GetCurrentStackTrace(
|
|
skipFrames + 1, std_support::span<void*>(result.buffer_.data(), result.buffer_.size()));
|
|
return result;
|
|
}
|
|
|
|
struct TestSupport : private Pinned {
|
|
static StackTrace constructFrom(std::initializer_list<void*> values) {
|
|
StackTrace result;
|
|
size_t elementsCount = std::min(values.size(), result.buffer_.size());
|
|
std::copy_n(values.begin(), elementsCount, result.buffer_.begin());
|
|
result.size_ = elementsCount;
|
|
return result;
|
|
}
|
|
};
|
|
|
|
private:
|
|
size_t size_;
|
|
std::array<void*, Capacity> buffer_;
|
|
};
|
|
|
|
template<>
|
|
class StackTrace<kDynamicCapacity> {
|
|
public:
|
|
using Iterator = void* const*;
|
|
|
|
StackTrace() noexcept = default;
|
|
StackTrace(const StackTrace& other) = default;
|
|
StackTrace(StackTrace&& other) noexcept = default;
|
|
|
|
StackTrace& operator=(const StackTrace& other) noexcept = default;
|
|
StackTrace& operator=(StackTrace&& other) noexcept = default;
|
|
|
|
size_t size() const noexcept {
|
|
return buffer_.size();
|
|
}
|
|
|
|
void* operator[](size_t index) const noexcept {
|
|
return buffer_[index];
|
|
}
|
|
|
|
Iterator begin() const noexcept {
|
|
return buffer_.data();
|
|
}
|
|
|
|
Iterator end() const noexcept {
|
|
return buffer_.data() + size();
|
|
}
|
|
|
|
std_support::span<void* const> data() const noexcept {
|
|
return std_support::span<void* const>(buffer_.data(), size());
|
|
}
|
|
|
|
bool operator==(const StackTrace& other) const noexcept {
|
|
return std::equal(begin(), end(), other.begin(), other.end());
|
|
}
|
|
|
|
bool operator!=(const StackTrace& other) const noexcept {
|
|
return !(*this == other);
|
|
}
|
|
|
|
// Maximal stacktrace depth that can be collected due to implementation limitations.
|
|
// Note that this limitation doesn't take into account the skipFrames parameter.
|
|
// I.e. real size of a returned stacktrace will be limited by (maxDepth - skipFrames).
|
|
static constexpr size_t maxDepth = internal::GetMaxStackTraceDepth<internal::StackTraceCapacityKind::kDynamic>();
|
|
|
|
NO_INLINE static StackTrace current(size_t skipFrames, size_t depthLimit) {
|
|
auto traceElements = internal::GetCurrentStackTrace(skipFrames + 1);
|
|
if (traceElements.size() > depthLimit) {
|
|
traceElements.resize(depthLimit);
|
|
}
|
|
return StackTrace(std::move(traceElements));
|
|
}
|
|
|
|
NO_INLINE static StackTrace current(size_t skipFrames = 0) {
|
|
// Avoid delegating to current(skipFrames, depth)
|
|
// to have the same number of "service" frames for both overloads.
|
|
auto traceElements = internal::GetCurrentStackTrace(skipFrames + 1);
|
|
return StackTrace(std::move(traceElements));
|
|
}
|
|
|
|
struct TestSupport : private Pinned {
|
|
static StackTrace constructFrom(std::initializer_list<void*> values) {
|
|
std::vector<void*> traceElements(values);
|
|
return StackTrace(std::move(traceElements));
|
|
}
|
|
};
|
|
|
|
private:
|
|
explicit StackTrace(std::vector<void*>&& buffer) noexcept : buffer_(buffer) {}
|
|
|
|
std::vector<void*> buffer_;
|
|
};
|
|
|
|
std::vector<std::string> GetStackTraceStrings(std_support::span<void* const> stackTrace) noexcept;
|
|
|
|
// It's not always safe to extract SourceInfo during unhandled exception termination.
|
|
void DisallowSourceInfo();
|
|
|
|
void PrintStackTraceStderr();
|
|
|
|
} // namespace kotlin
|
|
|
|
template <size_t Capacity>
|
|
struct std::hash<kotlin::StackTrace<Capacity>> {
|
|
size_t operator()(kotlin::StackTrace<Capacity> value) const {
|
|
size_t result = 0;
|
|
std::hash<void*> hasher;
|
|
for (void* p : value) {
|
|
result += kotlin::CombineHash(result, hasher(p));
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
|
|
#endif // RUNTIME_STACK_TRACE_H
|