├── .github └── workflows │ └── cmake-msvc-build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include ├── CMakeLists.txt └── fox │ └── stacktree.hpp ├── sample ├── CMakeLists.txt └── main.cpp └── test ├── CMakeLists.txt └── stacktree_test.cc /.github/workflows/cmake-msvc-build.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml 3 | name: CMake MSVC Build and Test 4 | 5 | on: 6 | push: 7 | branches: [ "main" ] 8 | pull_request: 9 | branches: [ "main" ] 10 | 11 | env: 12 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 13 | BUILD_TYPE: Release 14 | 15 | jobs: 16 | build: 17 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 18 | # You can convert this to a matrix build if you need cross-platform coverage. 19 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 20 | runs-on: windows-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - name: Configure CMake 26 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 27 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 28 | run: cmake -Dsample=ON -Dtest=ON -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 29 | 30 | - name: Build 31 | # Build your program with the given configuration 32 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 33 | 34 | - name: Test 35 | working-directory: ${{github.workspace}}/build 36 | # Execute tests defined by the CMake configuration. 37 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 38 | run: ctest -C ${{env.BUILD_TYPE}} 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | bin-etc/* 3 | bin-lib/* 4 | bin-game/* 5 | lib/* 6 | output/* 7 | 8 | .vs 9 | 10 | # Prerequisites 11 | *.d 12 | 13 | # Compiled Object files 14 | *.slo 15 | *.lo 16 | *.o 17 | *.obj 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Compiled Dynamic libraries 24 | *.so 25 | *.dylib 26 | *.dll 27 | 28 | # Fortran module files 29 | *.mod 30 | *.smod 31 | 32 | # Compiled Static libraries 33 | *.lai 34 | *.la 35 | *.a 36 | *.lib 37 | 38 | # Executables 39 | *.exe 40 | *.out 41 | *.app 42 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | project(stack-tree) 4 | 5 | if (PROJECT_IS_TOP_LEVEL) 6 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 7 | 8 | # set(CMAKE_SYSTEM_VERSION 10.0.22000.0) 9 | 10 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin-lib") 11 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin-etc") 12 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") 13 | 14 | set(CMAKE_CXX_STANDARD 23) 15 | set(CMAKE_CXX_STANDARD_REQUIRED) 16 | endif() 17 | 18 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 19 | enable_testing() 20 | 21 | include(CheckIPOSupported) 22 | check_ipo_supported(RESULT IPO_SUPPORTED OUTPUT IPO_NOT_SUPPORTED_ERROR LANGUAGES CXX) 23 | 24 | if(NOT ${IPO_SUPPORTED}) 25 | message(STATUS "IPO / LTO not supported: <${IPO_NOT_SUPPORTED_ERROR}>") 26 | else() 27 | message(STATUS "IPO / LTO supported.") 28 | endif() 29 | 30 | include(FetchContent) 31 | 32 | add_subdirectory("include") 33 | 34 | add_library(fox::stacktree ALIAS stacktree) 35 | 36 | option(sample ON) 37 | option(test ON) 38 | 39 | if (sample) 40 | add_subdirectory("sample") 41 | endif() 42 | 43 | if (test) 44 | add_subdirectory("test") 45 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Marcin (RedSkittleFox) Poloczek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CMake MSVC Build and Test](https://github.com/RedSkittleFox/stacktree/actions/workflows/cmake-msvc-build.yml/badge.svg)](https://github.com/RedSkittleFox/stacktree/actions/workflows/cmake-msvc-build.yml) 2 | # stacktree 3 | A way of storing and representing stacktraces as a tree with attached data! 4 | 5 | Features: 6 | * Follows STL design - provides iterators and custom allocator support. 7 | * Documentation is available in code. 8 | * Header only. 9 | * Fully tested. 10 | 11 | # [Example](https://github.com/RedSkittleFox/stacktree/blob/main/sample/main.cpp) 12 | ```cpp 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | // Just a dummy 21 | struct global_allocator 22 | { 23 | struct allocation 24 | { 25 | size_t allocation_size; 26 | std::stacktrace st; 27 | }; 28 | 29 | std::vector allocations; 30 | 31 | [[nodiscard]] void* allocate(size_t size) 32 | { 33 | auto st = std::stacktrace::current(1); 34 | allocations.emplace_back(size, std::move(st)); 35 | return nullptr; 36 | } 37 | 38 | void deallocate(void* ptr) 39 | { 40 | (void)ptr; 41 | } 42 | 43 | struct allocation_size 44 | { 45 | size_t size = {}; 46 | }; 47 | 48 | [[nodiscard]] fox::stacktree get_allocation_telemetry() const 49 | { 50 | fox::stacktree out; 51 | 52 | for(const auto& a : allocations) 53 | { 54 | out.insert(a.st, [&](auto& v) { v.value().size += a.allocation_size; }); 55 | } 56 | 57 | return out; 58 | } 59 | } alloc; 60 | 61 | std::ostream& operator<<(std::ostream& os, global_allocator::allocation_size s) 62 | { 63 | return os << s.size << " Bytes"; 64 | } 65 | 66 | void do_something_funny() 67 | { 68 | for(auto i : std::views::iota(0, 10)) 69 | { 70 | auto ptr = alloc.allocate(i * sizeof(std::string)); 71 | alloc.deallocate(ptr); 72 | } 73 | } 74 | 75 | void do_something_not_funny() 76 | { 77 | auto ptr = alloc.allocate(1024 * 1024); 78 | alloc.deallocate(ptr); 79 | } 80 | 81 | int main() 82 | { 83 | do_something_funny(); 84 | do_something_not_funny(); 85 | 86 | const auto tree = alloc.get_allocation_telemetry(); 87 | 88 | std::cout << tree << '\n'; 89 | 90 | return 0; 91 | } 92 | ``` 93 | 94 | ``` 95 | ntdll!RtlUserThreadStart+0x28 : 1050376 Bytes 96 | KERNEL32!BaseThreadInitThunk+0x1D : 1050376 Bytes 97 | stacktree_demo!mainCRTStartup+0xE : 1050376 Bytes 98 | stacktree_demo!__scrt_common_main+0xE : 1050376 Bytes 99 | stacktree_demo!__scrt_common_main_seh+0x12E : 1050376 Bytes 100 | stacktree_demo!invoke_main+0x39 : 1050376 Bytes 101 | ...\stacktree\sample\main.cpp(73): stacktree_demo!main+0x1C : 1800 Bytes 102 | ...\stacktree\sample\main.cpp(59): stacktree_demo!do_something_funny+0xC7 : 1800 Bytes 103 | ...\stacktree\sample\main.cpp(75): stacktree_demo!main+0x21 : 1048576 Bytes 104 | ...\stacktree\sample\main.cpp(66): stacktree_demo!do_something_not_funny+0x17 : 1048576 Bytes 105 | ``` 106 | 107 | # Requirement 108 | * C++23 or higher 109 | * `` support. Right now this library only supports MSVC until GCC and clang provide support for the C++23's `` library. 110 | * No external dependencies are requires, stacktree is a header-only library. 111 | 112 | # Installation 113 | ``` 114 | git clone https://github.com/RedSkittleFox/stacktree.git 115 | mkdir stacktree/output 116 | cd stakctree/output 117 | cmake -Dsample=ON -Dtest=on .. 118 | ``` 119 | or 120 | ```cmake 121 | include(FetchContent) 122 | FetchContent_Declare( 123 | stacktree 124 | GIT https://github.com/RedSkittleFox/stacktree.git 125 | ) 126 | 127 | FetchContent_MakeAvailable(stacktree) 128 | 129 | ``` 130 | 131 | ``` 132 | target_link_libraries(your_library PUBLIC fox::stacktree) 133 | ``` 134 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | set(sources 4 | "${CMAKE_CURRENT_SOURCE_DIR}/fox/stacktree.hpp" 5 | ) 6 | 7 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${sources}) 8 | 9 | add_library( 10 | stacktree 11 | INTERFACE 12 | ${sources} 13 | ) 14 | 15 | if(${IPO_SUPPORTED}) 16 | set_target_properties(stacktree PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 17 | endif() 18 | 19 | target_include_directories( 20 | stacktree 21 | INTERFACE 22 | ${CMAKE_CURRENT_SOURCE_DIR} 23 | ) 24 | -------------------------------------------------------------------------------- /include/fox/stacktree.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FOX_STACKTREE_HPP_ 2 | #define FOX_STACKTREE_HPP_ 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifndef __cpp_lib_stacktrace 18 | #error "Stacktree library requires stacktrace library to be present." 19 | #endif 20 | 21 | namespace fox 22 | { 23 | /** 24 | * \brief Extension of std::stacktrace_entry that carries value with it. 25 | * \tparam T The type of the element 26 | */ 27 | template 28 | class stacktree_entry : public std::stacktrace_entry 29 | { 30 | T value_; 31 | 32 | public: 33 | /** 34 | * \brief Default constructor. Creates an empty stacktree_entry 35 | */ 36 | stacktree_entry() : value_{} {} 37 | 38 | /** 39 | * \brief Constructs the stacktree_entry from std::stacktrace_entry and the value 40 | * \param se std::stracktrace_entry to initialzie stacktree_entry with 41 | * \param value the value to initialize stacktree_entry with 42 | */ 43 | stacktree_entry(const std::stacktrace_entry& se, const T& value = {}) 44 | : std::stacktrace_entry(se), value_(value) {} 45 | 46 | /** 47 | * \brief Copy constructor. Constructs stacktree_entry from a copy. 48 | * \param other another stacktree_entry to be used as a source to initialize the stacktree_entry with 49 | */ 50 | stacktree_entry(const stacktree_entry& other) = default; 51 | 52 | /** 53 | * \brief Move constructor. Constructs stacktree_entry by moving contents from 54 | * \param other other another stacktree_entry to be used as a source to initialize the stacktree_entry with 55 | */ 56 | stacktree_entry(stacktree_entry&& other) 57 | noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v) = default; 58 | 59 | 60 | /** 61 | * \brief Copy assignment operator. 62 | * \param other another stacktree_entry to be used as a source to initialize the stacktree_entry with 63 | * \return *this 64 | */ 65 | stacktree_entry& operator=(const stacktree_entry& other) = default; 66 | 67 | /** 68 | * \brief Replaces the underlying stacktrace_entry with the copy 69 | * \param other another stacktrace_entry to be used as a source to initialize the stacktree_entry with 70 | * \return *this 71 | */ 72 | stacktree_entry& operator=(const std::stacktrace_entry& other) 73 | { 74 | std::stacktrace_entry::operator=(other); 75 | value_ = {}; 76 | return *this; 77 | } 78 | 79 | /** 80 | * \brief Move assignment operator. 81 | * \param other another stacktree_entry to be used as a source to initialize the stacktree_entry with 82 | * \return *this 83 | */ 84 | stacktree_entry& operator=(stacktree_entry&& other) 85 | noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_move_assignable_v) = default; 86 | 87 | /** 88 | * \brief Destructs the stacktree_entry. 89 | */ 90 | ~stacktree_entry() noexcept = default; 91 | 92 | public: 93 | /** 94 | * \brief Converts stacktree_entry to stored-type reference. 95 | */ 96 | explicit operator const T&() const noexcept 97 | { 98 | return value_; 99 | } 100 | 101 | /** 102 | * \brief Converts stacktree_entry to stored-type reference. 103 | */ 104 | explicit operator T& () noexcept 105 | { 106 | return value_; 107 | } 108 | 109 | /** 110 | * \brief Getter for stored value. 111 | * \return Reference to stored value. 112 | */ 113 | const T& value() const noexcept 114 | { 115 | return value_; 116 | } 117 | 118 | /** 119 | * \brief Getter for stored value. 120 | * \return Reference to stored value. 121 | */ 122 | T& value() noexcept 123 | { 124 | return value_; 125 | } 126 | }; 127 | 128 | /** 129 | * \brief Type trait checking if type T is an instantiation of std::basic_stacktree 130 | */ 131 | template struct is_basic_stacktrace : std::false_type {}; 132 | 133 | /** 134 | * \brief Type trait checking if type T is an instantiation of std::basic_stacktree 135 | */ 136 | template struct is_basic_stacktrace> : std::true_type {}; 137 | 138 | /** 139 | * \brief Type trait checking if type T is an instantiation of std::scoped_allocator_adaptor 140 | */ 141 | template struct is_scoped_allocator_adaptor : std::false_type {}; 142 | 143 | /** 144 | * \brief Type trait checking if type T is an instantiation of std::scoped_allocator_adaptor 145 | */ 146 | template struct is_scoped_allocator_adaptor> : std::true_type {}; 147 | 148 | /** 149 | * \brief The basic_stacktree class template represents a tree structure with attached data composed of multiple basic_stacktraces 150 | * \tparam T Type to be stored by basic_stacktree nodes. 151 | * \tparam Allocator An allocator that is used to acquire/release memory and to construct/destroy the elements in that memory. 152 | */ 153 | template 154 | requires std::same_as || std::copyable 155 | class basic_stacktree 156 | { 157 | public: 158 | /** 159 | * \brief std::stacktrace_entry if T is void or stacktree_entry 160 | */ 161 | using value_type = 162 | std::conditional_t, 163 | std::stacktrace_entry, 164 | stacktree_entry 165 | >; 166 | 167 | /** 168 | * \brief std::stacktrace_entry 169 | */ 170 | using key_type = std::stacktrace_entry; 171 | 172 | /** 173 | * \brief T 174 | */ 175 | using mapped_type = T; 176 | 177 | /** 178 | * \brief Allocator 179 | */ 180 | using allocator_type = Allocator; 181 | 182 | private: 183 | struct node; 184 | 185 | using node_allocator_type = typename std::allocator_traits::template rebind_alloc; 186 | 187 | // Check if the allocator is scoped-like - during the construction of the members/collection, 188 | // the parent object uses it's own allocator to allocate children. This library currently implements a multi-level 189 | // tree structure using nested containers. 190 | template< 191 | class inner_t = typename std::allocator_traits::template rebind_alloc, 192 | class outer_t = std::scoped_allocator_adaptor::template rebind_alloc> 193 | > 194 | static constexpr bool is_not_scoped_allocator_impl = requires 195 | { 196 | { std::allocator_traits 197 | ::construct(std::declval(), static_cast*>(nullptr)) } -> std::same_as; 198 | }; 199 | 200 | static constexpr bool is_scoped_allocator = 201 | is_scoped_allocator_adaptor::value || 202 | !is_not_scoped_allocator_impl<> 203 | ; 204 | 205 | using scoped_node_allocator_type = 206 | std::conditional_t< 207 | is_scoped_allocator, 208 | node_allocator_type, 209 | std::scoped_allocator_adaptor 210 | >; 211 | 212 | static_assert(std::is_constructible_v>>, 213 | "Selected allocator type cannot be rebound (copy-construct) to allocator_type. "); 214 | 215 | using children_container = std::vector; 216 | 217 | struct node 218 | { 219 | using allocator_type = scoped_node_allocator_type; 220 | 221 | node() : node(allocator_type()) {} 222 | 223 | node(const allocator_type& alloc) 224 | : parent(nullptr), children(static_cast(alloc)) {} 225 | 226 | node(const node& other) = default; 227 | 228 | node(node&& other) noexcept = default; 229 | 230 | node(node&& other, const allocator_type& alloc) noexcept 231 | : 232 | parent(std::exchange(other.parent, nullptr)), 233 | stacktrace(std::move(other.stacktrace)), 234 | children(std::move(other.children), static_cast(alloc)) 235 | {} 236 | 237 | node(const node& other, const allocator_type& alloc) 238 | : 239 | parent(other.parent), 240 | stacktrace(other.stacktrace), 241 | children(other.children, static_cast(alloc)) 242 | {} 243 | 244 | node& operator=(const node& other) = default; 245 | node& operator=(node&& other) noexcept = default; 246 | 247 | ~node() noexcept = default; 248 | 249 | node( 250 | node* parent, 251 | const value_type& stacktrace, 252 | const allocator_type& alloc = allocator_type() 253 | ) : 254 | parent{parent}, 255 | stacktrace{ stacktrace }, 256 | children(static_cast(alloc)) 257 | {} 258 | 259 | node( 260 | node* parent, 261 | const value_type& stacktrace, 262 | const children_container& children, 263 | const allocator_type& alloc = allocator_type() 264 | ) : 265 | parent{ parent }, 266 | stacktrace{ stacktrace }, 267 | children(children, static_cast(alloc)) 268 | {} 269 | 270 | node( 271 | node* parent, 272 | const value_type& stacktrace, 273 | children_container&& children, 274 | const allocator_type& alloc = allocator_type() 275 | ) : 276 | parent{ parent }, 277 | stacktrace{ stacktrace }, 278 | children(std::move(children), static_cast(alloc)) 279 | {} 280 | 281 | node* parent = nullptr; 282 | value_type stacktrace; 283 | children_container children; 284 | 285 | [[nodiscard]] bool operator==(const node& rhs) const noexcept 286 | { 287 | return 288 | this->stacktrace == rhs.stacktrace && 289 | this->children == rhs.children; 290 | } 291 | }; 292 | 293 | static_assert(std::uses_allocator_v); 294 | 295 | friend bool operator<(const node& lhs, const std::stacktrace_entry& rhs) 296 | { 297 | return lhs.stacktrace < rhs; 298 | } 299 | 300 | friend bool operator<(const std::stacktrace_entry& lhs, const node& rhs) 301 | { 302 | return lhs < rhs.stacktrace; 303 | } 304 | 305 | node root_node_; 306 | 307 | public: 308 | /** 309 | * \brief Default constructor. Constructs an empty basic_stacktree with a default-constructed allocator. 310 | */ 311 | basic_stacktree() 312 | requires std::is_default_constructible_v 313 | = default; 314 | 315 | /** 316 | * \brief Constructs an empty basic_stacktree with the given allocator alloc. 317 | * \param alloc allocator to use for all memory allocations of this container 318 | */ 319 | explicit basic_stacktree(const allocator_type& alloc) 320 | : root_node_(nullptr, {}, node_allocator_type{alloc}) {} 321 | 322 | /** 323 | * \brief Copy constructor. Constructs the basic_stacktree with the copy of the contents of other. 324 | * \param other another container to be used as source to initialize the elements of the container with 325 | */ 326 | basic_stacktree(const basic_stacktree& other) = default; 327 | 328 | /** 329 | * \brief Move constructor. Constructs the basic_stacktree with the contents of other using move semantics. 330 | * \param other another container to be used as source to initialize the elements of the container with 331 | */ 332 | basic_stacktree(basic_stacktree&& other) noexcept( 333 | std::allocator_traits::is_always_equal::value 334 | ) = default; 335 | 336 | /** 337 | * \brief Constructs the container with the copy of the contents of other, using alloc as the allocator. 338 | * \param other another container to be used as source to initialize the elements of the container with 339 | * \param alloc allocator to use for all memory allocations of this container 340 | */ 341 | basic_stacktree( 342 | const basic_stacktree& other, 343 | const allocator_type& alloc 344 | ) : root_node_( 345 | nullptr, 346 | other.root_node_.stacktrace, 347 | other.root_node_.children, 348 | scoped_node_allocator_type{ node_allocator_type{ alloc } } 349 | ) {} 350 | 351 | /** 352 | * \brief Allocator-extended move constructor. 353 | * \param other another container to be used as source to initialize the elements of the container with 354 | * \param alloc allocator to use for all memory allocations of this container 355 | */ 356 | basic_stacktree( 357 | basic_stacktree&& other, 358 | const allocator_type& alloc 359 | ) : root_node_( 360 | std::move(other.root_node_), 361 | node_allocator_type{alloc} 362 | ) 363 | {} 364 | 365 | /** 366 | * \brief Constructs the basic_stacktree with the contents of the range [first, last). 367 | * \tparam InputIt LegacyInputIterator which is indirect convertible to T 368 | * \param first iterator to begin of the range to copy from 369 | * \param last iterator to end of the range to copy from 370 | * \param alloc allocator to use for all memory allocations of this container 371 | */ 372 | template 373 | basic_stacktree(InputIt first, InputIt last, const allocator_type& alloc = allocator_type()) 374 | requires is_basic_stacktrace>::value 375 | : root_node_(nullptr, {}, node_allocator_type{ alloc }) 376 | { 377 | this->assign(first, last); 378 | } 379 | 380 | /** 381 | * \brief Constructs the container with the contents of the initializer list init. 382 | * \tparam StacktraceAlloc Stacktrace's Allocator 383 | * \param il initializer list to initialize the elements of the container with 384 | * \param alloc allocator to use for all memory allocations of this container 385 | */ 386 | template 387 | basic_stacktree( 388 | std::initializer_list> il, 389 | const allocator_type& alloc = allocator_type() 390 | ) 391 | : basic_stacktree(std::begin(il), std::end(il), alloc) {} 392 | 393 | /** 394 | * \brief Constructs the container with the contents of the range rg. 395 | * \tparam R input_range whose elements are convertible to T 396 | * \param rg a container compatible range, that is, an input_range whose elements are convertible to T 397 | * \param alloc allocator to use for all memory allocations of this container 398 | */ 399 | template 400 | basic_stacktree( 401 | std::from_range_t, 402 | R&& rg, 403 | const allocator_type alloc = allocator_type() 404 | ) 405 | requires is_basic_stacktrace>::value 406 | : basic_stacktree(std::begin(rg), std::end(rg), alloc) {} 407 | 408 | /** 409 | * \brief Copy assignment operator. 410 | * \param other another container to use as data source 411 | * \return *this 412 | */ 413 | basic_stacktree& operator=(const basic_stacktree& other) = default; 414 | 415 | /** 416 | * \brief Move assignment operator. Replaces the contents with those of other using move semantics. 417 | * \param other another container to use as data source 418 | * \return *this 419 | */ 420 | basic_stacktree& operator=(basic_stacktree&& other) 421 | noexcept( 422 | std::allocator_traits::is_always_equal::value 423 | ) 424 | = default; 425 | 426 | /** 427 | * \brief Replaces the contents with those identified by initializer list il. 428 | * \tparam StacktraceAlloc std::basic_stacktrace's allocator 429 | * \param il initializer list to use as data source 430 | * \return *this 431 | */ 432 | template 433 | basic_stacktree& operator=(std::initializer_list> il) 434 | { 435 | this->assign(il); 436 | 437 | return *this; 438 | } 439 | 440 | /** 441 | * \brief Destructs the basic_stacktree. 442 | */ 443 | ~basic_stacktree() = default; 444 | 445 | public: 446 | /** 447 | * \brief Replaces the contents with the elements from the initializer list il. 448 | * \tparam StacktraceAlloc std::basic_stacktrace's allocator 449 | * \param il initializer list to copy the values from 450 | */ 451 | template 452 | void assign(std::initializer_list> il) 453 | { 454 | return this->assign(std::begin(il), std::end(il)); 455 | } 456 | 457 | /** 458 | * \brief Replaces the contents with copies of those in the range [first, last). 459 | * \tparam InputIt LegacyInputIterator which is indirect convertible to T 460 | * \param first iterator to begin of the range to copy from 461 | * \param last iterator to end of the range to copy from 462 | */ 463 | template 464 | void assign(InputIt first, InputIt last) 465 | requires is_basic_stacktrace>::value 466 | { 467 | this->clear(); 468 | this->insert(first, last); 469 | } 470 | 471 | /** 472 | * \brief Replaces elements in the basic_stacktree with a copy of each element in rg. 473 | * \tparam R input_range whose elements are convertible to T 474 | * \param rg a container compatible range, that is, an input_range whose elements are convertible to T 475 | */ 476 | template 477 | void assign_range(R&& rg) 478 | requires is_basic_stacktrace>::value 479 | { 480 | return this->assign(std::begin(rg), std::end(rg)); 481 | } 482 | 483 | /** 484 | * \brief Returns the allocator associated with the container. 485 | * \return The associated allocator. 486 | */ 487 | [[nodiscard]] allocator_type get_allocator() const noexcept 488 | { 489 | if constexpr(!is_scoped_allocator) 490 | return allocator_type{ this->root_node_.children.get_allocator().outer_allocator() }; 491 | else 492 | return allocator_type{ this->root_node_.children.get_allocator() }; 493 | } 494 | 495 | private: 496 | template 497 | class iterator_implementation 498 | { 499 | template 500 | requires std::same_as || std::copyable 501 | friend class basic_stacktree; 502 | 503 | public: 504 | using iterator_category = std::random_access_iterator_tag; 505 | using value_type = U; 506 | using difference_type = std::ptrdiff_t; 507 | using pointer = U*; 508 | using reference = U&; 509 | 510 | private: 511 | using node_type = std::conditional_t< 512 | std::is_const_v>, 513 | std::add_const_t, 514 | node 515 | >; 516 | 517 | private: 518 | node_type* node_; 519 | difference_type index_; 520 | private: 521 | void assert_children_in_range(difference_type modifier) const // Assert not set index, strong exception guarantee 522 | { 523 | difference_type sum = this->index_ + modifier; 524 | #define THROW_OUT_OF_RANGE \ 525 | throw std::out_of_range("Attempting to access out of range element via iterator.") 526 | 527 | if (modifier > 0 && sum < this->index_) // overflow 528 | THROW_OUT_OF_RANGE; 529 | 530 | if (modifier < 0 && sum > this->index_) // underflow 531 | THROW_OUT_OF_RANGE; 532 | 533 | if (node_ == nullptr || sum < 0 || sum > std::ssize(node_->children)) 534 | THROW_OUT_OF_RANGE; 535 | 536 | #undef THROW_OUT_OF_RANGE 537 | } 538 | 539 | void assert_valid_access() const 540 | { 541 | if(node_ == nullptr || index_ >= std::ssize(node_->children)) 542 | throw std::out_of_range("Attempting to access out of range element via iterator."); 543 | } 544 | 545 | void assert_same_domain(const iterator_implementation& other) const 546 | { 547 | if (this->node_ != other.node_) 548 | throw std::domain_error("Attempting to compare iterators belonging to different objects."); 549 | } 550 | 551 | iterator_implementation(node_type* node, difference_type index = 0) noexcept // begin 552 | : node_(node), index_(index) {} 553 | 554 | iterator_implementation(node_type* node, std::in_place_t) noexcept // begin 555 | : node_(node), index_((node == nullptr ? 0 : node->children.size())) {} 556 | public: 557 | iterator_implementation() noexcept 558 | : node_(nullptr), index_(0) {} 559 | 560 | iterator_implementation(const iterator_implementation>& other) 561 | requires std::is_const_v 562 | : node_(other.node_), index_(other.index_) {} 563 | 564 | iterator_implementation(const iterator_implementation&) noexcept = default; 565 | iterator_implementation(iterator_implementation&&) noexcept = default; 566 | 567 | iterator_implementation& operator=(const iterator_implementation&) noexcept = default; 568 | iterator_implementation& operator=(iterator_implementation&&) noexcept = default; 569 | 570 | ~iterator_implementation() noexcept = default; 571 | 572 | public: 573 | reference operator*() const 574 | { 575 | assert_valid_access(); 576 | return node_->children[index_].stacktrace; 577 | } 578 | 579 | pointer operator->() const 580 | { 581 | assert_valid_access(); 582 | return std::addressof(node_->children[index_].stacktrace); 583 | } 584 | 585 | reference operator[](difference_type n) const 586 | { 587 | return *(*this + n); 588 | } 589 | 590 | public: 591 | iterator_implementation& operator++() 592 | { 593 | assert_children_in_range(1); 594 | this->index_ += 1; 595 | return *this; 596 | } 597 | 598 | [[nodiscard]] iterator_implementation operator++(int) 599 | { 600 | return ++iterator(*this); 601 | } 602 | 603 | iterator_implementation& operator--() 604 | { 605 | assert_children_in_range( - 1); 606 | this->index_ -= 1; 607 | return *this; 608 | } 609 | 610 | [[nodiscard]] iterator_implementation operator--(int) 611 | { 612 | return --iterator(*this); 613 | } 614 | 615 | public: 616 | [[nodiscard]] bool operator==(const iterator_implementation& rhs) const noexcept 617 | { 618 | if (this->node_ != rhs.node_) 619 | return false; 620 | 621 | if (this->node_ == nullptr) 622 | return true; 623 | 624 | return this->index_ == rhs.index_; 625 | } 626 | 627 | [[nodiscard]] bool operator!=(const iterator_implementation& rhs) const noexcept 628 | { 629 | return !(*this == rhs); 630 | } 631 | 632 | public: 633 | iterator_implementation& operator+=(difference_type n) 634 | { 635 | assert_children_in_range(n); 636 | this->index_ += n; 637 | return *this; 638 | } 639 | 640 | [[nodiscard]] iterator_implementation operator+(difference_type n) const 641 | { 642 | return (iterator_implementation(*this) += n); 643 | } 644 | 645 | [[nodiscard]] friend iterator_implementation operator+(difference_type lhs, const iterator_implementation& rhs) 646 | { 647 | return (iterator_implementation(rhs) += lhs); 648 | } 649 | 650 | iterator_implementation& operator-=(difference_type n) 651 | { 652 | assert_children_in_range(-n); 653 | this->index_ -= n; 654 | return *this; 655 | } 656 | 657 | [[nodiscard]] iterator_implementation operator-(difference_type n) const 658 | { 659 | return (iterator(*this) -= n); 660 | } 661 | 662 | public: 663 | difference_type operator-(const iterator_implementation& rhs) const 664 | { 665 | assert_same_domain(rhs); 666 | return this->index_ - rhs.index_; 667 | } 668 | 669 | [[nodiscard]] bool operator<(const iterator_implementation& rhs) const 670 | { 671 | assert_same_domain(rhs); 672 | return this->index_ < rhs.index_; 673 | } 674 | 675 | [[nodiscard]] bool operator>(const iterator_implementation& rhs) const 676 | { 677 | assert_same_domain(rhs); 678 | return this->index_ > rhs.index_; 679 | } 680 | 681 | [[nodiscard]] bool operator<=(const iterator_implementation& rhs) const 682 | { 683 | assert_same_domain(rhs); 684 | return this->index_ <= rhs.index_; 685 | } 686 | 687 | [[nodiscard]] bool operator>=(const iterator_implementation& rhs) const 688 | { 689 | assert_same_domain(rhs); 690 | return this->index_ >= rhs.index_; 691 | } 692 | 693 | public: 694 | [[nodiscard]] iterator_implementation begin_parent() noexcept 695 | { 696 | if (this->node_ == nullptr) 697 | return iterator_implementation(nullptr); 698 | 699 | return iterator_implementation(this->node_->parent); 700 | } 701 | 702 | [[nodiscard]] iterator_implementation end_parent() noexcept 703 | { 704 | if (this->node_ == nullptr) 705 | return iterator_implementation(nullptr); 706 | 707 | return iterator_implementation(this->node_->parent, std::in_place); 708 | } 709 | 710 | [[nodiscard]] iterator_implementation begin_child() noexcept 711 | { 712 | if (this->node_ == nullptr) 713 | return iterator_implementation(nullptr); 714 | 715 | if (this->index_ < std::ssize(this->node_->children)) 716 | return iterator_implementation(std::addressof(this->node_->children[this->index_])); 717 | 718 | return iterator_implementation(nullptr); 719 | } 720 | 721 | [[nodiscard]] iterator_implementation end_child() noexcept 722 | { 723 | if (this->node_ == nullptr) 724 | return iterator_implementation(nullptr); 725 | 726 | if (this->index_ < std::ssize(this->node_->children)) 727 | return iterator_implementation(std::addressof(this->node_->children[this->index_]), std::in_place); 728 | 729 | return iterator_implementation(nullptr); 730 | } 731 | }; 732 | 733 | public: 734 | /** 735 | * \brief contiguous_iterator to value_type 736 | */ 737 | using iterator = iterator_implementation; 738 | 739 | /** 740 | * \brief contiguous_iterator to const value_type 741 | */ 742 | using const_iterator = iterator_implementation; 743 | 744 | static_assert(std::random_access_iterator); 745 | static_assert(std::random_access_iterator); 746 | 747 | /** 748 | * \brief Returns an iterator to the first element of the first level of the basic_stacktree. 749 | * \return Iterator to the first element of the first level. 750 | */ 751 | [[nodiscard]] iterator begin() noexcept 752 | { 753 | return iterator(std::addressof(root_node_)); 754 | } 755 | 756 | /** 757 | * \brief Returns an iterator to the last element of the first level of the basic_stacktree. 758 | * \return Iterator to the last element of the first level. 759 | */ 760 | [[nodiscard]] iterator end() noexcept 761 | { 762 | return iterator(std::addressof(root_node_), std::in_place); 763 | } 764 | 765 | /** 766 | * \brief Returns an iterator to the first element of the first level of the basic_stacktree. 767 | * \return Iterator to the first element of the first level. 768 | */ 769 | [[nodiscard]] const_iterator begin() const noexcept 770 | { 771 | return const_iterator(std::addressof(root_node_)); 772 | } 773 | 774 | /** 775 | * \brief Returns an iterator to the last element of the first level of the basic_stacktree. 776 | * \return Iterator to the last element of the first level. 777 | */ 778 | [[nodiscard]] const_iterator end() const noexcept 779 | { 780 | return const_iterator(std::addressof(root_node_), std::in_place); 781 | } 782 | 783 | /** 784 | * \brief Returns an iterator to the first element of the first level of the basic_stacktree. 785 | * \return Iterator to the first element of the first level. 786 | */ 787 | [[nodiscard]] const_iterator cbegin() const noexcept 788 | { 789 | return const_iterator(std::addressof(root_node_)); 790 | } 791 | 792 | /** 793 | * \brief Returns an iterator to the last element of the first level of the basic_stacktree. 794 | * \return Iterator to the last element of the first level. 795 | */ 796 | [[nodiscard]] const_iterator cend() const noexcept 797 | { 798 | return const_iterator(std::addressof(root_node_), std::in_place); 799 | } 800 | 801 | private: 802 | template 803 | iterator insert_internal(iterator pos, const std::basic_stacktrace& stacktrace, UnaryFunction& f) 804 | { 805 | static constexpr typename iterator::difference_type return_pos_npos 806 | = std::numeric_limits::min(); 807 | typename iterator::difference_type return_pos = return_pos_npos; 808 | 809 | node* node = pos.node_; 810 | if (node == nullptr) 811 | throw std::out_of_range("Iterator does not point to any stacktree node."); 812 | 813 | for(const std::stacktrace_entry& entry : stacktrace | std::views::reverse) 814 | { 815 | auto& children = node->children; 816 | 817 | auto first = std::lower_bound( 818 | std::begin(children), 819 | std::end(children), 820 | entry, 821 | std::less{} 822 | ); 823 | 824 | if (first == std::end(children) || static_cast(first->stacktrace) != entry) // Insert a new entry 825 | { 826 | // Construct new entry, we need to sort the vector 827 | typename std::remove_cvref_t::iterator it; 828 | it = children.emplace(first, node, entry); // Update children's parents 829 | 830 | for(auto& c : children) 831 | { 832 | for(auto& cc : c.children) 833 | { 834 | cc.parent = std::addressof(c); 835 | } 836 | } 837 | 838 | if constexpr(EnableFunction) 839 | std::invoke(f, it->stacktrace); 840 | 841 | if (return_pos == return_pos_npos) 842 | return_pos = std::distance(std::begin(children), it); 843 | 844 | node = std::addressof(*it); 845 | } 846 | else 847 | { 848 | // Enter an already existing node and merge args 849 | if (return_pos == return_pos_npos) 850 | return_pos = std::distance(std::begin(children), first); 851 | 852 | // Merge values 853 | if constexpr (EnableFunction) 854 | std::invoke(f, first->stacktrace); 855 | 856 | node = std::addressof(*first); 857 | } 858 | } 859 | 860 | return iterator(pos.node_, return_pos); 861 | } 862 | 863 | template 864 | iterator insert_range_internal(InputIt first, InputIt last, iterator pos, UnaryFunction& f) 865 | { 866 | iterator out = pos; 867 | for(; first != last; ++first) 868 | { 869 | out = insert_internal(out, *first, f); 870 | } 871 | 872 | return out; 873 | } 874 | 875 | public: 876 | /** 877 | * \brief Inserts std::basic_stacktrace into basic_stacktree and invokes function f on the inserted elements. 878 | * \tparam UnaryFunction Unary function compatible with T& (if T is not void) or value_type&. 879 | * \tparam StackTraceAllocator std::basic_stacktrace's allocator. 880 | * \param stacktrace std::basic_stacktrace to insert into basic_stacktree. 881 | * \param f Unary function which will modify inserted values. 882 | * \return Iterator to the lowest inserted node. 883 | */ 884 | template 885 | iterator insert(const std::basic_stacktrace& stacktrace, UnaryFunction f) 886 | requires (std::is_same_v && std::invocable) || std::invocable 887 | { 888 | return insert_internal(this->begin(), stacktrace, f); 889 | } 890 | 891 | /** 892 | * \brief Inserts std::basic_stacktrace into basic_stacktree. 893 | * \tparam StackTraceAllocator std::basic_stacktrace's allocator. 894 | * \param stacktrace std::basic_stacktrace to insert into basic_stacktree. 895 | * \return Iterator to the lowest inserted node. 896 | */ 897 | template 898 | iterator insert(const std::basic_stacktrace& stacktrace) 899 | { 900 | auto v = nullptr; 901 | return insert_internal(this->begin(), stacktrace, v); 902 | } 903 | 904 | /** 905 | * \brief Inserts the copies of those in the range [first, last). 906 | * \tparam InputIt LegacyInputIterator which is indirect convertible to T. 907 | * \param first iterator to begin of the range to copy from. 908 | * \param last iterator to end of the range to copy from. 909 | * \return Iterator to the lowest inserted node. 910 | */ 911 | template 912 | iterator insert(InputIt first, InputIt last) 913 | requires is_basic_stacktrace>::value 914 | { 915 | auto v = nullptr; 916 | return insert_range_internal(first, last, this->begin(), v); 917 | } 918 | 919 | /** 920 | * \brief Inserts the initializer list il. 921 | * \tparam StackTraceAllocator Stacktrace's Allocator 922 | * \param il initializer list to insert the elements of the container with 923 | * \return Iterator to the lowest inserted node. 924 | */ 925 | template 926 | iterator insert(std::initializer_list> il) 927 | { 928 | return this->insert(std::begin(il), std::end(il)); 929 | } 930 | 931 | /** 932 | * \brief Inserts the copies of those in the range rg. 933 | * \tparam R input_range whose elements are convertible to T 934 | * \param rg a container compatible range, that is, an input_range whose elements are convertible to T 935 | */ 936 | template 937 | void insert_range(R&& rg) 938 | requires is_basic_stacktrace>::value 939 | { 940 | this->insert(std::begin(rg), std::end(rg)); 941 | } 942 | 943 | /** 944 | * \brief Erases the specified elements from the container. Removes the element at pos. 945 | * \param pos iterator to the element to remove 946 | * \return Iterator following the last removed element. 947 | */ 948 | iterator erase(const_iterator pos) 949 | { 950 | // Const-cast node ptr 951 | // Even tho we pass non-mutable iterator, this function mutates the object 952 | // the underlying iterator points to, we do not violate const-correctness. 953 | auto p_node = const_cast(pos.node_); 954 | 955 | if (pos == this->end()) 956 | return iterator(p_node, pos.index_); 957 | 958 | auto r = p_node->children.erase( 959 | std::begin(p_node->children) + pos.index_); 960 | 961 | return iterator(p_node, std::distance(std::begin(p_node->children), r)); 962 | } 963 | 964 | /** 965 | * \brief Erases the specified elements from the container. Range of elements to remove. 966 | * \param first iterator to begin of the range to erase. 967 | * \param last iterator to end of the range to erase. 968 | * \return Iterator following the last removed element. 969 | */ 970 | iterator erase(const_iterator first, const_iterator last) 971 | { 972 | // Const-cast node ptr 973 | // Even tho we pass non-mutable iterator, this function mutates the object 974 | // the underlying iterator points to, we do not violate const-correctness. 975 | auto p_node = const_cast(first.node_); 976 | 977 | if (first == last) 978 | return iterator(p_node, first.index_); 979 | 980 | first.assert_same_domain(last); // p_node is same in first and last 981 | 982 | auto r = p_node->children.erase( 983 | std::begin(p_node->children) + first.index_, std::begin(p_node->children) + last.index_); 984 | 985 | return iterator(p_node, std::distance(std::begin(p_node->children), r)); 986 | } 987 | 988 | public: 989 | /** 990 | * \brief Checks if the container has no elements. 991 | * \return true if the container is empty, false otherwise. 992 | */ 993 | [[nodiscard]] bool empty() const noexcept 994 | { 995 | return std::empty(this->root_node_.children); 996 | } 997 | 998 | /** 999 | * \brief Erases all elements from the basic_stacktree. 1000 | */ 1001 | void clear() noexcept 1002 | { 1003 | this->root_node_.stacktrace = {}; 1004 | return this->root_node_.children.clear(); 1005 | } 1006 | 1007 | public: 1008 | [[nodiscard]] bool operator==(const basic_stacktree& rhs) const noexcept 1009 | { 1010 | return this->root_node_ == rhs.root_node_; 1011 | } 1012 | 1013 | [[nodiscard]] bool operator!=(const basic_stacktree& rhs) const noexcept 1014 | { 1015 | return !(*this == rhs); 1016 | } 1017 | 1018 | public: 1019 | void swap(basic_stacktree& other) 1020 | noexcept( 1021 | std::allocator_traits::propagate_on_container_swap::value || 1022 | std::allocator_traits::is_always_equal::value 1023 | ) 1024 | { 1025 | std::swap(this->root_node_, other.root_node_); 1026 | } 1027 | 1028 | private: 1029 | friend void print_node(std::ostream& os, const_iterator current, size_t depth) 1030 | { 1031 | for (size_t i = 0; i < depth; ++i) 1032 | os << " "; 1033 | 1034 | os << static_cast(*current); 1035 | 1036 | if constexpr (!std::is_same_v) 1037 | os << " : " << current->value(); 1038 | 1039 | os << '\n'; 1040 | 1041 | for (auto s = current.begin_child(), e = current.end_child(); 1042 | s != e; ++s) 1043 | { 1044 | print_node(os, s, depth + 1); 1045 | } 1046 | } 1047 | }; 1048 | 1049 | template 1050 | using stacktree = basic_stacktree>; 1051 | 1052 | template 1053 | void swap(basic_stacktree& lhs, basic_stacktree& rhs) 1054 | noexcept(noexcept(lhs.swap(rhs))) 1055 | { 1056 | lhs.swap(rhs); 1057 | } 1058 | 1059 | 1060 | template 1061 | std::ostream& operator<<(std::ostream& os, const basic_stacktree& st) 1062 | { 1063 | return print_node(os, st.begin(), 0), os; 1064 | } 1065 | 1066 | template 1067 | [[nodiscard]] std::string to_string(const basic_stacktree& st) 1068 | { 1069 | std::ostringstream ss; 1070 | ss << st; 1071 | return ss.str(); 1072 | } 1073 | 1074 | namespace pmr 1075 | { 1076 | template 1077 | using stacktree = basic_stacktree>; 1078 | } 1079 | } 1080 | 1081 | 1082 | #endif -------------------------------------------------------------------------------- /sample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | set(sources 4 | "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" 5 | ) 6 | 7 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${sources}) 8 | 9 | add_executable( 10 | stacktree-demo 11 | ${sources} 12 | ) 13 | 14 | if(${IPO_SUPPORTED}) 15 | set_target_properties(stacktree-demo PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 16 | endif() 17 | 18 | if(MSVC) 19 | target_compile_options( 20 | stacktree-demo 21 | PRIVATE /WX # all warnings as errors 22 | PRIVATE /MP # multi-processor compilation 23 | # PRIVATE /FR 24 | ) 25 | endif() 26 | 27 | target_include_directories( 28 | stacktree-demo 29 | PUBLIC 30 | ${CMAKE_CURRENT_SOURCE_DIR} 31 | ) 32 | 33 | target_link_libraries( 34 | stacktree-demo 35 | stacktree 36 | ) -------------------------------------------------------------------------------- /sample/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Outputs: 3 | ntdll!RtlUserThreadStart+0x28 : 1050376 Bytes 4 | KERNEL32!BaseThreadInitThunk+0x1D : 1050376 Bytes 5 | stacktree_demo!mainCRTStartup+0xE : 1050376 Bytes 6 | stacktree_demo!__scrt_common_main+0xE : 1050376 Bytes 7 | stacktree_demo!__scrt_common_main_seh+0x12E : 1050376 Bytes 8 | stacktree_demo!invoke_main+0x39 : 1050376 Bytes 9 | ...\stacktree\sample\main.cpp(73): stacktree_demo!main+0x1C : 1800 Bytes 10 | ...\stacktree\sample\main.cpp(59): stacktree_demo!do_something_funny+0xC7 : 1800 Bytes 11 | ...\stacktree\sample\main.cpp(75): stacktree_demo!main+0x21 : 1048576 Bytes 12 | ...\stacktree\sample\main.cpp(66): stacktree_demo!do_something_not_funny+0x17 : 1048576 Bytes 13 | 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | // Just a dummy 24 | struct global_allocator 25 | { 26 | struct allocation 27 | { 28 | size_t allocation_size; 29 | std::stacktrace st; 30 | }; 31 | 32 | std::vector allocations; 33 | 34 | [[nodiscard]] void* allocate(size_t size) 35 | { 36 | auto st = std::stacktrace::current(1); 37 | allocations.emplace_back(size, std::move(st)); 38 | return nullptr; 39 | } 40 | 41 | void deallocate(void* ptr) 42 | { 43 | (void)ptr; 44 | } 45 | 46 | struct allocation_size 47 | { 48 | size_t size = {}; 49 | }; 50 | 51 | [[nodiscard]] fox::stacktree get_allocation_telemetry() const 52 | { 53 | fox::stacktree out; 54 | 55 | for(const auto& a : allocations) 56 | { 57 | out.insert(a.st, [&](auto& v) { v.value().size += a.allocation_size; }); 58 | } 59 | 60 | return out; 61 | } 62 | } alloc; 63 | 64 | std::ostream& operator<<(std::ostream& os, global_allocator::allocation_size s) 65 | { 66 | return os << s.size << " Bytes"; 67 | } 68 | 69 | void do_something_funny() 70 | { 71 | for(auto i : std::views::iota(0, 10)) 72 | { 73 | auto ptr = alloc.allocate(i * sizeof(std::string)); 74 | alloc.deallocate(ptr); 75 | } 76 | } 77 | 78 | void do_something_not_funny() 79 | { 80 | auto ptr = alloc.allocate(1024 * 1024); 81 | alloc.deallocate(ptr); 82 | } 83 | 84 | int main() 85 | { 86 | do_something_funny(); 87 | do_something_not_funny(); 88 | 89 | const auto tree = alloc.get_allocation_telemetry(); 90 | 91 | std::cout << tree << '\n'; 92 | 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | cmake_policy(PUSH) 4 | 5 | if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") 6 | cmake_policy(SET CMP0135 NEW) 7 | endif() 8 | 9 | include(FetchContent) 10 | FetchContent_Declare( 11 | googletest 12 | URL https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip 13 | ) 14 | # For Windows: Prevent overriding the parent project's compiler/linker settings 15 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 16 | FetchContent_MakeAvailable(googletest) 17 | 18 | set(sources 19 | "${CMAKE_CURRENT_SOURCE_DIR}/stacktree_test.cc" 20 | ) 21 | 22 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${sources}) 23 | 24 | add_executable( 25 | stacktree-test 26 | ${sources} 27 | ) 28 | 29 | if(MSVC) 30 | target_compile_options( 31 | stacktree-test 32 | PRIVATE /W4 33 | PRIVATE /MP 34 | PRIVATE /arch:AVX2 35 | PRIVATE /WX 36 | ) 37 | 38 | endif() 39 | 40 | target_link_libraries( 41 | stacktree-test 42 | GTest::gtest_main 43 | GTest::gmock_main 44 | stacktree 45 | ) 46 | 47 | include(GoogleTest) 48 | gtest_discover_tests(stacktree-test) 49 | 50 | if (PROJECT_IS_TOP_LEVEL) 51 | set_target_properties(gtest_main PROPERTIES FOLDER "vendor") 52 | set_target_properties(gtest PROPERTIES FOLDER "vendor") 53 | set_target_properties(gmock_main PROPERTIES FOLDER "vendor") 54 | set_target_properties(gmock PROPERTIES FOLDER "vendor") 55 | endif() 56 | 57 | cmake_policy(POP) -------------------------------------------------------------------------------- /test/stacktree_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #ifdef _MSC_VER 10 | #pragma warning( push ) 11 | #pragma warning( disable : 4834 ) 12 | #endif 13 | 14 | namespace fox 15 | { 16 | // This is needed for consistent stack-trace level generation for unit-tests. 17 | #ifdef _MSC_VER 18 | #define NOINLINE __declspec(noinline) 19 | #else 20 | #define NOINLINE __attribute__((noinline)) 21 | #endif 22 | 23 | 24 | template 25 | class stacktree_test : public ::testing::Test 26 | { 27 | protected: 28 | NOINLINE void SetUp() override 29 | { 30 | stacktrace_0 = st_func0(); 31 | stacktrace_1 = st_func1(); 32 | } 33 | 34 | private: 35 | NOINLINE [[nodiscard]] static std::stacktrace st_func0() 36 | { 37 | return std::stacktrace::current(); 38 | } 39 | 40 | NOINLINE [[nodiscard]] static std::stacktrace st_func1() 41 | { 42 | return std::stacktrace::current(); 43 | } 44 | 45 | public: 46 | using type = T; 47 | 48 | std::pmr::monotonic_buffer_resource resource_0; 49 | std::pmr::monotonic_buffer_resource resource_1; 50 | std::pmr::polymorphic_allocator<> alloc_0{ std::addressof(resource_0) }; 51 | std::pmr::polymorphic_allocator<> alloc_1{ std::addressof(resource_1) }; 52 | 53 | std::stacktrace stacktrace_0; 54 | std::stacktrace stacktrace_1; 55 | 56 | template 57 | void validate_stacktrace_0(const fox::basic_stacktree& st) 58 | { 59 | ASSERT_FALSE(st.empty()); 60 | ASSERT_NE(st.begin(), st.end()); 61 | 62 | auto b = st.begin(); 63 | auto e = st.end(); 64 | 65 | for (const auto& v : stacktrace_0 | std::views::reverse) 66 | { 67 | ASSERT_EQ(std::distance(b, e), 1); 68 | ASSERT_EQ(static_cast(*b), v); 69 | 70 | e = b.end_child(); 71 | b = b.begin_child(); 72 | } 73 | } 74 | 75 | template 76 | void validate_stacktrace_1(const fox::basic_stacktree& st) 77 | { 78 | ASSERT_FALSE(st.empty()); 79 | ASSERT_NE(st.begin(), st.end()); 80 | 81 | auto b = st.begin(); 82 | auto e = st.end(); 83 | 84 | for (const auto& v : stacktrace_1 | std::views::reverse) 85 | { 86 | ASSERT_EQ(std::distance(b, e), 1); 87 | ASSERT_EQ(static_cast(*b), v); 88 | 89 | e = b.end_child(); 90 | b = b.begin_child(); 91 | } 92 | } 93 | 94 | template 95 | void validate_stacktrace_01(const fox::basic_stacktree& st) 96 | { 97 | ASSERT_FALSE(st.empty()); 98 | ASSERT_NE(st.begin(), st.end()); 99 | 100 | auto b = st.begin(); 101 | auto e = st.end(); 102 | 103 | for (const auto& v : stacktrace_1 | std::views::drop(2) | std::views::reverse) 104 | { 105 | ASSERT_EQ(std::distance(b, e), 1); 106 | ASSERT_EQ(static_cast(*b), v); 107 | 108 | e = b.end_child(); 109 | b = b.begin_child(); 110 | } 111 | 112 | std::set last{ *(stacktrace_0.begin() + 1), *(stacktrace_1.begin() + 1) }; 113 | 114 | EXPECT_TRUE(std::ranges::equal(std::begin(last), std::end(last), b, e)); 115 | 116 | auto b0 = b.begin_child(); 117 | auto e0 = b.end_child(); 118 | 119 | auto b1 = (b+1).begin_child(); 120 | auto e1 = (b+1).end_child(); 121 | 122 | ASSERT_EQ(std::distance(b0, e0), 1); 123 | ASSERT_EQ(std::distance(b1, e1), 1); 124 | ASSERT_EQ(static_cast(*b0), *stacktrace_0.begin()); 125 | ASSERT_EQ(static_cast(*b1), *stacktrace_1.begin()); 126 | } 127 | 128 | template 129 | fox::basic_stacktree construct_0(Args&&... args) 130 | { 131 | auto out = fox::basic_stacktree> 132 | ( std::forward(args)..., alloc_0 ); 133 | 134 | EXPECT_EQ(out.get_allocator(), alloc_0); 135 | return out; 136 | } 137 | 138 | template 139 | fox::basic_stacktree construct_1(Args&&... args) 140 | { 141 | auto out = fox::basic_stacktree> 142 | ( std::forward(args)..., alloc_1 ); 143 | 144 | EXPECT_EQ(out.get_allocator(), alloc_1); 145 | return out; 146 | } 147 | 148 | }; 149 | 150 | using types = testing::Types; 151 | TYPED_TEST_SUITE(stacktree_test, types); 152 | 153 | TYPED_TEST(stacktree_test, constructor_default) 154 | { 155 | fox::stacktree a; 156 | 157 | EXPECT_TRUE(a.empty()); 158 | EXPECT_EQ(a.begin(), a.end()); 159 | } 160 | 161 | TYPED_TEST(stacktree_test, constructor_allocator) 162 | { 163 | auto a = this->construct_0(); 164 | 165 | EXPECT_TRUE(a.empty()); 166 | EXPECT_EQ(a.begin(), a.end()); 167 | EXPECT_EQ(a.get_allocator().resource(), std::addressof(this->resource_0)); 168 | } 169 | 170 | TYPED_TEST(stacktree_test, constructor_copy_constructor) 171 | { 172 | fox::stacktree a{ this->stacktrace_0 }; 173 | this->validate_stacktrace_0(a); 174 | fox::stacktree b = a; 175 | this->validate_stacktrace_0(a); 176 | this->validate_stacktrace_0(b); 177 | 178 | EXPECT_EQ(a, b); 179 | } 180 | 181 | TYPED_TEST(stacktree_test, constructor_move_construct) 182 | { 183 | fox::stacktree a{ this->stacktrace_0 }; 184 | this->validate_stacktrace_0(a); 185 | fox::stacktree b = std::move(a); 186 | this->validate_stacktrace_0(b); 187 | 188 | EXPECT_TRUE(a.empty()); 189 | EXPECT_EQ(a.begin(), a.end()); 190 | 191 | EXPECT_NE(a, b); 192 | } 193 | 194 | TYPED_TEST(stacktree_test, constructor_copy_constructor_allocator) 195 | { 196 | auto a = this->construct_0(std::from_range, std::vector{ this->stacktrace_0 }); 197 | this->validate_stacktrace_0(a); 198 | auto b = this->construct_1(a); 199 | this->validate_stacktrace_0(a); 200 | this->validate_stacktrace_0(b); 201 | 202 | EXPECT_EQ(a, b); 203 | EXPECT_NE(a.get_allocator().resource(), b.get_allocator().resource()); 204 | } 205 | 206 | TYPED_TEST(stacktree_test, constructor_move_constructor_allocator) 207 | { 208 | auto a = this->construct_0(std::from_range, std::vector{ this->stacktrace_0 }); 209 | this->validate_stacktrace_0(a); 210 | auto b = this->construct_1(std::move(a)); 211 | this->validate_stacktrace_0(b); 212 | 213 | // In an element-wise move other is not guaranteed to be empty after the move. 214 | 215 | // EXPECT_TRUE(a.empty()); 216 | // EXPECT_EQ(a.begin(), a.end()); 217 | 218 | // EXPECT_NE(a, b); 219 | EXPECT_NE(a.get_allocator().resource(), b.get_allocator().resource()); 220 | } 221 | 222 | TYPED_TEST(stacktree_test, constructor_iterator_pair) 223 | { 224 | std::vector v{ this->stacktrace_0, this->stacktrace_1 }; 225 | auto a = this->construct_0(std::begin(v), std::end(v)); 226 | this->validate_stacktrace_01(a); 227 | } 228 | 229 | TYPED_TEST(stacktree_test, constructor_initializer_list) 230 | { 231 | std::initializer_list il{ this->stacktrace_0, this->stacktrace_1 }; 232 | auto a = this->construct_0(il); 233 | this->validate_stacktrace_01(a); 234 | } 235 | 236 | TYPED_TEST(stacktree_test, constructor_from_range) 237 | { 238 | std::vector v{ this->stacktrace_0, this->stacktrace_1 }; 239 | auto a = this->construct_0(std::from_range, v); 240 | this->validate_stacktrace_01(a); 241 | } 242 | 243 | TYPED_TEST(stacktree_test, assignment_op_copy) 244 | { 245 | fox::stacktree a{ this->stacktrace_0 }; 246 | this->validate_stacktrace_0(a); 247 | fox::stacktree b{this->stacktrace_1}; 248 | this->validate_stacktrace_1(b); 249 | b = a; 250 | this->validate_stacktrace_0(a); 251 | this->validate_stacktrace_0(b); 252 | 253 | EXPECT_EQ(a, b); 254 | } 255 | 256 | TYPED_TEST(stacktree_test, assignment_op_move) 257 | { 258 | fox::stacktree a{ this->stacktrace_0 }; 259 | this->validate_stacktrace_0(a); 260 | fox::stacktree b{ this->stacktrace_1 }; 261 | this->validate_stacktrace_1(b); 262 | b = std::move(a); 263 | this->validate_stacktrace_0(b); 264 | 265 | EXPECT_TRUE(a.empty()); 266 | EXPECT_EQ(a.begin(), a.end()); 267 | 268 | EXPECT_NE(a, b); 269 | } 270 | 271 | TYPED_TEST(stacktree_test, assignment_op_il) 272 | { 273 | fox::stacktree b{ this->stacktrace_1 }; 274 | this->validate_stacktrace_1(b); 275 | b = std::initializer_list{ this->stacktrace_0 }; 276 | this->validate_stacktrace_0(b); 277 | } 278 | 279 | TYPED_TEST(stacktree_test, assign_il) 280 | { 281 | fox::stacktree b{ this->stacktrace_1 }; 282 | this->validate_stacktrace_1(b); 283 | b.assign(std::initializer_list{ this->stacktrace_0 }); 284 | this->validate_stacktrace_0(b); 285 | } 286 | 287 | TYPED_TEST(stacktree_test, assign_iterator_pair) 288 | { 289 | fox::stacktree b{ this->stacktrace_1 }; 290 | this->validate_stacktrace_1(b); 291 | auto il = std::initializer_list{ this->stacktrace_0 }; 292 | b.assign(std::begin(il), std::end(il)); 293 | this->validate_stacktrace_0(b); 294 | } 295 | 296 | TYPED_TEST(stacktree_test, assign_range) 297 | { 298 | fox::stacktree b{ this->stacktrace_1 }; 299 | this->validate_stacktrace_1(b); 300 | b.assign_range(std::initializer_list{ this->stacktrace_0 }); 301 | this->validate_stacktrace_0(b); 302 | } 303 | 304 | TYPED_TEST(stacktree_test, get_allocator) 305 | { 306 | { 307 | auto a = this->construct_0(); 308 | EXPECT_EQ(a.get_allocator(), this->alloc_0); 309 | } 310 | 311 | { 312 | fox::stacktree a; 313 | EXPECT_EQ(a.get_allocator(), std::allocator{}); 314 | } 315 | } 316 | 317 | TYPED_TEST(stacktree_test, begin_end) 318 | { 319 | { 320 | auto a = this->construct_0(); 321 | auto b = a.begin(); 322 | auto e = a.end(); 323 | 324 | EXPECT_TRUE((std::indirectly_writable)); 325 | EXPECT_TRUE((std::indirectly_writable)); 326 | 327 | EXPECT_TRUE(std::indirectly_readable); 328 | EXPECT_TRUE(std::indirectly_readable); 329 | } 330 | 331 | { 332 | auto a = this->construct_0(); 333 | auto b = a.cbegin(); 334 | auto e = a.cend(); 335 | 336 | EXPECT_FALSE((std::indirectly_writable)); 337 | EXPECT_FALSE((std::indirectly_writable)); 338 | 339 | EXPECT_TRUE(std::indirectly_readable); 340 | EXPECT_TRUE(std::indirectly_readable); 341 | } 342 | 343 | { 344 | const auto a = this->construct_0(); 345 | auto b = a.begin(); 346 | auto e = a.end(); 347 | 348 | EXPECT_FALSE((std::indirectly_writable)); 349 | EXPECT_FALSE((std::indirectly_writable)); 350 | 351 | EXPECT_TRUE(std::indirectly_readable); 352 | EXPECT_TRUE(std::indirectly_readable); 353 | } 354 | } 355 | 356 | TYPED_TEST(stacktree_test, iterator_operator_dereference) 357 | { 358 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 359 | 360 | auto b = a.begin(); 361 | auto e = a.end(); 362 | 363 | auto entry = *b; 364 | EXPECT_EQ( 365 | static_cast(entry), 366 | *this->stacktrace_0.rbegin() 367 | ); 368 | 369 | // Accessing invalid 370 | EXPECT_ANY_THROW(*e); 371 | } 372 | 373 | TYPED_TEST(stacktree_test, iterator_operator_arrow) 374 | { 375 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 376 | 377 | auto b = a.begin(); 378 | auto e = a.end(); 379 | 380 | auto entry = b->native_handle(); 381 | EXPECT_EQ( 382 | entry, 383 | this->stacktrace_0.rbegin()->native_handle() 384 | ); 385 | 386 | // Accessing invalid 387 | EXPECT_ANY_THROW(e->native_handle()); 388 | } 389 | 390 | TYPED_TEST(stacktree_test, iterator_operator_subscript) 391 | { 392 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 393 | 394 | auto b = a.begin(); 395 | auto e = a.end(); 396 | 397 | auto entry = b[0]; 398 | EXPECT_EQ( 399 | static_cast(entry), 400 | *this->stacktrace_0.rbegin() 401 | ); 402 | 403 | EXPECT_NO_THROW(e[-1]); 404 | 405 | // Accessing invalid 406 | EXPECT_ANY_THROW(e[0]); 407 | EXPECT_ANY_THROW(b[1]); 408 | EXPECT_ANY_THROW(b[-1]); 409 | } 410 | 411 | TYPED_TEST(stacktree_test, iterator_operator_preincrement) 412 | { 413 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 414 | 415 | auto b = a.begin(); 416 | auto e = a.end(); 417 | ++b; 418 | EXPECT_EQ(b, e); 419 | 420 | // Accessing invalid 421 | EXPECT_ANY_THROW(++b); 422 | EXPECT_ANY_THROW(++e); 423 | } 424 | 425 | TYPED_TEST(stacktree_test, iterator_operator_postincrement) 426 | { 427 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 428 | 429 | auto b = a.begin(); 430 | auto e = a.end(); 431 | auto v = b++; 432 | EXPECT_NE(b, v); 433 | EXPECT_EQ(v, e); 434 | 435 | // Accessing invalid 436 | EXPECT_ANY_THROW((void)v++); 437 | EXPECT_ANY_THROW((void)e++); 438 | } 439 | 440 | TYPED_TEST(stacktree_test, iterator_operator_predecrement) 441 | { 442 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 443 | 444 | auto b = a.begin(); 445 | auto e = a.end(); 446 | --e; 447 | EXPECT_EQ(b, e); 448 | 449 | EXPECT_ANY_THROW(--b); 450 | } 451 | 452 | TYPED_TEST(stacktree_test, iterator_operator_postdecrement) 453 | { 454 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 455 | 456 | auto b = a.begin(); 457 | auto e = a.end(); 458 | auto v = e--; 459 | EXPECT_NE(e, v); 460 | EXPECT_EQ(v, b); 461 | 462 | EXPECT_ANY_THROW((void)b--); 463 | EXPECT_ANY_THROW((void)v--); 464 | } 465 | 466 | TYPED_TEST(stacktree_test, iterator_operator_eqaulity) 467 | { 468 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 469 | 470 | auto b = a.begin(); 471 | auto e = a.end(); 472 | EXPECT_FALSE(b == e); 473 | EXPECT_TRUE(b == b); 474 | } 475 | 476 | TYPED_TEST(stacktree_test, iterator_operator_ineqaulity) 477 | { 478 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 479 | 480 | auto b = a.begin(); 481 | auto e = a.end(); 482 | EXPECT_TRUE(b != e); 483 | EXPECT_FALSE(b != b); 484 | } 485 | 486 | TYPED_TEST(stacktree_test, iterator_operator_add_assign) 487 | { 488 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 489 | 490 | auto b = a.begin(); 491 | auto e = a.end(); 492 | b += 1; 493 | EXPECT_EQ(b, e); 494 | 495 | // Accessing invalid 496 | EXPECT_ANY_THROW(b += 1); 497 | EXPECT_ANY_THROW(e += 2); 498 | } 499 | 500 | TYPED_TEST(stacktree_test, iterator_operator_add_0) 501 | { 502 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 503 | 504 | auto b = a.begin(); 505 | auto e = a.end(); 506 | auto d = b + 1; 507 | EXPECT_EQ(b + 1, e); 508 | EXPECT_EQ(d, e); 509 | EXPECT_EQ(b, e + (-1)); 510 | 511 | // Accessing invalid 512 | EXPECT_ANY_THROW(d + 1); 513 | EXPECT_ANY_THROW(d + 2); 514 | EXPECT_ANY_THROW(b + 2); 515 | EXPECT_ANY_THROW(e + 1); 516 | EXPECT_ANY_THROW(e + (-2)); 517 | } 518 | 519 | TYPED_TEST(stacktree_test, iterator_operator_add_1) 520 | { 521 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 522 | 523 | auto b = a.begin(); 524 | auto e = a.end(); 525 | auto d = 1 + b; 526 | EXPECT_EQ(1 + b, e); 527 | EXPECT_EQ(d, e); 528 | EXPECT_EQ(b, (-1) + e); 529 | 530 | // Accessing invalid 531 | EXPECT_ANY_THROW(1 + d); 532 | EXPECT_ANY_THROW(2 + d); 533 | EXPECT_ANY_THROW(2 + b); 534 | EXPECT_ANY_THROW(1 + e); 535 | EXPECT_ANY_THROW((-2) + e); 536 | } 537 | 538 | TYPED_TEST(stacktree_test, iterator_operator_substract_assign) 539 | { 540 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 541 | 542 | auto b = a.begin(); 543 | auto e = a.end(); 544 | e -= 1; 545 | EXPECT_EQ(b, e); 546 | 547 | // Accessing invalid 548 | EXPECT_ANY_THROW(b -= 1); 549 | EXPECT_ANY_THROW(e -= 2); 550 | } 551 | 552 | TYPED_TEST(stacktree_test, iterator_operator_sub) 553 | { 554 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 555 | 556 | auto b = a.begin(); 557 | auto e = a.end(); 558 | auto d = e - 1; 559 | EXPECT_EQ(b, e - 1); 560 | EXPECT_EQ(d, b); 561 | EXPECT_EQ(b - (-1), e); 562 | 563 | // Accessing invalid 564 | EXPECT_ANY_THROW(d - 1); 565 | EXPECT_ANY_THROW(d - 2); 566 | EXPECT_ANY_THROW(e - 2); 567 | EXPECT_ANY_THROW(b - 1); 568 | EXPECT_ANY_THROW(b - (-2)); 569 | } 570 | 571 | TYPED_TEST(stacktree_test, iterator_operator_diff) 572 | { 573 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 574 | 575 | auto b = a.begin(); 576 | auto e = a.end(); 577 | 578 | EXPECT_EQ(b - b, 0); 579 | EXPECT_EQ(b - e, -1); 580 | EXPECT_EQ(e - b, 1); 581 | EXPECT_EQ(e - e, 0); 582 | } 583 | 584 | TYPED_TEST(stacktree_test, iterator_operator_less) 585 | { 586 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 587 | 588 | auto b = a.begin(); 589 | auto e = a.end(); 590 | 591 | EXPECT_TRUE(b < e); 592 | EXPECT_FALSE(b < b); 593 | EXPECT_FALSE(e < b); 594 | EXPECT_FALSE(e < e); 595 | } 596 | 597 | TYPED_TEST(stacktree_test, iterator_operator_less_eq) 598 | { 599 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 600 | 601 | auto b = a.begin(); 602 | auto e = a.end(); 603 | 604 | EXPECT_TRUE(b <= e); 605 | EXPECT_TRUE(b <= b); 606 | EXPECT_FALSE(e <= b); 607 | EXPECT_TRUE(e <= e); 608 | } 609 | 610 | TYPED_TEST(stacktree_test, iterator_operator_greater) 611 | { 612 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 613 | 614 | auto b = a.begin(); 615 | auto e = a.end(); 616 | 617 | EXPECT_FALSE(b > e); 618 | EXPECT_FALSE(b > b); 619 | EXPECT_TRUE(e > b); 620 | EXPECT_FALSE(e > e); 621 | } 622 | 623 | TYPED_TEST(stacktree_test, iterator_operator_greater_eq) 624 | { 625 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 626 | 627 | auto b = a.begin(); 628 | auto e = a.end(); 629 | 630 | EXPECT_FALSE(b >= e); 631 | EXPECT_TRUE(b >= b); 632 | EXPECT_TRUE(e >= b); 633 | EXPECT_TRUE(e >= e); 634 | } 635 | 636 | TYPED_TEST(stacktree_test, iterator_child) 637 | { 638 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 639 | 640 | auto b = a.begin(); 641 | auto e = a.end(); 642 | 643 | auto cb = b.begin_child(); 644 | auto ce = b.end_child(); 645 | 646 | EXPECT_NE(cb, ce); 647 | 648 | auto entry = *cb; 649 | EXPECT_EQ( 650 | static_cast(entry), 651 | *(this->stacktrace_0.rbegin() + 1) 652 | ); 653 | } 654 | 655 | TYPED_TEST(stacktree_test, iterator_parent) 656 | { 657 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 658 | 659 | auto b = a.begin(); 660 | auto e = a.end(); 661 | 662 | auto cb = b.begin_child(); 663 | auto ce = b.end_child(); 664 | 665 | auto pb = cb.begin_parent(); 666 | auto pe = cb.end_parent(); 667 | 668 | EXPECT_NE(pb, pe); 669 | EXPECT_EQ(pb, b); 670 | EXPECT_EQ(pe, e); 671 | } 672 | 673 | TYPED_TEST(stacktree_test, insert_0) 674 | { 675 | auto a = this->construct_0(); 676 | a.insert(this->stacktrace_0, 677 | [this, i = this->stacktrace_0.rbegin()](typename decltype(a)::value_type& v) mutable 678 | { 679 | EXPECT_TRUE((v == *(i++))); 680 | } 681 | ); 682 | 683 | this->validate_stacktrace_0(a); 684 | } 685 | 686 | TYPED_TEST(stacktree_test, insert_1) 687 | { 688 | auto a = this->construct_0(); 689 | a.insert(this->stacktrace_0); 690 | this->validate_stacktrace_0(a); 691 | } 692 | 693 | TYPED_TEST(stacktree_test, insert_2) 694 | { 695 | auto a = this->construct_0(); 696 | std::initializer_list v{ this->stacktrace_0, this->stacktrace_1 }; 697 | a.insert(std::begin(v), std::end(v)); 698 | this->validate_stacktrace_01(a); 699 | } 700 | 701 | TYPED_TEST(stacktree_test, insert_3) 702 | { 703 | auto a = this->construct_0(); 704 | std::initializer_list v{ this->stacktrace_0, this->stacktrace_1 }; 705 | a.insert(v); 706 | this->validate_stacktrace_01(a); 707 | } 708 | 709 | TYPED_TEST(stacktree_test, insert_range) 710 | { 711 | auto a = this->construct_0(); 712 | std::initializer_list v{ this->stacktrace_0, this->stacktrace_1 }; 713 | a.insert_range(v); 714 | this->validate_stacktrace_01(a); 715 | } 716 | 717 | TYPED_TEST(stacktree_test, erase_0) 718 | { 719 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 720 | auto pos = a.begin().begin_child().begin_child(); 721 | a.erase(pos); 722 | EXPECT_EQ((a.begin().begin_child().begin_child()), (a.begin().begin_child().end_child())); 723 | } 724 | 725 | TYPED_TEST(stacktree_test, erase_1) 726 | { 727 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 728 | auto b = a.begin().begin_child().begin_child(); 729 | auto e = a.begin().begin_child().end_child(); 730 | a.erase(b, e); 731 | EXPECT_EQ((a.begin().begin_child().begin_child()), (a.begin().begin_child().end_child())); 732 | } 733 | 734 | TYPED_TEST(stacktree_test, empty) 735 | { 736 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 737 | EXPECT_FALSE(a.empty()); 738 | auto b = this->construct_1(); 739 | EXPECT_TRUE(b.empty()); 740 | } 741 | 742 | TYPED_TEST(stacktree_test, clear) 743 | { 744 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 745 | EXPECT_FALSE(a.empty()); 746 | a.clear(); 747 | EXPECT_TRUE(a.empty()); 748 | auto b = this->construct_1(); 749 | EXPECT_TRUE(b.empty()); 750 | a.clear(); 751 | EXPECT_TRUE(b.empty()); 752 | } 753 | 754 | TYPED_TEST(stacktree_test, op_eq) 755 | { 756 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 757 | auto b = this->construct_1(std::initializer_list{ this->stacktrace_1 }); 758 | auto c = this->construct_1(std::initializer_list{ this->stacktrace_1 }); 759 | 760 | EXPECT_FALSE(a == b); 761 | EXPECT_FALSE(a == c); 762 | EXPECT_TRUE(b == c); 763 | EXPECT_TRUE(a == a); 764 | EXPECT_TRUE(b == b); 765 | } 766 | 767 | TYPED_TEST(stacktree_test, op_neq) 768 | { 769 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 770 | auto b = this->construct_1(std::initializer_list{ this->stacktrace_1 }); 771 | auto c = this->construct_1(std::initializer_list{ this->stacktrace_1 }); 772 | 773 | EXPECT_TRUE(a != b); 774 | EXPECT_TRUE(a != c); 775 | EXPECT_FALSE(b != c); 776 | EXPECT_FALSE(a != a); 777 | EXPECT_FALSE(b != b); 778 | } 779 | 780 | TYPED_TEST(stacktree_test, swap) 781 | { 782 | auto a = this->construct_0(std::initializer_list{ this->stacktrace_0 }); 783 | auto b = this->construct_1(std::initializer_list{ this->stacktrace_1 }); 784 | 785 | auto a_old = a; 786 | auto b_old = b; 787 | 788 | a.swap(b); 789 | EXPECT_EQ(a_old, b); 790 | EXPECT_EQ(b_old, a); 791 | EXPECT_NE(a_old, a); 792 | EXPECT_NE(b_old, b); 793 | } 794 | } 795 | 796 | #ifdef _MSC_VER 797 | #pragma warning( pop ) 798 | #endif --------------------------------------------------------------------------------