diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ec50af..689f61e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,4 +49,4 @@ if(${IS_TOPLEVEL_PROJECT}) endif() endif() -# TODO: add install target \ No newline at end of file +# TODO: add install target diff --git a/include/kf/EarlyAllocator.h b/include/kf/EarlyAllocator.h index 0780bbd..ff5a5ec 100644 --- a/include/kf/EarlyAllocator.h +++ b/include/kf/EarlyAllocator.h @@ -27,14 +27,16 @@ namespace kf { } - _CONSTEXPR20_DYNALLOC ~EarlyAllocator() = default; - _CONSTEXPR20_DYNALLOC EarlyAllocator& operator=(const EarlyAllocator&) = default; + ~EarlyAllocator() noexcept = default; + EarlyAllocator& operator=(const EarlyAllocator&) noexcept = default; template - T* initialize(const size_t count) + T* initialize(const size_t count) noexcept { - assert(!m_ptr); - assert(!m_size); + if (m_ptr || m_size) + { + _Xinvalid_argument("m_ptr || m_size"); + } m_size = count * sizeof(T); m_ptr = static_cast(operator new(m_size, poolType)); @@ -42,7 +44,7 @@ namespace kf return m_ptr; } - _CONSTEXPR20_DYNALLOC void deallocate(const T* ptr, const size_t count) + void deallocate(const T* ptr, const size_t count) noexcept { if (ptr != m_ptr || count * sizeof(T) > m_size) { @@ -54,7 +56,7 @@ namespace kf m_size = 0; } - _NODISCARD _CONSTEXPR20_DYNALLOC T* allocate(const size_t count) + _NODISCARD T* allocate(const size_t count) noexcept { if (!m_ptr || count * sizeof(T) > m_size) { diff --git a/include/kf/MapAllocator.h b/include/kf/MapAllocator.h new file mode 100644 index 0000000..439a912 --- /dev/null +++ b/include/kf/MapAllocator.h @@ -0,0 +1,91 @@ +#pragma once +#include + +namespace kf +{ + // TODO: refactor + + struct AllocatorState + { + std::unique_ptr buffer; + size_t size; + }; + + template + class MapAllocator + { + public: + using value_type = T; + + MapAllocator() = default; + + // Needed because allocator has extra parameter for POOL_TYPE + template + struct rebind + { + using other = MapAllocator; + }; + + friend class MapAllocator; + + template + MapAllocator(const MapAllocator& other) + : m_state(other.m_state) + { + } + + [[nodiscard]] bool initialize() + { + m_state = make_shared(); + return m_state != nullptr; + } + + T* allocate(std::size_t n) + { + if (!m_state || !m_state->buffer) + { + return nullptr; + } + + if (m_state->size < n) + { + return nullptr; + } + + auto buffer = reinterpret_cast(m_state->buffer.release()); + m_state->size = 0; + return buffer; + } + + void deallocate(T*, std::size_t) + { + m_state->buffer.reset(); + m_state->size = 0; + } + + [[nodiscard]] bool prepareMemory(std::size_t size) + { + if (!m_state) + { + return false; + } + + if (m_state->buffer) + { + return true; + } + + m_state->buffer.reset(new(poolType) char[size]); + if (!m_state->buffer) + { + return false; + } + m_state->size = size; + + return true; + } + + private: + std::shared_ptr m_state; + }; +} diff --git a/include/kf/UString.h b/include/kf/UString.h index f71940a..f578898 100644 --- a/include/kf/UString.h +++ b/include/kf/UString.h @@ -75,6 +75,7 @@ namespace kf if (newByteLength > 0) { +#pragma warning(suppress : 4996) // ExAllocatePoolWithTag is deprecated, use ExAllocatePool2 #pragma warning(suppress: 28160) // Must succeed pool allocations are forbidden. Allocation failures cause a system crash. newBuffer = ::ExAllocatePoolWithTag(poolType, newByteLength, PoolTag); diff --git a/include/kf/stl/map b/include/kf/stl/map new file mode 100644 index 0000000..57744c8 --- /dev/null +++ b/include/kf/stl/map @@ -0,0 +1,173 @@ +#pragma once +#include +#include +#include + +#if _ITERATOR_DEBUG_LEVEL > 0 +#error "_ITERATOR_DEBUG_LEVEL must not be greater than 0" +#endif + +namespace kf +{ + ////////////////////////////////////////////////////////////////////////// + // map - wraps std::map and allows using it in exception-free environments. + // + // Note: some methods return NTSTATUS or std::optional to indicate an error + // thus they differ from std::map! + + template> + class map + { + public: + using allocator_type = MapAllocator, poolType>; + using map_type = std::map; + using key_type = map_type::key_type; + using mapped_type = map_type::mapped_type; + using value_type = map_type::value_type; + using size_type = map_type::size_type; + using iterator = map_type::iterator; + using const_iterator = map_type::const_iterator; + + public: + map() = default; + + map(const map&) = delete; + map& operator=(const map&) = delete; + + map(map&& other) = default; + map& operator=(map&& other) = default; + + [[nodiscard]] NTSTATUS initialize() + { + allocator_type allocator; + if (!allocator.initialize()) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Prepare memory for head node + if (!allocator.prepareMemory(kNodeSize)) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + m_internalMap = make_unique(allocator); + if (!m_internalMap) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + return STATUS_SUCCESS; + } + + std::optional> operator[](KeyType&& key) + { + if (!m_internalMap->get_allocator().prepareMemory(kNodeSize)) + { + return {}; + } + + return std::ref((*m_internalMap)[std::forward(key)]); + } + + template + std::optional> emplace(Args&&... values) + { + if (!m_internalMap->get_allocator().prepareMemory(kNodeSize)) + { + return {}; + } + + return m_internalMap->emplace(std::forward(values)...); + } + + [[nodiscard]] iterator find(const KeyType& key) + { + return m_internalMap->find(key); + } + + [[nodiscard]] const_iterator find(const KeyType& key) const + { + return m_internalMap->find(key); + } + + [[nodiscard]] bool contains(const KeyType& key) const + { + return m_internalMap->contains(key); + } + + void clear() noexcept + { + m_internalMap->clear(); + } + + iterator erase(const_iterator where) noexcept + { + return m_internalMap->erase(where); + } + + iterator erase(const_iterator first, const_iterator last) noexcept + { + return m_internalMap->erase(first, last); + } + + size_type erase(const KeyType& key) noexcept + { + return m_internalMap->erase(key); + } + + [[nodiscard]] iterator begin() noexcept + { + return m_internalMap->begin(); + } + + [[nodiscard]] const_iterator begin() const noexcept + { + return m_internalMap->begin(); + } + + [[nodiscard]] iterator end() noexcept + { + return m_internalMap->end(); + } + + [[nodiscard]] const_iterator end() const noexcept + { + return m_internalMap->end(); + } + + [[nodiscard]] iterator upper_bound(const KeyType& key) + { + return m_internalMap->upper_bound(key); + } + + [[nodiscard]] const_iterator upper_bound(const KeyType& key) const + { + return m_internalMap->upper_bound(key); + } + + [[nodiscard]] iterator lower_bound(const KeyType& key) + { + return m_internalMap->lower_bound(key); + } + + [[nodiscard]] const_iterator lower_bound(const KeyType& key) const + { + return m_internalMap->lower_bound(key); + } + + bool empty() const noexcept + { + return m_internalMap->empty(); + } + + size_type size() const noexcept + { + return m_internalMap->size(); + } + + private: + static constexpr size_t kNodeSize = sizeof(map_type::_Alnode_traits::value_type); + std::unique_ptr m_internalMap; + }; +} diff --git a/include/kf/stl/memory b/include/kf/stl/memory index 2192295..61ccd4c 100644 --- a/include/kf/stl/memory +++ b/include/kf/stl/memory @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include template diff --git a/include/kf/stl/new b/include/kf/stl/new index a0ce0e7..f217355 100644 --- a/include/kf/stl/new +++ b/include/kf/stl/new @@ -2,15 +2,15 @@ #pragma warning(push) #pragma warning(disable : 4595) // non-member operator new or delete functions may not be declared inline -#pragma warning(disable : 4996) // TODO: ExAllocatePoolWithTag is deprecated, use ExAllocatePool2 -inline void* __cdecl operator new(size_t size, POOL_TYPE poolType) +inline void* __cdecl operator new(size_t size, POOL_TYPE poolType) noexcept { #pragma warning(suppress: 28160) // Must succeed pool allocations are forbidden. Allocation failures cause a system crash. +#pragma warning(suppress: 4996) // ExAllocatePoolWithTag is deprecated, use ExAllocatePool2 return ::ExAllocatePoolWithTag(poolType, size ? size : 1, 'n++C'); } -inline void __cdecl operator delete(void* ptr) +inline void __cdecl operator delete(void* ptr) noexcept { if (ptr) { @@ -18,7 +18,7 @@ inline void __cdecl operator delete(void* ptr) } } -inline void __cdecl operator delete[](void* ptr) +inline void __cdecl operator delete[](void* ptr) noexcept { if (ptr) { @@ -27,7 +27,14 @@ inline void __cdecl operator delete[](void* ptr) } // size-aware deallocation -inline void __cdecl operator delete(void* ptr, size_t) +inline void __cdecl operator delete(void* ptr, size_t) noexcept +{ + operator delete(ptr); +} + +// To avoid warning C4291: 'void *operator new(size_t,POOL_TYPE) noexcept': +// no matching operator delete found; memory will not be freed if initialization throws an exception +inline void operator delete(void* ptr, POOL_TYPE) noexcept { operator delete(ptr); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8d38b77..0c2103b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -42,6 +42,7 @@ wdk_add_driver(kf-test WINVER NTDDI_WIN10 STL Bitmap.cpp BitmapRangeIterator.cpp HexTest.cpp + MapTest.cpp Vector.cpp ) diff --git a/test/MapTest.cpp b/test/MapTest.cpp new file mode 100644 index 0000000..026e4dc --- /dev/null +++ b/test/MapTest.cpp @@ -0,0 +1,149 @@ +#include "pch.h" +#include + +SCENARIO("Map test: insert and find integers") +{ + kf::map map; + REQUIRE_NT_SUCCESS(map.initialize()); + + THEN("Map is initialized") + { + REQUIRE_NT_SUCCESS(map.initialize()); + } + + THEN("Map is empty") + { + REQUIRE(map.empty()); + REQUIRE(map.size() == 0); + } + + const int kIterations = 5; // Number of iterations to insert + const int kKeyMultiplier = 2; // Multiplier for keys to ensure they are not equal to iteration index + + WHEN("Map is populated with integers") + { + for (int i = 0; i < kIterations; ++i) + { + auto result = map.emplace(i * kKeyMultiplier, i * kIterations); + REQUIRE(result.has_value()); + REQUIRE(result->second); + } + + THEN("It contains proper count of pairs") + { + REQUIRE(!map.empty()); + REQUIRE(map.size() == kIterations); + } + + THEN("All values are found by existing keys") + { + for (int i = 0; i < kIterations; i += kKeyMultiplier) + { + REQUIRE(map.contains(i * kKeyMultiplier)); + + auto value = map.find(i * kKeyMultiplier); + REQUIRE(value != map.end()); + REQUIRE((*value).first == i * kKeyMultiplier); + REQUIRE((*value).second == i * kIterations); + } + } + + THEN("The value for key greater than max key is not found") + { + auto value = map.find(kIterations * kKeyMultiplier + 1); + REQUIRE(value == map.end()); + } + + THEN("The begin is valid") + { + REQUIRE(map.begin()->first == 0); + REQUIRE(map.begin()->second == 0); + } + + THEN("The end is valid") + { + auto end = map.end(); + --end; + REQUIRE(end->first == (kIterations - 1) * kKeyMultiplier); + REQUIRE(end->second == (kIterations - 1) * kIterations); + } + + THEN("The map can be erased by key") + { + auto erasedCount = map.erase(0); + REQUIRE(erasedCount == 1); + + REQUIRE(map.size() == kIterations - 1); + REQUIRE(!map.contains(0)); + + auto value = map.find(0); + REQUIRE(value == map.end()); + } + + THEN("The value can be erased by iterator") + { + auto it = map.find(2); + REQUIRE(it != map.end()); + + auto erasedIt = map.erase(it); + REQUIRE(erasedIt != map.end()); + REQUIRE(erasedIt->first == 4); + } + + THEN("The map can be cleared") + { + map.clear(); + REQUIRE(map.empty()); + REQUIRE(map.size() == 0); + } + + THEN("upper_bound works properly") + { + // upper_bound finds next for existing element + auto it = map.upper_bound(4); + REQUIRE(it != map.end()); + REQUIRE(it->first == 6); + + // upper_bound finds next for not existing element + it = map.upper_bound(5); + REQUIRE(it != map.end()); + REQUIRE(it->first == 6); + + // upper_bound finds first element + it = map.upper_bound(-1); + REQUIRE(it != map.end()); + REQUIRE(it->first == 0); + + // upper_bound doesn't find element after the last one + it = map.upper_bound(9); + REQUIRE(it == map.end()); + } + + THEN("lower_bound works properly") + { + // lower_bound finds existing element + auto it = map.lower_bound(4); + REQUIRE(it != map.end()); + REQUIRE(it->first == 4); + + // lower_bound finds next for not existing element + it = map.lower_bound(5); + REQUIRE(it != map.end()); + REQUIRE(it->first == 6); + + // lower_bound finds first element + it = map.lower_bound(-1); + REQUIRE(it != map.end()); + REQUIRE(it->first == 0); + + // lower_bound finds last element + it = map.lower_bound(8); + REQUIRE(it != map.end()); + REQUIRE(it->first == 8); + + // lower_bound doesn't find element after the last one + it = map.lower_bound(9); + REQUIRE(it == map.end()); + } + } +}