From e0aff50eacb7d18cc64ddf93d304f558f7cc3257 Mon Sep 17 00:00:00 2001 From: The Fern Date: Sun, 19 Mar 2023 20:02:55 +0100 Subject: [PATCH 1/2] Add better stream output to overflow allocator Update README --- .github/workflows/docs.yml | 3 ++ .github/workflows/main.yml | 1 + README.md | 3 ++ include/ktl/allocators/overflow.h | 83 ++++++++++++++++++++++++++++--- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d7e8669..8644c01 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,6 +1,9 @@ name: Docs on: + push: + branches: [ master ] + paths: '**/*.md' workflow_dispatch: jobs: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6178505..cc73e68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,6 +8,7 @@ on: paths-ignore: - '**/*.md' - '**/*.gitignore' + - '**/Doxyfile' jobs: Build: diff --git a/README.md b/README.md index d9c9d43..b4ce1c9 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,9 @@ This library also contains various containers that are STL compliant. | `size_t size() const` | Returns the current amount of elements in the vector. | # Allocator Examples +The following examples all have `using namespace ktl` or equivalent at the top for brevity. +These examples can all be seen as unit tests in [`src/test/exotic_allocator_test.cpp`](https://github.com/KredeGC/KTL/tree/master/src/test/exotic_allocator_test.cpp). + Create an allocator which will attempt to use a linear allocator for allocation, but fall back on malloc when full. ```cpp // Create the allocator from some 8kb buffer and straight malloc diff --git a/include/ktl/allocators/overflow.h b/include/ktl/allocators/overflow.h index e85a4b2..f4ec6ba 100644 --- a/include/ktl/allocators/overflow.h +++ b/include/ktl/allocators/overflow.h @@ -22,11 +22,15 @@ namespace ktl typedef typename detail::get_size_type_t size_type; private: - static constexpr int OVERFLOW_PATTERN = 0b01010101010101010101010101010101; - static constexpr int64_t OVERFLOW_TEST = 0b0101010101010101010101010101010101010101010101010101010101010101; - static constexpr size_t OVERFLOW_SIZE = 8; + static constexpr unsigned int OVERFLOW_PATTERN = 0b10100101101001011010010110100101; + static constexpr unsigned char OVERFLOW_TEST = 0b10100101; + static constexpr size_t OVERFLOW_SIZE = 64; public: + /** + * @brief Construct the allocator with a reference to a stream object + * @param stream The stream to use when leaks or corruption happens + */ explicit overflow(Stream& stream) noexcept : m_Stream(stream), m_Alloc(), @@ -85,6 +89,13 @@ namespace ktl } #pragma region Allocation + /** + * @brief Attempts to allocate a chunk of memory defined by @p n + * @note Allocates 64 bytes more on either side of the returned address. + * This memory will be used for overflow checking. + * @param n The amount of bytes to allocate memory for + * @return A location in memory that is at least @p n bytes big or nullptr if it could not be allocated + */ void* allocate(size_type n) { m_Allocs += n; @@ -101,6 +112,11 @@ namespace ktl return ptr + OVERFLOW_SIZE; } + /** + * @brief Attempts to deallocate the memory at location @p p + * @param p The location in memory to deallocate + * @param n The size that was initially allocated + */ void deallocate(void* p, size_type n) { KTL_ASSERT(p != nullptr); @@ -109,14 +125,34 @@ namespace ktl if (p) { - char* ptr = reinterpret_cast(p); + unsigned char* ptr = reinterpret_cast(p); + + size_t before = 0; + size_t after = 0; // Check against corruption - if (std::memcmp(ptr + n, &OVERFLOW_TEST, OVERFLOW_SIZE) != 0) - m_Stream << "--------MEMORY CORRUPTION DETECTED--------\nThe area around " << reinterpret_cast(ptr + n) << " has been illegally modified\n"; + for (unsigned char* i = ptr - 1; i >= ptr - OVERFLOW_SIZE; i--) + { + if (*i != OVERFLOW_TEST) + before = ptr - i; + } + + for (unsigned char* i = ptr + n; i < ptr + n + OVERFLOW_SIZE; i++) + { + if (*i != OVERFLOW_TEST) + after = i - ptr - n + 1; + } + + if (before || after) + { + m_Stream << "--------MEMORY CORRUPTION DETECTED--------\nThe area around " << p << " (" << n << " bytes) has been illegally modified\n"; - if (std::memcmp(ptr - OVERFLOW_SIZE, &OVERFLOW_TEST, OVERFLOW_SIZE) != 0) - m_Stream << "--------MEMORY CORRUPTION DETECTED--------\nThe area around " << reinterpret_cast(ptr - OVERFLOW_SIZE) << " has been illegally modified\n"; + if (before) + m_Stream << " Before (" << before << " bytes)\n"; + + if (after) + m_Stream << " After (" << after << " bytes)\n"; + } size_type size = n + OVERFLOW_SIZE * 2; m_Alloc.deallocate(ptr - OVERFLOW_SIZE, size); @@ -125,6 +161,13 @@ namespace ktl #pragma endregion #pragma region Construction + /** + * @brief Constructs an object of T with the given @p ...args at the given location + * @note Keeps track of the number of constructions + * @tparam ...Args The types of the arguments + * @param p The location of the object in memory + * @param ...args A range of arguments to use to construct the object + */ template void construct(T* p, Args&&... args) { @@ -136,6 +179,11 @@ namespace ktl ::new(p) T(std::forward(args)...); } + /** + * @brief Destructs an object of T at the given location + * @note Keeps track of the number of destructions + * @param p The location of the object in memory + */ template void destroy(T* p) { @@ -149,6 +197,11 @@ namespace ktl #pragma endregion #pragma region Utility + /** + * @brief Returns the maximum size that an allocation can be + * @note Only defined if the underlying allocator defines it + * @return The maximum size an allocation may be + */ template typename std::enable_if, size_type>::type max_size() const noexcept @@ -156,6 +209,12 @@ namespace ktl return m_Alloc.max_size(); } + /** + * @brief Returns whether or not the allocator owns the given location in memory + * @note Only defined if the underlying allocator defines it + * @param p The location of the object in memory + * @return Whether the allocator owns @p p + */ template typename std::enable_if, bool>::type owns(void* p) const @@ -164,11 +223,19 @@ namespace ktl } #pragma endregion + /** + * @brief Returns a reference to the underlying allocator + * @return The allocator + */ Alloc& get_allocator() { return m_Alloc; } + /** + * @brief Returns a const reference to the underlying allocator + * @return The allocator + */ const Alloc& get_allocator() const { return m_Alloc; From adb0471da536041bcfdbe23b2a6d26b6e704ed3a Mon Sep 17 00:00:00 2001 From: The Fern Date: Wed, 29 Mar 2023 15:54:55 +0200 Subject: [PATCH 2/2] Add basic packed_ptr type and test --- include/ktl/containers/packed_ptr.h | 70 +++++++++++++++++++++++++ include/ktl/containers/packed_ptr_fwd.h | 9 ++++ include/ktl/containers/trivial_vector.h | 6 ++- include/ktl/ktl.h | 1 + include/ktl/utility/bits.h | 20 +++++++ src/test/packed_ptr_test.cpp | 47 +++++++++++++++++ 6 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 include/ktl/containers/packed_ptr.h create mode 100644 include/ktl/containers/packed_ptr_fwd.h create mode 100644 include/ktl/utility/bits.h create mode 100644 src/test/packed_ptr_test.cpp diff --git a/include/ktl/containers/packed_ptr.h b/include/ktl/containers/packed_ptr.h new file mode 100644 index 0000000..7abb09f --- /dev/null +++ b/include/ktl/containers/packed_ptr.h @@ -0,0 +1,70 @@ +#pragma once + +#include "../utility/assert.h" +#include "../utility/bits.h" +#include "packed_ptr_fwd.h" + +#include + +namespace ktl +{ + /** + * @brief A packed pointer-like type which takes advantage of any bits that don't get used due to alignment + * @tparam PtrT The pointer type to use + */ + template + class packed_ptr + { + public: + static constexpr uintmax_t FREE_BITS = detail::log2(Alignment); + + private: + static_assert(Bits <= FREE_BITS, "The number of bits in use cannot surpass the number of free bits"); + + static constexpr uintptr_t INT_MASK = ((1ULL << Bits) - 1); + static constexpr uintptr_t PTR_MASK = ~((1ULL << FREE_BITS) - 1); + + public: + packed_ptr() noexcept : + m_Value(reinterpret_cast(nullptr)) {} + + template + explicit packed_ptr(PtrT p, Int value) noexcept : + m_Value((reinterpret_cast(p) & PTR_MASK) | (static_cast(value) & INT_MASK)) + { + // Pointer must be correctly aligned + KTL_ASSERT((reinterpret_cast(p) & (Alignment - 1)) == 0); + } + + operator bool() const noexcept { return m_Value; } + + PtrT get_ptr() const noexcept { return reinterpret_cast(m_Value & PTR_MASK); } + + template + Int get_int() const noexcept + { + static_assert(std::is_unsigned_v, "Packed integer must be unsigned"); + + return static_cast(m_Value & INT_MASK); + } + + void set_ptr(PtrT p) noexcept + { + // Pointer must be correctly aligned + KTL_ASSERT((reinterpret_cast(p) & (Alignment - 1)) == 0); + + m_Value = (reinterpret_cast(p) & PTR_MASK) | (m_Value & INT_MASK); + } + + template + void set_int(Int value) noexcept + { + static_assert(std::is_unsigned_v, "Packed integer must be unsigned"); + + m_Value = (m_Value & PTR_MASK) | (static_cast(value) & INT_MASK); + } + + private: + uintptr_t m_Value; + }; +} \ No newline at end of file diff --git a/include/ktl/containers/packed_ptr_fwd.h b/include/ktl/containers/packed_ptr_fwd.h new file mode 100644 index 0000000..22334c2 --- /dev/null +++ b/include/ktl/containers/packed_ptr_fwd.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace ktl +{ + template + class packed_ptr; +} \ No newline at end of file diff --git a/include/ktl/containers/trivial_vector.h b/include/ktl/containers/trivial_vector.h index 4858750..d194b60 100644 --- a/include/ktl/containers/trivial_vector.h +++ b/include/ktl/containers/trivial_vector.h @@ -403,13 +403,13 @@ namespace ktl /** * @brief Erases all elements in a range. - * @param first An iterator pointing to the element first. + * @param first An iterator pointing to the first element. * @param last An iterator pointing to the location after the last element. * @return An iterator pointing to the element immidiately after the erased ones. */ iterator erase(const_iterator first, const_iterator last) noexcept { - KTL_ASSERT(first >= last); + KTL_ASSERT(first <= last); KTL_ASSERT(first >= m_Begin && last <= m_End); std::memmove(const_cast(first), last, (m_End - last) * sizeof(T)); @@ -462,5 +462,7 @@ namespace ktl T* m_Begin; T* m_End; T* m_EndMax; + + //std::conditional_t m_Data; }; } \ No newline at end of file diff --git a/include/ktl/ktl.h b/include/ktl/ktl.h index 9f0d47d..026d419 100644 --- a/include/ktl/ktl.h +++ b/include/ktl/ktl.h @@ -16,6 +16,7 @@ // Containers #include "containers/binary_heap.h" +#include "containers/packed_ptr.h" #include "containers/trivial_array.h" #include "containers/trivial_buffer.h" #include "containers/trivial_vector.h" \ No newline at end of file diff --git a/include/ktl/utility/bits.h b/include/ktl/utility/bits.h new file mode 100644 index 0000000..0fd6d26 --- /dev/null +++ b/include/ktl/utility/bits.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace ktl::detail +{ + constexpr inline uintmax_t log2(uintmax_t n) + { + uintmax_t r = 0; + + if (n >> 32) { r += 32U; n >>= 32U; } + if (n >> 16) { r += 16U; n >>= 16U; } + if (n >> 8) { r += 8U; n >>= 8U; } + if (n >> 4) { r += 4U; n >>= 4U; } + if (n >> 2) { r += 2U; n >>= 2U; } + if (n >> 1) { r += 1U; n >>= 1U; } + + return r; + } +} \ No newline at end of file diff --git a/src/test/packed_ptr_test.cpp b/src/test/packed_ptr_test.cpp new file mode 100644 index 0000000..430a354 --- /dev/null +++ b/src/test/packed_ptr_test.cpp @@ -0,0 +1,47 @@ +#include "shared/assert_utility.h" +#include "shared/test.h" +#include "shared/types.h" + +#define KTL_DEBUG_ASSERT +#include "ktl/containers/packed_ptr.h" + +#include + +// Naming scheme: packed_ptr +// Contains tests that use the ktl::packed_ptr + +namespace ktl::test::packed_ptr +{ + KTL_ADD_TEST(test_packed_ptr_stack) + { + int t; + int* in_ptr = &t; + uint16_t in_value = 2; + + ktl::packed_ptr pack(in_ptr, in_value); + + KTL_TEST_ASSERT(pack); + KTL_TEST_ASSERT(in_ptr == pack.get_ptr()); + KTL_TEST_ASSERT(in_value == pack.get_int()); + } + + KTL_ADD_TEST(test_packed_ptr_malloc) + { + std::allocator alloc; + + double* in_ptr = alloc.allocate(1); + uint64_t in_value = 2; + + // Using more than 2 bits may require full x64 support + ktl::packed_ptr pack; + + pack.set_ptr(in_ptr); + pack.set_int(in_value); + + KTL_TEST_ASSERT(pack); + KTL_TEST_ASSERT(in_ptr == pack.get_ptr()); + KTL_TEST_ASSERT(in_value == pack.get_int()); + + alloc.deallocate(in_ptr, 1); + } +} \ No newline at end of file