diff --git a/kotlin-native/runtime/src/main/cpp/Natives.cpp b/kotlin-native/runtime/src/main/cpp/Natives.cpp index 44caaf60e3b..3d5616d7d6c 100644 --- a/kotlin-native/runtime/src/main/cpp/Natives.cpp +++ b/kotlin-native/runtime/src/main/cpp/Natives.cpp @@ -42,12 +42,12 @@ KInt Kotlin_Any_hashCode(KConstRef thiz) { } NO_INLINE OBJ_GETTER0(Kotlin_getCurrentStackTrace) { - KStdVector stackTrace; + kotlin::StackTrace stackTrace; { // Don't use `kotlin::CallWithThreadState` to avoid messing up callstack. kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative); // Skip this function and primary `Throwable` constructor. - stackTrace = kotlin::GetCurrentStackTrace(2); + stackTrace = kotlin::StackTrace<>::current(2); } ObjHolder resultHolder; @@ -61,7 +61,7 @@ NO_INLINE OBJ_GETTER0(Kotlin_getCurrentStackTrace) { OBJ_GETTER(Kotlin_getStackTraceStrings, KConstRef stackTrace) { const KNativePtr* array = PrimitiveArrayAddressOfElementAt(stackTrace->array(), 0); size_t size = stackTrace->array()->count_; - auto stackTraceStrings = kotlin::CallWithThreadState(kotlin::GetStackTraceStrings, array, size); + auto stackTraceStrings = kotlin::CallWithThreadState(kotlin::GetStackTraceStrings, kotlin::std_support::span(array, size)); ObjHolder resultHolder; ObjHeader* strings = AllocArrayInstance(theArrayTypeInfo, stackTraceStrings.size(), resultHolder.slot()); diff --git a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp index fc18ed80a87..2d997056375 100644 --- a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp +++ b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp @@ -15,6 +15,8 @@ #include #endif +#include + #include "Common.h" #include "ExecFormat.h" #include "Porting.h" @@ -29,22 +31,30 @@ namespace { #if USE_GCC_UNWIND struct Backtrace { - Backtrace(int count, int skip) : skipCount(skip) { - uint32_t size = count - skipCount; - if (size < 0) { - size = 0; - } - array.reserve(size); + Backtrace(size_t skip, std_support::span buffer): currentSize(0), skipCount(skip), buffer(buffer) {} + + void setNextElement(_Unwind_Ptr element) { + RuntimeAssert(currentSize < buffer.size(), "Buffer overflow"); + buffer[currentSize++] = reinterpret_cast(element); } - void setNextElement(_Unwind_Ptr element) { array.push_back(reinterpret_cast(element)); } + bool full() const { return currentSize >= buffer.size(); } - int skipCount; - KStdVector array; + size_t currentSize; + size_t skipCount; + std_support::span buffer; }; +_Unwind_Ptr getUnwindPtr(_Unwind_Context* context) { +#if (__MINGW32__ || __MINGW64__) + return _Unwind_GetRegionStart(context); +#else + return _Unwind_GetIP(context); +#endif +} + _Unwind_Reason_Code depthCountCallback(struct _Unwind_Context* context, void* arg) { - int* result = reinterpret_cast(arg); + size_t* result = reinterpret_cast(arg); (*result)++; return _URC_NO_REASON; } @@ -56,11 +66,12 @@ _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { return _URC_NO_REASON; } -#if (__MINGW32__ || __MINGW64__) - _Unwind_Ptr address = _Unwind_GetRegionStart(context); -#else - _Unwind_Ptr address = _Unwind_GetIP(context); -#endif + // If the buffer is full, skip the remaining frames. + if (backtrace->full()) { + return _URC_NO_REASON; + } + + _Unwind_Ptr address = getUnwindPtr(context); backtrace->setNextElement(address); return _URC_NO_REASON; @@ -79,34 +90,76 @@ int getSourceInfo(void* symbol, SourceInfo *result, int result_len) { // TODO: this implementation is just a hack, e.g. the result is inexact; // however it is better to have an inexact stacktrace than not to have any. -NO_INLINE KStdVector kotlin::GetCurrentStackTrace(int extraSkipFrames) noexcept { +NO_INLINE KStdVector kotlin::internal::GetCurrentStackTrace(size_t skipFrames) noexcept { #if KONAN_NO_BACKTRACE return {}; #else - // Skips this function frame + anything asked by the caller. - const int kSkipFrames = 1 + extraSkipFrames; -#if USE_GCC_UNWIND - int depth = 0; - _Unwind_Backtrace(depthCountCallback, static_cast(&depth)); - Backtrace result(depth, kSkipFrames); - if (result.array.capacity() > 0) { - _Unwind_Backtrace(unwindCallback, static_cast(&result)); - } - return std::move(result.array); -#else - const int maxSize = 32; - void* buffer[maxSize]; - int size = backtrace(buffer, maxSize); - if (size < kSkipFrames) return {}; +#if (__MINGW32__ || __MINGW64__) + // Skip GetCurrentStackTrace, _Unwind_Backtrace + anything asked by the caller. + const size_t kSkipFrames = 2 + skipFrames; +#else + // Skip GetCurrentStackTrace + anything asked by the caller. + const size_t kSkipFrames = 1 + skipFrames; +#endif KStdVector result; - result.reserve(size - kSkipFrames); - for (int index = kSkipFrames; index < size; ++index) { - result.push_back(buffer[index]); - } +#if USE_GCC_UNWIND + size_t depth = 0; + _Unwind_Backtrace(depthCountCallback, static_cast(&depth)); + if (depth <= kSkipFrames) return {}; + result.resize(depth - kSkipFrames); + + Backtrace traceHolder(kSkipFrames, std_support::span(result.data(), result.size())); + _Unwind_Backtrace(unwindCallback, static_cast(&traceHolder)); + RuntimeAssert(result.size() == traceHolder.currentSize, "Expected and collected sizes of the stacktrace differ"); + return result; +#else + // Take into account this function and StackTrace::current. + constexpr size_t maxSize = GetMaxStackTraceDepth() + 2; + result.resize(maxSize); + auto size = static_cast(backtrace(result.data(), static_cast(result.size()))); + if (size <= kSkipFrames) return {}; + result.resize(size); + + // Drop first kSkipFrames elements. + result.erase(result.begin(), std::next(result.begin(), kSkipFrames)); + return result; +#endif // !USE_GCC_UNWIND +#endif // !KONAN_NO_BACKTRACE +} + +// TODO: this implementation is just a hack, e.g. the result is inexact; +// however it is better to have an inexact stacktrace than not to have any. +NO_INLINE size_t kotlin::internal::GetCurrentStackTrace(size_t skipFrames, std_support::span buffer) noexcept { +#if KONAN_NO_BACKTRACE + return {}; +#else + +#if (__MINGW32__ || __MINGW64__) + // Skip GetCurrentStackTrace, _Unwind_Backtrace + anything asked by the caller. + const size_t kSkipFrames = 2 + skipFrames; +#else + // Skip GetCurrentStackTrace + anything asked by the caller. + const size_t kSkipFrames = 1 + skipFrames; #endif + +#if USE_GCC_UNWIND + Backtrace traceHolder(kSkipFrames, buffer); + _Unwind_Backtrace(unwindCallback, static_cast(&traceHolder)); + return traceHolder.currentSize; +#else + // Take into account this function and StackTrace::current. + constexpr size_t maxSize = GetMaxStackTraceDepth() + 2; + void* tmpBuffer[maxSize]; + size_t size = backtrace(tmpBuffer, static_cast(maxSize)); + if (size <= kSkipFrames) return 0; + + size_t elementsCount = std::min(buffer.size(), size - kSkipFrames); + std::copy_n(std::begin(tmpBuffer) + kSkipFrames, elementsCount, std::begin(buffer)); + return elementsCount; +#endif // !USE_GCC_UNWIND #endif // !KONAN_NO_BACKTRACE } @@ -183,17 +236,18 @@ KNativePtr adjustAddressForSourceInfo(KNativePtr address) { KNativePtr adjustAddressForSourceInfo(KNativePtr address) { return address; } #endif -KStdVector kotlin::GetStackTraceStrings(void* const* stackTrace, size_t stackTraceSize) noexcept { +KStdVector kotlin::GetStackTraceStrings(std_support::span stackTrace) noexcept { #if KONAN_NO_BACKTRACE KStdVector strings; strings.push_back(""); return strings; #else + size_t size = stackTrace.size(); KStdVector strings; - strings.reserve(stackTraceSize); - if (stackTraceSize > 0) { + strings.reserve(size); + if (size > 0) { SourceInfo buffer[10]; // outside of the loop to avoid calling constructors and destructors each time - for (size_t index = 0; index < stackTraceSize; ++index) { + for (size_t index = 0; index < size; ++index) { KNativePtr address = stackTrace[index]; if (!address || reinterpret_cast(address) == 1) continue; address = adjustAddressForSourceInfo(address); @@ -246,17 +300,10 @@ NO_INLINE void kotlin::PrintStackTraceStderr() { // NOTE: This might be called from both runnable and native states (including in uninitialized runtime) // TODO: This is intended for runtime use. Try to avoid memory allocations and signal unsafe functions. - // TODO: This might have to go into `GetCurrentStackTrace`, but this changes the generated stacktrace for - // `Throwable`. -#if KONAN_WINDOWS - // Skip this function and `_Unwind_Backtrace`. - constexpr int kSkipFrames = 2; -#else // Skip this function. constexpr int kSkipFrames = 1; -#endif - auto stackTrace = GetCurrentStackTrace(kSkipFrames); - auto stackTraceStrings = GetStackTraceStrings(stackTrace.data(), stackTrace.size()); + StackTrace trace = StackTrace<>::current(kSkipFrames); + auto stackTraceStrings = GetStackTraceStrings(trace.data()); for (auto& frame : stackTraceStrings) { konan::consoleErrorUtf8(frame.c_str(), frame.size()); konan::consoleErrorf("\n"); diff --git a/kotlin-native/runtime/src/main/cpp/StackTrace.hpp b/kotlin-native/runtime/src/main/cpp/StackTrace.hpp index 973579687bc..380cf699ec8 100644 --- a/kotlin-native/runtime/src/main/cpp/StackTrace.hpp +++ b/kotlin-native/runtime/src/main/cpp/StackTrace.hpp @@ -3,18 +3,143 @@ * that can be found in the LICENSE file. */ +#ifndef RUNTIME_STACK_TRACE_H +#define RUNTIME_STACK_TRACE_H + +#include "cpp_support/Span.hpp" #include "Memory.h" #include "Types.h" namespace kotlin { -// TODO: Instead of KStd* provide allocator-customizable versions, to allow stack memory allocation. -// TODO: Model API as in upcoming https://en.cppreference.com/w/cpp/utility/basic_stacktrace +namespace internal { -KStdVector GetCurrentStackTrace(int extraSkipFrames) noexcept; +NO_INLINE KStdVector GetCurrentStackTrace(size_t skipFrames) noexcept; +NO_INLINE size_t GetCurrentStackTrace(size_t skipFrames, std_support::span buffer) noexcept; -// TODO: This is asking for a span. -KStdVector GetStackTraceStrings(void* const* stackTrace, size_t stackTraceSize) noexcept; +enum class StackTraceCapacityKind { + kFixed, kDynamic +}; + +template +constexpr size_t GetMaxStackTraceDepth() noexcept { +#if KONAN_NO_BACKTRACE + return 0; +#elif USE_GCC_UNWIND + return std::numeric_limits::max(); +#else + switch (kind) { + case StackTraceCapacityKind::kFixed: + return 32; + case StackTraceCapacityKind::kDynamic: + return 128; + } +#endif +} + +} + +static constexpr size_t kDynamicCapacity = std::numeric_limits::max(); + +template +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 data() const noexcept { + return std_support::span(buffer_.data(), size()); + } + + // 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(), Capacity); + + NO_INLINE static StackTrace current(size_t skipFrames = 0) noexcept { + StackTrace result; + result.size_ = internal::GetCurrentStackTrace( + skipFrames + 1, std_support::span(result.buffer_.data(), result.buffer_.size())); + return result; + } + +private: + size_t size_; + std::array buffer_; +}; + +template<> +class StackTrace { +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 data() const noexcept { + return std_support::span(buffer_.data(), size()); + } + + // 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(); + + NO_INLINE static StackTrace current(size_t skipFrames = 0) { + StackTrace result; + result.buffer_ = internal::GetCurrentStackTrace(skipFrames + 1); + return result; + } + +private: + KStdVector buffer_; +}; + + +KStdVector GetStackTraceStrings(std_support::span stackTrace) noexcept; // It's not always safe to extract SourceInfo during unhandled exception termination. void DisallowSourceInfo(); @@ -22,3 +147,5 @@ void DisallowSourceInfo(); void PrintStackTraceStderr(); } // namespace kotlin + +#endif // RUNTIME_STACK_TRACE_H \ No newline at end of file diff --git a/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp b/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp index 69ad1ec9acd..1daa6cbcb93 100644 --- a/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp @@ -14,16 +14,35 @@ #include "Porting.h" #include "TestSupport.hpp" +#include + using namespace kotlin; namespace { -NO_INLINE KStdVector GetStackTrace1(int skipFrames) { - return GetCurrentStackTrace(skipFrames); +template +NO_INLINE StackTrace GetStackTrace1(size_t skipFrames = 0) { + return StackTrace::current(skipFrames); } -NO_INLINE KStdVector GetStackTrace2(int skipFrames) { - return GetStackTrace1(skipFrames); +template +NO_INLINE StackTrace GetStackTrace2(size_t skipFrames = 0) { + return GetStackTrace1(skipFrames); +} + +template +NO_INLINE StackTrace GetStackTrace3(size_t skipFrames = 0) { + return GetStackTrace2(skipFrames); +} + +// Disable optimizations for these functions to avoid inlining and tail recursion optimization. +template +[[clang::optnone]] StackTrace GetDeepStackTrace(size_t depth) { + if (depth <= 1) { + return StackTrace::current(); + } else { + return GetDeepStackTrace(depth - 1); + } } NO_INLINE void AbortWithStackTrace(int) { @@ -34,29 +53,188 @@ NO_INLINE void AbortWithStackTrace(int) { } // namespace TEST(StackTraceTest, StackTrace) { - // TODO: Consider incorporating extra skipping to `GetCurrentStackTrace` on windows. -#if KONAN_WINDOWS - constexpr int kSkip = 1; -#else - constexpr int kSkip = 0; -#endif - auto stackTrace = GetStackTrace2(kSkip); - auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data(), stackTrace.size()); - ASSERT_GT(symbolicStackTrace.size(), 0ul); + auto stackTrace = GetStackTrace3(); + auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data()); + ASSERT_GT(symbolicStackTrace.size(), 1ul); EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1")); + EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2")); } TEST(StackTraceTest, StackTraceWithSkip) { - // TODO: Consider incorporating extra skipping to `GetCurrentStackTrace` on windows. -#if KONAN_WINDOWS - constexpr int kSkip = 2; -#else constexpr int kSkip = 1; -#endif - auto stackTrace = GetStackTrace2(kSkip); - auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data(), stackTrace.size()); - ASSERT_GT(symbolicStackTrace.size(), 0ul); + auto stackTrace = GetStackTrace3(kSkip); + auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data()); + ASSERT_GT(symbolicStackTrace.size(), 1ul); EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace2")); + EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace3")); +} + +TEST(StackTraceTest, StackAllocatedTrace) { + auto stackTrace = GetStackTrace3<2>(); + auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data()); + ASSERT_EQ(symbolicStackTrace.size(), 2ul); + EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1")); + EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2")); +} + +TEST(StackTraceTest, StackAllocatedTraceWithSkip) { + constexpr int kSkip = 1; + auto stackTrace = GetStackTrace3<2>(kSkip); + auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data()); + ASSERT_EQ(symbolicStackTrace.size(), 2ul); + EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace2")); + EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace3")); +} + +TEST(StackTraceTest, EmptyStackTrace) { + constexpr size_t kSkip = 1000000; + auto stackTrace = GetStackTrace1(kSkip); + EXPECT_EQ(stackTrace.size(), 0ul); + auto data = stackTrace.data(); + EXPECT_EQ(data.size(), 0ul); + auto symbolicStackTrace = GetStackTraceStrings(data); + EXPECT_EQ(stackTrace.size(), 0ul); + EXPECT_EQ(symbolicStackTrace.size(), 0ul); +} + +TEST(StackTraceTest, StackAllocatedEmptyTrace) { + constexpr size_t kSkip = 1000000; + auto stackTrace = GetStackTrace1<1>(kSkip); + EXPECT_EQ(stackTrace.size(), 0ul); + auto data = stackTrace.data(); + EXPECT_EQ(data.size(), 0ul); + auto symbolicStackTrace = GetStackTraceStrings(data); + EXPECT_EQ(stackTrace.size(), 0ul); + EXPECT_EQ(symbolicStackTrace.size(), 0ul); +} + +TEST(StackTraceTest, DeepStackTrace) { + constexpr size_t knownStackDepth = 150; + auto stackTrace = GetDeepStackTrace(knownStackDepth); + auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data()); + +#if USE_GCC_UNWIND + EXPECT_GE(stackTrace.size(), knownStackDepth); + size_t lastKnownIndex = knownStackDepth - 1; +#else + // For platforms where the libc unwind is used (e.g. MacOS) the size of a collected trace is limited (see StackTrace::maxDepth). + EXPECT_EQ(stackTrace.size(), StackTrace<>::maxDepth); + size_t lastKnownIndex = StackTrace<>::maxDepth - 1; +#endif + + ASSERT_GT(symbolicStackTrace.size(), lastKnownIndex); + EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetDeepStackTrace")); + EXPECT_THAT(symbolicStackTrace[lastKnownIndex], testing::HasSubstr("GetDeepStackTrace")); +} + +TEST(StackTraceTest, StackAllocatedDeepTrace) { + constexpr size_t knownStackDepth = 100; + constexpr size_t capacity = 10; + auto stackTrace = GetDeepStackTrace(knownStackDepth); + auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data()); + + EXPECT_EQ(StackTrace::maxDepth, capacity); + EXPECT_EQ(stackTrace.size(), capacity); + + ASSERT_EQ(symbolicStackTrace.size(), capacity); + EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetDeepStackTrace")); + EXPECT_THAT(symbolicStackTrace[capacity - 1], testing::HasSubstr("GetDeepStackTrace")); +} + +TEST(StackTraceTest, StackAllocatedDeepTraceWithEnoughCapacity) { + constexpr size_t knownStackDepth = 100; + constexpr size_t capacity = 150; + auto stackTrace = GetDeepStackTrace(knownStackDepth); + +#if USE_GCC_UNWIND + EXPECT_GE(stackTrace.size(), knownStackDepth); +#else + // For platforms where the libc unwind is used (e.g. MacOS) the size of a collected trace is limited (see StackTrace::maxDepth). + EXPECT_EQ(stackTrace.size(), StackTrace::maxDepth); +#endif +} + +TEST(StackTraceTest, Iteration) { + auto stackTrace = GetStackTrace2(); + + KStdVector actualAddresses; + for (auto addr : stackTrace) { + actualAddresses.push_back(addr); + } + + EXPECT_GT(actualAddresses.size(), 0ul); + EXPECT_EQ(actualAddresses.size(), stackTrace.size()); + + auto symbolicStackTrace = GetStackTraceStrings(std_support::span(actualAddresses.data(), actualAddresses.size())); + EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1")); + EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2")); +} + +TEST(StackTraceTest, StackAllocatedIteration) { + auto stackTrace = GetStackTrace2<2>(); + + KStdVector actualAddresses; + for (auto addr : stackTrace) { + actualAddresses.push_back(addr); + } + + EXPECT_EQ(actualAddresses.size(), 2ul); + EXPECT_EQ(actualAddresses.size(), stackTrace.size()); + + auto symbolicStackTrace = GetStackTraceStrings(std_support::span(actualAddresses.data(), actualAddresses.size())); + EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1")); + EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2")); +} + +TEST(StackTraceTest, IndexedAccess) { + auto stackTrace = GetStackTrace2(); + + KStdVector actualAddresses; + for (size_t i = 0; i < stackTrace.size(); i++) { + actualAddresses.push_back(stackTrace[i]); + } + + EXPECT_GT(actualAddresses.size(), 0ul); + auto symbolicStackTrace = GetStackTraceStrings(std_support::span(actualAddresses.data(), actualAddresses.size())); + EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1")); + EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2")); +} + +TEST(StackTraceTest, StackAllocatedIndexedAccess) { + auto stackTrace = GetStackTrace2<2>(); + + KStdVector actualAddresses; + for (size_t i = 0; i < stackTrace.size(); i++) { + actualAddresses.push_back(stackTrace[i]); + } + + EXPECT_EQ(actualAddresses.size(), 2ul); + auto symbolicStackTrace = GetStackTraceStrings(std_support::span(actualAddresses.data(), actualAddresses.size())); + EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1")); + EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2")); +} + +TEST(StackTraceTest, IndexedAccessAndIteration) { + auto stackTrace = GetStackTrace2(); + + size_t i = 0; + for (auto addr : stackTrace) { + EXPECT_EQ(addr, stackTrace[i]); + i++; + } + EXPECT_EQ(stackTrace.size(), i); +} + +TEST(StackTraceTest, StackAllocatedIndexedAccessAndIteration) { + auto stackTrace = GetStackTrace2<2>(); + + size_t i = 0; + for (auto addr : stackTrace) { + EXPECT_EQ(addr, stackTrace[i]); + i++; + } + EXPECT_EQ(stackTrace.size(), i); + EXPECT_EQ(stackTrace.size(), 2ul); } TEST(StackTraceDeathTest, PrintStackTrace) {