diff --git a/include/kf/SpanUtils.h b/include/kf/SpanUtils.h index 0bcb028..ca7e0bb 100644 --- a/include/kf/SpanUtils.h +++ b/include/kf/SpanUtils.h @@ -1,70 +1,69 @@ #pragma once #include +#include namespace kf { - using namespace std; - template - inline constexpr span span_cast(span input) noexcept + constexpr std::span span_cast(std::span input) noexcept { return { reinterpret_cast(input.data()), input.size_bytes() / sizeof(T) }; } template - inline constexpr span span_cast(U* data, size_t size) noexcept + constexpr std::span span_cast(U* data, size_t size) noexcept { return { reinterpret_cast(data), size * sizeof(U) / sizeof(T) }; } - inline constexpr span as_bytes(const void* p, size_t size) noexcept + inline constexpr std::span as_bytes(const void* p, size_t size) noexcept { return { static_cast(p), size }; } // TODO: rename to asBytes template - inline constexpr auto as_bytes(const T(&p)[N]) noexcept + constexpr auto as_bytes(const T(&p)[N]) noexcept { - return span{ reinterpret_cast(p), sizeof(p) }; + return std::span{ reinterpret_cast(p), sizeof(p) }; } - inline constexpr span as_writable_bytes(void* p, size_t size) noexcept + inline constexpr std::span as_writable_bytes(void* p, size_t size) noexcept { return { static_cast(p), size }; } // TODO: rename to asWritableBytes template - inline constexpr auto as_writable_bytes(T(&p)[N]) noexcept + constexpr auto as_writable_bytes(T(&p)[N]) noexcept { - return span{ reinterpret_cast(p), sizeof(p) }; + return std::span{ reinterpret_cast(p), sizeof(p) }; } - template - inline constexpr span copyTruncate(span dst, span src) noexcept + template requires std::is_same_v> + constexpr std::span copyTruncate(std::span dst, std::span src) noexcept { // // Source can be larger than destination, truncate in such case // - src = src.first(min(src.size(), dst.size())); + auto truncatedSrc = src.first((std::min)(src.size(), dst.size())); - return { dst.begin(), copy(src.begin(), src.end(), dst.begin()) }; + return { dst.begin(), std::copy(truncatedSrc.begin(), truncatedSrc.end(), dst.begin()) }; } - template - inline constexpr span copyExact(span dst, span src) noexcept + template requires std::is_same_v> + constexpr std::span copyExact(std::span dst, std::span src) noexcept { // // Source MUST be equal to destination // - if constexpr (dstExtent == dynamic_extent || srcExtent == dynamic_extent) + if constexpr (dstExtent == std::dynamic_extent || srcExtent == std::dynamic_extent) { if (dst.size() != src.size()) { - _Xinvalid_argument("dst.size() != src.size()"); + std::_Xinvalid_argument("dst.size() != src.size()"); } } else @@ -72,21 +71,21 @@ namespace kf static_assert(srcExtent == dstExtent); } - return { dst.begin(), copy(src.begin(), src.end(), dst.begin()) }; + return { dst.begin(), std::copy(src.begin(), src.end(), dst.begin()) }; } - template - inline constexpr span copy(span dst, span src) noexcept + template requires std::is_same_v> + constexpr std::span copy(std::span dst, std::span src) noexcept { // // Source MUST be smaller or equal to destination // - if constexpr (dstExtent == dynamic_extent || srcExtent == dynamic_extent) + if constexpr (dstExtent == std::dynamic_extent || srcExtent == std::dynamic_extent) { if (dst.size() < src.size()) { - _Xinvalid_argument("dst.size() < src.size()"); + std::_Xinvalid_argument("dst.size() < src.size()"); } } else @@ -98,15 +97,15 @@ namespace kf } template - inline constexpr bool equals(span left, span right) noexcept + constexpr bool equals(std::span left, std::span right) noexcept { return std::equal(left.begin(), left.end(), right.begin(), right.end()); } - template - inline constexpr ptrdiff_t indexOf(span input, typename span::const_reference elem, ptrdiff_t fromIndex = 0) noexcept + template + constexpr ptrdiff_t indexOf(std::span input, typename std::span::const_reference elem, ptrdiff_t fromIndex = 0) noexcept { - for (auto i = fromIndex; i < ssize(input); ++i) + for (auto i = fromIndex; i < std::ssize(input); ++i) { if (input[i] == elem) { @@ -117,8 +116,8 @@ namespace kf return -1; } - template - inline constexpr span split(span input, typename span::const_reference separator, _Inout_ ptrdiff_t& fromIndex) noexcept + template + constexpr std::span split(std::span input, typename std::span::const_reference separator, _Inout_ ptrdiff_t& fromIndex) noexcept { auto originalFromIndex = fromIndex; @@ -134,8 +133,8 @@ namespace kf return input.subspan(originalFromIndex, count); } - template - inline constexpr T atOrDefault(span input, size_t index, convertible_to auto defaultValue) noexcept + template + constexpr T atOrDefault(std::span input, size_t index, std::convertible_to auto defaultValue) noexcept { return input.size() > index ? input[index] : defaultValue; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 458df5b..f066e50 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -56,6 +56,7 @@ wdk_add_driver(kf-test WINVER NTDDI_WIN10 STL VariableSizeStructTest.cpp ScopeFailureTest.cpp Base64Test.cpp + SpanUtilsTest.cpp AlgorithmTest.cpp AutoSpinLockTest.cpp EResourceSharedLockTest.cpp diff --git a/test/SpanUtilsTest.cpp b/test/SpanUtilsTest.cpp new file mode 100644 index 0000000..6f08e16 --- /dev/null +++ b/test/SpanUtilsTest.cpp @@ -0,0 +1,399 @@ +#include "pch.h" +#include + +SCENARIO("SpanUtils") +{ + GIVEN("an array of int for span_cast from int to std::byte") + { + constexpr int kArr[] = { 1, 2, 3, 4 }; + + WHEN("span_cast is called on a non-empty span") + { + std::span intSpan(kArr); + auto byteSpan = kf::span_cast(intSpan); + + THEN("the size_bytes matches the intSpan and byteSpan") + { + REQUIRE(byteSpan.size_bytes() == intSpan.size_bytes()); + } + } + + WHEN("span_cast is called on an empty span") + { + const std::span emptySpan; + auto byteSpan = kf::span_cast(emptySpan); + + THEN("the result span is empty") + { + REQUIRE(byteSpan.size() == 0); + } + } + } + + GIVEN("a pointer to int and a size for span_cast from int* to std::byte span") + { + constexpr int kArr[] = { 1, 2, 3, 4 }; + + WHEN("span_cast is called") + { + auto byteSpan = kf::span_cast(kArr, 4); + + THEN("the size matches the byte size of the array") + { + REQUIRE(byteSpan.size() == sizeof(kArr)); + } + } + + WHEN("span_cast is called with nullptr and size 0") + { + constexpr int* kNullArr = nullptr; + auto nullSpan = kf::span_cast(kNullArr, 0); + + THEN("the result span is empty") + { + REQUIRE(nullSpan.size() == 0); + } + } + } + + GIVEN("an int array for as_bytes") + { + constexpr int kArr[] = { 42, 99 }; + + WHEN("as_bytes is called with pointer and size") + { + auto s = kf::as_bytes(kArr, sizeof(kArr)); + + THEN("the span covers the array memory") + { + REQUIRE(s.size() == sizeof(kArr)); + REQUIRE(static_cast(s.data()) == static_cast(kArr)); + } + } + + WHEN("as_bytes is called with nullptr and size 0") + { + auto nullSpan = kf::as_bytes(nullptr, 0); + + THEN("the result span is empty") + { + REQUIRE(nullSpan.size() == 0); + } + } + } + + GIVEN("an int array for as_bytes(T[N])") + { + constexpr int kArr[] = { 1, 2, 3 }; + + WHEN("as_bytes is called") + { + auto s = kf::as_bytes(kArr); + + THEN("the span covers the array memory") + { + REQUIRE(s.size() == sizeof(kArr)); + REQUIRE(static_cast(s.data()) == static_cast(kArr)); + } + } + } + + GIVEN("an int array for as_writable_bytes") + { + int arr[] = { 0, 0 }; + + WHEN("as_writable_bytes is called with pointer and size") + { + auto s = kf::as_writable_bytes(arr, sizeof(arr)); + + THEN("the span covers the array memory and is writable") + { + REQUIRE(s.size() == sizeof(arr)); + REQUIRE(static_cast(s.data()) == static_cast(arr)); + } + + THEN("the span is writable") + { + s[0] = std::byte{ 0xFF }; + REQUIRE(s[0] == std::byte{ 0xFF }); + } + } + + WHEN("as_writable_bytes is called with nullptr and size 0") + { + auto nullSpan = kf::as_writable_bytes(nullptr, 0); + + THEN("the result span is empty") + { + REQUIRE(nullSpan.size() == 0); + } + } + } + + GIVEN("an int array for as_writable_bytes(T[N])") + { + int arr[] = { 0, 0 }; + + WHEN("as_writable_bytes is called") + { + auto s = kf::as_writable_bytes(arr); + + THEN("the span covers the array memory and is writable") + { + REQUIRE(s.size() == sizeof(arr)); + REQUIRE(static_cast(s.data()) == static_cast(arr)); + } + + THEN("the span is writable") + { + s[0] = std::byte{ 0xAA }; + REQUIRE(s[0] == std::byte{ 0xAA }); + } + } + } + + GIVEN("source and destination arrays for copyTruncate") + { + int biggerArr[] = { 1, 2, 3, 4, 5 }; + int smallerArr[] = { 11, 12, 13 }; + + WHEN("copyTruncate is called with source larger than destination") + { + auto result = kf::copyTruncate(std::span{ smallerArr }, std::span{ biggerArr }); + + THEN("only the first destination.size() elements are copied") + { + constexpr int kExpected[] = { 1, 2, 3 }; + REQUIRE(std::ranges::equal(result, kExpected)); + } + } + + WHEN("copyTruncate is called with src smaller than dst") + { + auto result = kf::copyTruncate(std::span{ biggerArr }, std::span{ smallerArr }); + + THEN("all src elements are copied") + { + constexpr int kExpectedResult[] = { 11, 12, 13 }; + REQUIRE(std::ranges::equal(result, kExpectedResult)); + + constexpr int kExpectedBiggerArr[] = { 11, 12, 13, 4, 5 }; + REQUIRE(std::ranges::equal(biggerArr, kExpectedBiggerArr)); + } + } + } + + GIVEN("source and destination arrays for copyExact") + { + WHEN("copyExact is called with equal sizes") + { + constexpr int kSrc[] = { 1, 2, 3 }; + int dst[] = { 11, 22, 33 }; + + auto result = kf::copyExact(std::span{ dst }, std::span{ kSrc }); + + THEN("all elements are copied") + { + constexpr int kExpected[] = { 1, 2, 3 }; + REQUIRE(std::ranges::equal(result, kExpected)); + } + } + } + + GIVEN("source and destination arrays for copy") + { + int smallerArr[] = { 5, 6 }; + int biggerArr[] = { 55, 66, 77, 88 }; + + WHEN("copy is called with source smaller than destination") + { + auto result = kf::copy(std::span{ biggerArr }, std::span{ smallerArr }); + + THEN("all source elements are copied") + { + constexpr int kExpected[] = { 5, 6 }; + REQUIRE(std::ranges::equal(result, kExpected)); + } + } + } + + GIVEN("arrays for equals") + { + constexpr int kA[] = { 1, 2, 3 }; + constexpr int kB[] = { 1, 2, 3 }; + constexpr int kC[] = { 1, 2, 4 }; + constexpr int kD[] = { 1, 2, 3, 4 }; + + WHEN("equals is called on identical arrays") + { + THEN("returns true") + { + REQUIRE(kf::equals(std::span{ kA }, std::span{ kB })); + } + } + + WHEN("equals is called on different array content") + { + THEN("returns false") + { + REQUIRE(!kf::equals(std::span{ kA }, std::span{ kC })); + } + } + + WHEN("equals is called on different array size") + { + THEN("returns false") + { + REQUIRE(!kf::equals(std::span{ kC }, std::span{ kD })); + } + } + + WHEN("equals is called on empty arrays") + { + constexpr int* kLeft = nullptr; + constexpr int* kRight = nullptr; + + THEN("returns true") + { + REQUIRE(kf::equals(std::span{ kLeft, 0 }, std::span{ kRight, 0 })); + } + } + } + + GIVEN("an array for indexOf") + { + constexpr int kArr[] = { 10, 20, 30, 40, 50 }; + + WHEN("indexOf is called for a present value") + { + THEN("returns the correct index") + { + REQUIRE(kf::indexOf(std::span{ kArr }, 30) == 2); + } + } + + WHEN("indexOf is called for a missing value") + { + THEN("returns -1") + { + REQUIRE(kf::indexOf(std::span{ kArr }, 75) == -1); + } + } + + WHEN("indexOf is called with fromIndex out of bounds") + { + THEN("returns -1") + { + REQUIRE(kf::indexOf(std::span{ kArr }, 10, 5) == -1); + } + } + } + + GIVEN("an array for split") + { + constexpr int kArr[] = { 1, 2, 3, 7, 2, 5, 9, 8 }; + constexpr int kSeparator = 2; + constexpr int kMissingSeparator = 75; + + WHEN("split is called with idx before first separator") + { + ptrdiff_t idx = 0; + auto result = kf::split(std::span{ kArr }, kSeparator, idx); + + THEN("returns the first segment before separator") + { + constexpr int kExpected[] = { 1 }; + REQUIRE(std::ranges::equal(result, kExpected)); + } + + THEN("idx is updated to the first separator index") + { + REQUIRE(idx == 2); + } + } + + WHEN("split is called with idx between separators") + { + ptrdiff_t idx = 2; + auto result = kf::split(std::span{ kArr }, kSeparator, idx); + + THEN("returns the segment after the first separator and before the second separator") + { + constexpr int kExpected[] = { 3, 7 }; + REQUIRE(std::ranges::equal(result, kExpected)); + } + + THEN("idx is updated to the next separator index") + { + REQUIRE(idx == 5); + } + } + + WHEN("split is called with idx after the last separator") + { + ptrdiff_t idx = 5; + auto result = kf::split(std::span{ kArr }, kSeparator, idx); + + THEN("returns the last segment after separator") + { + constexpr int kExpected[] = { 5, 9, 8 }; + REQUIRE(std::ranges::equal(result, kExpected)); + } + + THEN("idx is updated to -1 indicating no more separators") + { + REQUIRE(idx == -1); + } + } + + WHEN("split is called with no separator found") + { + ptrdiff_t idx = 0; + auto result = kf::split(std::span{ kArr }, kMissingSeparator, idx); + + THEN("returns the whole array") + { + REQUIRE(result.size_bytes() == sizeof(kArr)); + REQUIRE(static_cast(result.data()) == static_cast(kArr)); + } + + THEN("idx is updated to -1 indicating no more separators") + { + REQUIRE(idx == -1); + } + } + } + + GIVEN("an array for atOrDefault") + { + constexpr int kArr[3] = { 7, 8, 9 }; + constexpr int kDefault = -1; + constexpr int kOtherDefault = 42; + + WHEN("atOrDefault is called with a valid index") + { + THEN("returns the element at that index") + { + REQUIRE(kf::atOrDefault(std::span{ kArr }, 1, kDefault) == 8); + } + } + + WHEN("atOrDefault is called with an invalid index") + { + THEN("returns the default value") + { + REQUIRE(kf::atOrDefault(std::span{ kArr }, 5, kDefault) == kDefault); + } + } + + WHEN("atOrDefault is called on an empty span") + { + constexpr int* kEmpty = nullptr; + + THEN("returns the default value") + { + REQUIRE(kf::atOrDefault(std::span{ kEmpty, 0 }, 0, kOtherDefault) == kOtherDefault); + } + } + } +} \ No newline at end of file diff --git a/test/pch.h b/test/pch.h index b5c57a3..d7c4938 100644 --- a/test/pch.h +++ b/test/pch.h @@ -21,6 +21,17 @@ extern "C" inline int _CrtDbgReport( KeBugCheckEx(KERNEL_SECURITY_CHECK_FAILURE, 0, 0, 0, 0); } +__declspec(noreturn) inline void __cdecl _invoke_watson( + _In_opt_z_ wchar_t const* const /*expression*/, + _In_opt_z_ wchar_t const* const /*function_name*/, + _In_opt_z_ wchar_t const* const /*file_name*/, + _In_ unsigned int const /*line_number*/, + _In_ uintptr_t const /*reserved*/) +{ +#pragma warning(suppress: 28159) // Consider using 'error logging or driver shutdown' instead of 'KeBugCheckEx' + KeBugCheckEx(KERNEL_SECURITY_CHECK_FAILURE, 0, 0, 0, 0); +} + namespace std { [[noreturn]] inline void __cdecl _Xinvalid_argument(_In_z_ const char* /*What*/)