From ab4637f6c7ed312ca26471af703adb7699661159 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Oct 2025 10:57:49 +0200 Subject: [PATCH 1/2] Do not rely on std::string::reserve Passing an object with capacity through std::ostringstream may change its capacity. Use malloc instead to make sure the memory is allocated. --- apps/components_tests/bsa/testbsafile.cpp | 47 +++++++++++++++++------ 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/components_tests/bsa/testbsafile.cpp b/apps/components_tests/bsa/testbsafile.cpp index 23bff99331..ab5ab31930 100644 --- a/apps/components_tests/bsa/testbsafile.cpp +++ b/apps/components_tests/bsa/testbsafile.cpp @@ -8,10 +8,14 @@ #include #include +#include +#include #include #include #include +#include #include +#include namespace Bsa { @@ -19,6 +23,17 @@ namespace Bsa { using namespace ::testing; + struct Free + { + void operator()(void* ptr) const { std::free(ptr); } + }; + + struct Buffer + { + std::unique_ptr mData; + std::size_t mCapacity; + }; + struct Header { uint32_t mFormat; @@ -69,13 +84,9 @@ namespace Bsa std::format("{}.{}.bsa", testInfo->test_suite_name(), testInfo->name())); } - std::string makeBsaBuffer(std::uint32_t fileSize, std::uint32_t fileOffset) + Buffer makeBsaBuffer(std::uint32_t fileSize, std::uint32_t fileOffset) { - std::string buffer; - - buffer.reserve(static_cast(fileSize) + static_cast(fileOffset) + 34); - - std::ostringstream stream(std::move(buffer)); + std::ostringstream stream; const Header header{ .mFormat = static_cast(BsaVersion::Uncompressed), @@ -98,7 +109,17 @@ namespace Bsa writeArchive(archive, stream); - return std::move(stream).str(); + const std::string data = std::move(stream).str(); + + const std::size_t capacity = static_cast(fileSize) + static_cast(fileOffset) + 34; + std::unique_ptr buffer(reinterpret_cast(std::malloc(capacity))); + + if (buffer == nullptr) + throw std::bad_alloc(); + + std::memcpy(buffer.get(), data.data(), data.size()); + + return Buffer{ .mData = std::move(buffer), .mCapacity = capacity }; } TEST(BSAFileTest, shouldHandleEmpty) @@ -266,11 +287,13 @@ namespace Bsa TEST(BSAFileTest, shouldHandleSingleFileAtTheEndOfLargeFile) { constexpr std::uint32_t maxUInt32 = std::numeric_limits::max(); - const std::string buffer = makeBsaBuffer(maxUInt32, maxUInt32 - 34); + constexpr std::uint32_t fileSize = maxUInt32; + constexpr std::uint32_t fileOffset = maxUInt32 - 34; + const Buffer buffer = makeBsaBuffer(fileSize, fileOffset); TestBSAFile file; // Use capacity assuming we never read beyond small header. - Files::IMemStream stream(buffer.data(), buffer.capacity()); + Files::IMemStream stream(buffer.mData.get(), buffer.mCapacity); file.readHeader(stream); std::vector namesBuffer = { 'a', '\0' }; @@ -289,11 +312,13 @@ namespace Bsa TEST(BSAFileTest, shouldThrowExceptionOnTooBigAbsoluteOffset) { constexpr std::uint32_t maxUInt32 = std::numeric_limits::max(); - const std::string buffer = makeBsaBuffer(maxUInt32, maxUInt32 - 34 + 1); + constexpr std::uint32_t fileSize = maxUInt32; + constexpr std::uint32_t fileOffset = maxUInt32 - 34 + 1; + const Buffer buffer = makeBsaBuffer(fileSize, fileOffset); TestBSAFile file; // Use capacity assuming we never read beyond small header. - Files::IMemStream stream(buffer.data(), buffer.capacity()); + Files::IMemStream stream(buffer.mData.get(), buffer.mCapacity); EXPECT_THROW(file.readHeader(stream), std::runtime_error); EXPECT_THAT(file.getList(), IsEmpty()); From 7bf1ea32b0cf2a518c8fcbd3fa6d20fef48fa02b Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Oct 2025 10:19:06 +0200 Subject: [PATCH 2/2] Disable not working tests for MSVC std::streambuf in MSVC does not support buffers larger than 2**31 - 1: https://developercommunity.visualstudio.com/t/stdbasic-stringbuf-is-broken/290124 Simple test to check if it works: TEST(IMemStreamTest, shouldRead) { std::string src(std::numeric_limits::max() / 2 + 1, '\0'); Files::IMemStream stream(src.data(), src.size()); std::string dst(src.size(), '\0'); stream.read(dst.data(), src.size()); EXPECT_FALSE(stream.fail()) << std::generic_category().message(errno); } --- apps/components_tests/bsa/testbsafile.cpp | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/apps/components_tests/bsa/testbsafile.cpp b/apps/components_tests/bsa/testbsafile.cpp index ab5ab31930..365697e6ae 100644 --- a/apps/components_tests/bsa/testbsafile.cpp +++ b/apps/components_tests/bsa/testbsafile.cpp @@ -284,6 +284,34 @@ namespace Bsa })); } + TEST(BSAFileTest, shouldHandleSomewhatLargeFiles) + { + constexpr std::uint32_t maxUInt32 = std::numeric_limits::max(); + constexpr std::uint32_t fileSize = maxUInt32 / 4; + constexpr std::uint32_t fileOffset = maxUInt32 / 4 - 34; + const Buffer buffer = makeBsaBuffer(fileSize, fileOffset); + + TestBSAFile file; + // Use capacity assuming we never read beyond small header. + Files::IMemStream stream(buffer.mData.get(), buffer.mCapacity); + file.readHeader(stream); + + std::vector namesBuffer = { 'a', '\0' }; + + EXPECT_THAT(file.getList(), + ElementsAre(BSAFile::FileStruct{ + .mFileSize = maxUInt32 / 4, + .mOffset = maxUInt32 / 4, + .mHash = BSAFile::Hash{ .mLow = 0xaaaabbbb, .mHigh = 0xccccdddd }, + .mNameOffset = 0, + .mNameSize = 1, + .mNamesBuffer = &namesBuffer, + })); + } + +// std::streambuf in MSVC does not support buffers larger than 2**31 - 1: +// https://developercommunity.visualstudio.com/t/stdbasic-stringbuf-is-broken/290124 +#ifndef _MSC_VER TEST(BSAFileTest, shouldHandleSingleFileAtTheEndOfLargeFile) { constexpr std::uint32_t maxUInt32 = std::numeric_limits::max(); @@ -323,5 +351,6 @@ namespace Bsa EXPECT_THAT(file.getList(), IsEmpty()); } +#endif } }