diff --git a/CMakeLists.txt b/CMakeLists.txt index 8439a3be2..78afe2fa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,12 @@ elseif(NOT APPLE) endif() if(BUILD_TESTING) + add_test_executable(shader_cache_file_read_tests + ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/CacheFileReadTests.cpp) + target_include_directories(shader_cache_file_read_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler) + add_test(NAME shader_cache_file_read COMMAND shader_cache_file_read_tests) + set_tests_properties(shader_cache_file_read PROPERTIES TIMEOUT 20) find_package(Python3 QUIET COMPONENTS Interpreter) if(Python3_Interpreter_FOUND) add_test(NAME pr_overlap_encoding COMMAND ${Python3_EXECUTABLE} diff --git a/core/shader/recompiler/ShaderCacheDirectory.hpp b/core/shader/recompiler/ShaderCacheDirectory.hpp index e17f9b110..db8e2f8fe 100644 --- a/core/shader/recompiler/ShaderCacheDirectory.hpp +++ b/core/shader/recompiler/ShaderCacheDirectory.hpp @@ -4,11 +4,15 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include #include #include #include @@ -89,17 +93,27 @@ inline bool WriteFileAtomically(const std::filesystem::path& path, std::span& bytes) { bytes.clear(); - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file) return false; - const auto size = static_cast(file.tellg()); - if (size < 0) return false; - file.seekg(0, std::ios::beg); - bytes.resize(static_cast(size)); - if (!file.read(reinterpret_cast(bytes.data()), static_cast(bytes.size()))) { + try { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) return false; + const auto size = static_cast(file.tellg()); + if (size < 0 || static_cast(size) > bytes.max_size() || + static_cast(size) > static_cast(std::numeric_limits::max())) + return false; + if (!file.seekg(0, std::ios::beg)) return false; + bytes.resize(static_cast(size)); + if (!file.read(reinterpret_cast(bytes.data()), static_cast(bytes.size()))) { + bytes.clear(); + return false; + } + return true; + } catch (const std::bad_alloc&) { + bytes.clear(); + return false; + } catch (const std::length_error&) { bytes.clear(); return false; } - return true; } inline std::uint64_t HashBytes(std::span bytes, std::uint64_t seed = 0) { diff --git a/core/shader/recompiler/tests/CacheFileReadTests.cpp b/core/shader/recompiler/tests/CacheFileReadTests.cpp new file mode 100644 index 000000000..29f5e61b3 --- /dev/null +++ b/core/shader/recompiler/tests/CacheFileReadTests.cpp @@ -0,0 +1,44 @@ +#include "ShaderCacheDirectory.hpp" +#include +#include +#include + +static bool FailLargeAllocation = false; + +void* operator new(std::size_t size) { + if (FailLargeAllocation && size > 1024 * 1024) throw std::bad_alloc(); + if (auto* memory = std::malloc(size == 0 ? 1 : size)) return memory; + throw std::bad_alloc(); +} +void operator delete(void* memory) noexcept { std::free(memory); } +void operator delete(void* memory, std::size_t) noexcept { std::free(memory); } +void* operator new[](std::size_t size) { return ::operator new(size); } +void operator delete[](void* memory) noexcept { ::operator delete(memory); } +void operator delete[](void* memory, std::size_t) noexcept { ::operator delete(memory); } + +static void Require(bool ok) { if (!ok) throw std::runtime_error("Cache file read failed"); } + +int main() { + const auto path = std::filesystem::temp_directory_path() / ("anyps5-cache-read-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + int status = 0; + try { + std::vector bytes{std::byte{1}}; + Require(!ShaderRecompiler::ReadWholeFile(path, bytes) && bytes.empty()); + { std::ofstream file(path, std::ios::binary); } + Require(ShaderRecompiler::ReadWholeFile(path, bytes) && bytes.empty()); + const std::vector payload(2 * 1024 * 1024, 'S'); + { std::ofstream file(path, std::ios::binary | std::ios::trunc); file.write(payload.data(), payload.size()); Require(static_cast(file)); } + Require(ShaderRecompiler::ReadWholeFile(path, bytes) && bytes.size() == payload.size()); + Require(bytes.front() == std::byte{'S'} && bytes.back() == std::byte{'S'}); + std::vector failed{std::byte{1}}; + FailLargeAllocation = true; + const auto read = ShaderRecompiler::ReadWholeFile(path, failed); + FailLargeAllocation = false; + Require(!read && failed.empty()); + std::cout << "5 cache file read cases passed\n"; + } catch (const std::exception& error) { FailLargeAllocation = false; std::cerr << error.what() << '\n'; status = 1; } + std::error_code error; + std::filesystem::remove(path, error); + return status; +}