From 1f835c8fe021a1caa3ec42474b74218736b25de7 Mon Sep 17 00:00:00 2001 From: cursey Date: Sun, 12 May 2024 18:10:51 -0700 Subject: [PATCH 1/4] InlineHook/MidHook: Add enable() and disable() --- include/safetyhook/easy.hpp | 17 ++++-- include/safetyhook/inline_hook.hpp | 41 ++++++++++++-- include/safetyhook/mid_hook.hpp | 35 +++++++++--- src/easy.cpp | 8 +-- src/inline_hook.cpp | 88 ++++++++++++++++++++++-------- src/mid_hook.cpp | 30 ++++++++-- test/inline_hook.cpp | 47 ++++++++++++++++ test/mid_hook.cpp | 61 +++++++++++++++++++++ 8 files changed, 276 insertions(+), 51 deletions(-) diff --git a/include/safetyhook/easy.hpp b/include/safetyhook/easy.hpp index bb4bb28..080a2a8 100644 --- a/include/safetyhook/easy.hpp +++ b/include/safetyhook/easy.hpp @@ -12,29 +12,34 @@ namespace safetyhook { /// @brief Easy to use API for creating an InlineHook. /// @param target The address of the function to hook. /// @param destination The address of the destination function. +/// @param flags The flags to use. /// @return The InlineHook object. -[[nodiscard]] InlineHook create_inline(void* target, void* destination); +[[nodiscard]] InlineHook create_inline(void* target, void* destination, InlineHook::Flags flags = InlineHook::Default); /// @brief Easy to use API for creating an InlineHook. /// @param target The address of the function to hook. /// @param destination The address of the destination function. +/// @param flags The flags to use. /// @return The InlineHook object. -[[nodiscard]] InlineHook create_inline(FnPtr auto target, FnPtr auto destination) { - return create_inline(reinterpret_cast(target), reinterpret_cast(destination)); +[[nodiscard]] InlineHook create_inline( + FnPtr auto target, FnPtr auto destination, InlineHook::Flags flags = InlineHook::Default) { + return create_inline(reinterpret_cast(target), reinterpret_cast(destination), flags); } /// @brief Easy to use API for creating a MidHook. /// @param target the address of the function to hook. /// @param destination The destination function. +/// @param flags The flags to use. /// @return The MidHook object. -[[nodiscard]] MidHook create_mid(void* target, MidHookFn destination); +[[nodiscard]] MidHook create_mid(void* target, MidHookFn destination, MidHook::Flags = MidHook::Default); /// @brief Easy to use API for creating a MidHook. /// @param target the address of the function to hook. /// @param destination The destination function. +/// @param flags The flags to use. /// @return The MidHook object. -[[nodiscard]] MidHook create_mid(FnPtr auto target, MidHookFn destination) { - return create_mid(reinterpret_cast(target), destination); +[[nodiscard]] MidHook create_mid(FnPtr auto target, MidHookFn destination, MidHook::Flags flags = MidHook::Default) { + return create_mid(reinterpret_cast(target), destination, flags); } /// @brief Easy to use API for creating a VmtHook. diff --git a/include/safetyhook/inline_hook.hpp b/include/safetyhook/inline_hook.hpp index 0ddac53..cb7e2be 100644 --- a/include/safetyhook/inline_hook.hpp +++ b/include/safetyhook/inline_hook.hpp @@ -87,42 +87,54 @@ class InlineHook final { [[nodiscard]] static Error not_enough_space(uint8_t* ip) { return {.type = NOT_ENOUGH_SPACE, .ip = ip}; } }; + /// @brief Flags for InlineHook. + enum Flags : int { + Default = 0, ///< Default flags. + StartDisabled = 1 << 0, ///< Start the hook disabled. + }; + /// @brief Create an inline hook. /// @param target The address of the function to hook. /// @param destination The destination address. + /// @param flags The flags to use. /// @return The InlineHook or an InlineHook::Error if an error occurred. /// @note This will use the default global Allocator. /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). - [[nodiscard]] static std::expected create(void* target, void* destination); + [[nodiscard]] static std::expected create( + void* target, void* destination, Flags flags = Default); /// @brief Create an inline hook. /// @param target The address of the function to hook. /// @param destination The destination address. + /// @param flags The flags to use. /// @return The InlineHook or an InlineHook::Error if an error occurred. /// @note This will use the default global Allocator. /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). - [[nodiscard]] static std::expected create(FnPtr auto target, FnPtr auto destination) { - return create(reinterpret_cast(target), reinterpret_cast(destination)); + [[nodiscard]] static std::expected create( + FnPtr auto target, FnPtr auto destination, Flags flags = Default) { + return create(reinterpret_cast(target), reinterpret_cast(destination), flags); } /// @brief Create an inline hook with a given Allocator. /// @param allocator The allocator to use. /// @param target The address of the function to hook. /// @param destination The destination address. + /// @param flags The flags to use. /// @return The InlineHook or an InlineHook::Error if an error occurred. /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). [[nodiscard]] static std::expected create( - const std::shared_ptr& allocator, void* target, void* destination); + const std::shared_ptr& allocator, void* target, void* destination, Flags flags = Default); /// @brief Create an inline hook with a given Allocator. /// @param allocator The allocator to use. /// @param target The address of the function to hook. /// @param destination The destination address. + /// @param flags The flags to use. /// @return The InlineHook or an InlineHook::Error if an error occurred. /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). [[nodiscard]] static std::expected create( - const std::shared_ptr& allocator, FnPtr auto target, FnPtr auto destination) { - return create(allocator, reinterpret_cast(target), reinterpret_cast(destination)); + const std::shared_ptr& allocator, FnPtr auto target, FnPtr auto destination, Flags flags = Default) { + return create(allocator, reinterpret_cast(target), reinterpret_cast(destination), flags); } InlineHook() = default; @@ -285,15 +297,32 @@ class InlineHook final { return original()(args...); } + /// @brief Enable the hook. + [[nodiscard]] std::expected enable(); + + /// @brief Disable the hook. + [[nodiscard]] std::expected disable(); + + /// @brief Check if the hook is enabled. + [[nodiscard]] bool enabled() const { return m_enabled; } + private: friend class MidHook; + enum class Type { + Unset, + E9, + FF, + }; + uint8_t* m_target{}; uint8_t* m_destination{}; Allocation m_trampoline{}; std::vector m_original_bytes{}; uintptr_t m_trampoline_size{}; std::recursive_mutex m_mutex{}; + bool m_enabled{}; + Type m_type{Type::Unset}; std::expected setup( const std::shared_ptr& allocator, uint8_t* target, uint8_t* destination); diff --git a/include/safetyhook/mid_hook.hpp b/include/safetyhook/mid_hook.hpp index a5ad4f8..7b2fb79 100644 --- a/include/safetyhook/mid_hook.hpp +++ b/include/safetyhook/mid_hook.hpp @@ -52,43 +52,55 @@ class MidHook final { } }; + /// @brief Flags for MidHook. + enum Flags : int { + Default = 0, ///< Default flags. + StartDisabled = 1, ///< Start the hook disabled. + }; + /// @brief Creates a new MidHook object. /// @param target The address of the function to hook. /// @param destination_fn The destination function. + /// @param flags The flags to use. /// @return The MidHook object or a MidHook::Error if an error occurred. /// @note This will use the default global Allocator. /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). - [[nodiscard]] static std::expected create(void* target, MidHookFn destination_fn); + [[nodiscard]] static std::expected create( + void* target, MidHookFn destination_fn, Flags flags = Default); /// @brief Creates a new MidHook object. /// @param target The address of the function to hook. /// @param destination_fn The destination function. + /// @param flags The flags to use. /// @return The MidHook object or a MidHook::Error if an error occurred. /// @note This will use the default global Allocator. /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). - [[nodiscard]] static std::expected create(FnPtr auto target, MidHookFn destination_fn) { - return create(reinterpret_cast(target), destination_fn); + [[nodiscard]] static std::expected create( + FnPtr auto target, MidHookFn destination_fn, Flags flags = Default) { + return create(reinterpret_cast(target), destination_fn, flags); } /// @brief Creates a new MidHook object with a given Allocator. /// @param allocator The Allocator to use. /// @param target The address of the function to hook. /// @param destination_fn The destination function. + /// @param flags The flags to use. /// @return The MidHook object or a MidHook::Error if an error occurred. /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). [[nodiscard]] static std::expected create( - const std::shared_ptr& allocator, void* target, MidHookFn destination_fn); + const std::shared_ptr& allocator, void* target, MidHookFn destination_fn, Flags flags = Default); /// @brief Creates a new MidHook object with a given Allocator. /// @tparam T The type of the function to hook. /// @param allocator The Allocator to use. /// @param target The address of the function to hook. /// @param destination_fn The destination function. + /// @param flags The flags to use. /// @return The MidHook object or a MidHook::Error if an error occurred. /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). - [[nodiscard]] static std::expected create( - const std::shared_ptr& allocator, FnPtr auto target, MidHookFn destination_fn) { - return create(allocator, reinterpret_cast(target), destination_fn); + [[nodiscard]] static std::expected create(const std::shared_ptr& allocator, + FnPtr auto target, MidHookFn destination_fn, Flags flags = Default) { + return create(allocator, reinterpret_cast(target), destination_fn, flags); } MidHook() = default; @@ -123,6 +135,15 @@ class MidHook final { /// @return true if the hook is valid, false otherwise. explicit operator bool() const { return static_cast(m_stub); } + /// @brief Enable the hook. + [[nodiscard]] std::expected enable(); + + /// @brief Disable the hook. + [[nodiscard]] std::expected disable(); + + /// @brief Check if the hook is enabled. + [[nodiscard]] bool enabled() const { return m_hook.enabled(); } + private: InlineHook m_hook{}; uint8_t* m_target{}; diff --git a/src/easy.cpp b/src/easy.cpp index 65efb06..1f92b7c 100644 --- a/src/easy.cpp +++ b/src/easy.cpp @@ -1,16 +1,16 @@ #include "safetyhook/easy.hpp" namespace safetyhook { -InlineHook create_inline(void* target, void* destination) { - if (auto hook = InlineHook::create(target, destination)) { +InlineHook create_inline(void* target, void* destination, InlineHook::Flags flags) { + if (auto hook = InlineHook::create(target, destination, flags)) { return std::move(*hook); } else { return {}; } } -MidHook create_mid(void* target, MidHookFn destination) { - if (auto hook = MidHook::create(target, destination)) { +MidHook create_mid(void* target, MidHookFn destination, MidHook::Flags flags) { + if (auto hook = MidHook::create(target, destination, flags)) { return std::move(*hook); } else { return {}; diff --git a/src/inline_hook.cpp b/src/inline_hook.cpp index cd7d4cb..dea6a81 100644 --- a/src/inline_hook.cpp +++ b/src/inline_hook.cpp @@ -114,12 +114,12 @@ static bool decode(ZydisDecodedInstruction* ix, uint8_t* ip) { return ZYAN_SUCCESS(ZydisDecoderDecodeInstruction(&decoder, nullptr, ip, 15, ix)); } -std::expected InlineHook::create(void* target, void* destination) { - return create(Allocator::global(), target, destination); +std::expected InlineHook::create(void* target, void* destination, Flags flags) { + return create(Allocator::global(), target, destination, flags); } std::expected InlineHook::create( - const std::shared_ptr& allocator, void* target, void* destination) { + const std::shared_ptr& allocator, void* target, void* destination, Flags flags) { InlineHook hook{}; if (const auto setup_result = @@ -128,6 +128,12 @@ std::expected InlineHook::create( return std::unexpected{setup_result.error()}; } + if (!(flags & StartDisabled)) { + if (auto enable_result = hook.enable(); !enable_result) { + return std::unexpected{enable_result.error()}; + } + } + return hook; } @@ -146,10 +152,14 @@ InlineHook& InlineHook::operator=(InlineHook&& other) noexcept { m_trampoline = std::move(other.m_trampoline); m_trampoline_size = other.m_trampoline_size; m_original_bytes = std::move(other.m_original_bytes); + m_enabled = other.m_enabled; + m_type = other.m_type; other.m_target = nullptr; other.m_destination = nullptr; other.m_trampoline_size = 0; + other.m_enabled = false; + other.m_type = Type::Unset; } return *this; @@ -305,20 +315,7 @@ std::expected InlineHook::e9_hook(const std::shared_ptr } #endif - std::optional error; - - // jmp from original to trampoline. - trap_threads(m_target, m_trampoline.data(), m_original_bytes.size(), [this, &trampoline_epilogue, &error] { - if (auto result = emit_jmp_e9(m_target, reinterpret_cast(&trampoline_epilogue->jmp_to_destination), - m_original_bytes.size()); - !result) { - error = result.error(); - } - }); - - if (error) { - return std::unexpected{*error}; - } + m_type = Type::E9; return {}; } @@ -367,34 +364,77 @@ std::expected InlineHook::ff_hook(const std::shared_ptr return std::unexpected{result.error()}; } + m_type = Type::FF; + + return {}; +} +#endif + +std::expected InlineHook::enable() { + std::scoped_lock lock{m_mutex}; + + if (m_enabled) { + return {}; + } + std::optional error; // jmp from original to trampoline. trap_threads(m_target, m_trampoline.data(), m_original_bytes.size(), [this, &error] { - if (auto result = emit_jmp_ff(m_target, m_destination, m_target + sizeof(JmpFF), m_original_bytes.size()); - !result) { - error = result.error(); + if (m_type == Type::E9) { + auto trampoline_epilogue = reinterpret_cast( + m_trampoline.address() + m_trampoline_size - sizeof(TrampolineEpilogueE9)); + + if (auto result = emit_jmp_e9(m_target, + reinterpret_cast(&trampoline_epilogue->jmp_to_destination), m_original_bytes.size()); + !result) { + error = result.error(); + } } + +#if SAFETYHOOK_ARCH_X86_64 + if (m_type == Type::FF) { + if (auto result = emit_jmp_ff(m_target, m_destination, m_target + sizeof(JmpFF), m_original_bytes.size()); + !result) { + error = result.error(); + } + } +#endif }); if (error) { return std::unexpected{*error}; } + m_enabled = true; + return {}; } -#endif -void InlineHook::destroy() { +std::expected InlineHook::disable() { std::scoped_lock lock{m_mutex}; - if (!m_trampoline) { - return; + if (!m_enabled) { + return {}; } trap_threads(m_trampoline.data(), m_target, m_original_bytes.size(), [this] { std::copy(m_original_bytes.begin(), m_original_bytes.end(), m_target); }); + m_enabled = false; + + return {}; +} + +void InlineHook::destroy() { + [[maybe_unused]] auto disable_result = disable(); + + std::scoped_lock lock{m_mutex}; + + if (!m_trampoline) { + return; + } + m_trampoline.free(); } } // namespace safetyhook diff --git a/src/mid_hook.cpp b/src/mid_hook.cpp index 3284ff4..339bef8 100644 --- a/src/mid_hook.cpp +++ b/src/mid_hook.cpp @@ -68,12 +68,12 @@ constexpr std::array asm_data = {0xFF, 0x35, 0xA7, 0x00, 0x00, 0x0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; #endif -std::expected MidHook::create(void* target, MidHookFn destination) { - return create(Allocator::global(), target, destination); +std::expected MidHook::create(void* target, MidHookFn destination, Flags flags) { + return create(Allocator::global(), target, destination, flags); } std::expected MidHook::create( - const std::shared_ptr& allocator, void* target, MidHookFn destination) { + const std::shared_ptr& allocator, void* target, MidHookFn destination, Flags flags) { MidHook hook{}; if (const auto setup_result = hook.setup(allocator, reinterpret_cast(target), destination); @@ -81,6 +81,12 @@ std::expected MidHook::create( return std::unexpected{setup_result.error()}; } + if (!(flags & StartDisabled)) { + if (auto enable_result = hook.enable(); !enable_result) { + return std::unexpected{enable_result.error()}; + } + } + return hook; } @@ -131,7 +137,7 @@ std::expected MidHook::setup( store(m_stub.data() + 0x59, m_stub.data() + m_stub.size() - 8); #endif - auto hook_result = InlineHook::create(allocator, m_target, m_stub.data()); + auto hook_result = InlineHook::create(allocator, m_target, m_stub.data(), InlineHook::StartDisabled); if (!hook_result) { m_stub.free(); @@ -148,4 +154,20 @@ std::expected MidHook::setup( return {}; } + +std::expected MidHook::enable() { + if (auto enable_result = m_hook.enable(); !enable_result) { + return std::unexpected{Error::bad_inline_hook(enable_result.error())}; + } + + return {}; +} + +std::expected MidHook::disable() { + if (auto disable_result = m_hook.disable(); !disable_result) { + return std::unexpected{Error::bad_inline_hook(disable_result.error())}; + } + + return {}; +} } // namespace safetyhook diff --git a/test/inline_hook.cpp b/test/inline_hook.cpp index 5a45af2..807c052 100644 --- a/test/inline_hook.cpp +++ b/test/inline_hook.cpp @@ -623,4 +623,51 @@ static suite<"inline hook"> inline_hook_tests = [] { expect(fn() == 42_i); }; + + "Function hook can be enable and disabled"_test = [] { + struct Target { + SAFETYHOOK_NOINLINE static int fn(int a) { + volatile int b = a; + return b * 2; + } + }; + + expect(Target::fn(1) == 2_i); + expect(Target::fn(2) == 4_i); + expect(Target::fn(3) == 6_i); + + static SafetyHookInline hook; + + struct Hook { + static int fn(int a) { return hook.call(a + 1); } + }; + + auto hook0_result = SafetyHookInline::create(Target::fn, Hook::fn, SafetyHookInline::StartDisabled); + + expect(hook0_result.has_value()); + + hook = std::move(*hook0_result); + + expect(Target::fn(1) == 2_i); + expect(Target::fn(2) == 4_i); + expect(Target::fn(3) == 6_i); + + expect(hook.enable().has_value()); + + expect(Target::fn(1) == 4_i); + expect(Target::fn(2) == 6_i); + expect(Target::fn(3) == 8_i); + + expect(hook.disable().has_value()); + + expect(Target::fn(1) == 2_i); + expect(Target::fn(2) == 4_i); + expect(Target::fn(3) == 6_i); + + hook.reset(); + + expect(Target::fn(1) == 2_i); + expect(Target::fn(2) == 4_i); + expect(Target::fn(3) == 6_i); + }; }; \ No newline at end of file diff --git a/test/mid_hook.cpp b/test/mid_hook.cpp index 8560f3f..5441d21 100644 --- a/test/mid_hook.cpp +++ b/test/mid_hook.cpp @@ -71,4 +71,65 @@ static suite<"mid hook"> mid_hook_tests = [] { expect(Target::add_42(2.0f) == 2.42_f); }; #endif + + "Mid hook enable and disable"_test = [] { + struct Target { + SAFETYHOOK_NOINLINE static int SAFETYHOOK_FASTCALL add_42(int a) { + volatile int b = a; + return b + 42; + } + }; + + expect(Target::add_42(0) == 42_i); + expect(Target::add_42(1) == 43_i); + expect(Target::add_42(2) == 44_i); + + static SafetyHookMid hook; + + struct Hook { + static void add_42(SafetyHookContext& ctx) { +#if SAFETYHOOK_OS_WINDOWS +#if SAFETYHOOK_ARCH_X86_64 + ctx.rcx = 1337 - 42; +#elif SAFETYHOOK_ARCH_X86_32 + ctx.ecx = 1337 - 42; +#endif +#elif SAFETYHOOK_OS_LINUX +#if SAFETYHOOK_ARCH_X86_64 + ctx.rdi = 1337 - 42; +#elif SAFETYHOOK_ARCH_X86_32 + ctx.edi = 1337 - 42; +#endif +#endif + } + }; + + auto hook_result = SafetyHookMid::create(Target::add_42, Hook::add_42, SafetyHookMid::StartDisabled); + + expect(hook_result.has_value()); + + hook = std::move(*hook_result); + + expect(Target::add_42(0) == 42_i); + expect(Target::add_42(1) == 43_i); + expect(Target::add_42(2) == 44_i); + + expect(hook.enable().has_value()); + + expect(Target::add_42(1) == 1337_i); + expect(Target::add_42(2) == 1337_i); + expect(Target::add_42(3) == 1337_i); + + expect(hook.disable().has_value()); + + expect(Target::add_42(0) == 42_i); + expect(Target::add_42(1) == 43_i); + expect(Target::add_42(2) == 44_i); + + hook.reset(); + + expect(Target::add_42(0) == 42_i); + expect(Target::add_42(1) == 43_i); + expect(Target::add_42(2) == 44_i); + }; }; \ No newline at end of file From a11e1638312453b164975156092a5d91b236df8f Mon Sep 17 00:00:00 2001 From: bottiger1 <55270538+bottiger1@users.noreply.github.com> Date: Mon, 13 May 2024 01:33:52 -0700 Subject: [PATCH 2/4] Add --polyfill option to amalgamate to modify source to use std::expected polyfill so it can be compiled on C++20. https://raw.githubusercontent.com/TartanLlama/expected/master/include/tl/expected.hpp --- amalgamate.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/amalgamate.py b/amalgamate.py index 2f2b51c..7dad719 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -7,6 +7,8 @@ import os import re +import sys +import argparse SAFETYHOOK_ROOT = Path(__file__).resolve().parent PUBLIC_INCLUDE_PATHS = [ @@ -18,6 +20,10 @@ OUTPUT_DIR = SAFETYHOOK_ROOT / 'amalgamated-dist' FILE_HEADER = ['// DO NOT EDIT. This file is auto-generated by `amalgamate.py`.', ''] +parser = argparse.ArgumentParser(description='bundles cpp and hpp files together') +parser.add_argument('--polyfill', action='store_true', + help='replace std::except with a polyfill so it can be compiled on C++20 or older. https://raw.githubusercontent.com/TartanLlama/expected/master/include/tl/expected.hpp') + # Python versions before 3.10 don't have the root_dir argument for glob, so we # crudely emulate it here. @@ -154,8 +160,15 @@ def merge_sources(*, source_dir: Path, covered_headers: Set[Path]): return output +def do_polyfill(content): + return content.replace('', '') \ + .replace('std::expected', 'tl::expected') \ + .replace('std::unexpected', 'tl::unexpected') def main(): + args = parser.parse_args() + polyfill = args.polyfill is True + if OUTPUT_DIR.exists(): print('Output directory exists. Deleting.') rmtree(OUTPUT_DIR) @@ -164,20 +177,26 @@ def main(): covered_headers = set() with open(OUTPUT_DIR / 'safetyhook.hpp', 'w') as f: - f.write('\n'.join(FILE_HEADER + merge_headers( + content = '\n'.join(FILE_HEADER + merge_headers( header='safetyhook.hpp', search_paths=PUBLIC_INCLUDE_PATHS, covered_headers=covered_headers, stack=[], - ))) + )) + if polyfill: + content = do_polyfill(content) + f.write(content) print(covered_headers) with open(OUTPUT_DIR / 'safetyhook.cpp', 'w') as f: - f.write('\n'.join(FILE_HEADER + merge_sources( + content = '\n'.join(FILE_HEADER + merge_sources( source_dir=SAFETYHOOK_ROOT / 'src', covered_headers=covered_headers, - ))) + )) + if polyfill: + content = do_polyfill(content) + f.write(content) if __name__ == '__main__': From 17be414633036b063063b1c761ca574886d64b6f Mon Sep 17 00:00:00 2001 From: bottiger1 <55270538+bottiger1@users.noreply.github.com> Date: Mon, 13 May 2024 17:46:10 -0700 Subject: [PATCH 3/4] allow for tl/expected.hpp --- amalgamate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amalgamate.py b/amalgamate.py index 7dad719..710744b 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -161,7 +161,7 @@ def merge_sources(*, source_dir: Path, covered_headers: Set[Path]): return output def do_polyfill(content): - return content.replace('', '') \ + return content.replace('#include ', '#if __has_include("expected.hpp")\n#include \n#else\n#include \n#endif\n') \ .replace('std::expected', 'tl::expected') \ .replace('std::unexpected', 'tl::unexpected') From c8d2a1471bc61bec1ec9d06d6c0ece525f59206b Mon Sep 17 00:00:00 2001 From: cursey Date: Tue, 14 May 2024 22:46:41 -0700 Subject: [PATCH 4/4] Fix cmake.toml and cleanup amalgamate.py --- CMakeLists.txt | 2 +- amalgamate.py | 22 +++++++++++++++++----- cmake.toml | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4807db5..df17dce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,7 +190,7 @@ if(SAFETYHOOK_AMALGAMATE) # amalgamate add_custom_command( OUTPUT ${AMALGAMATED_FILE} ${AMALGAMATED_HEADER} DEPENDS ${HEADER_FILES} ${SOURCE_FILES} ${AMALGAMATE_SCRIPT} - COMMAND ${Python3_EXECUTABLE} ${AMALGAMATE_SCRIPT} ${AMALGAMATED_FILE} ${AMALGAMATED_HEADER} + COMMAND ${Python3_EXECUTABLE} ${AMALGAMATE_SCRIPT} MAIN_DEPENDENCY ${AMALGAMATE_SCRIPT} COMMENT "Amalgamating" ) diff --git a/amalgamate.py b/amalgamate.py index 710744b..2a705cf 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -4,6 +4,7 @@ from typing import List, Set from glob import glob from shutil import rmtree +from textwrap import dedent import os import re @@ -21,8 +22,8 @@ FILE_HEADER = ['// DO NOT EDIT. This file is auto-generated by `amalgamate.py`.', ''] parser = argparse.ArgumentParser(description='bundles cpp and hpp files together') -parser.add_argument('--polyfill', action='store_true', - help='replace std::except with a polyfill so it can be compiled on C++20 or older. https://raw.githubusercontent.com/TartanLlama/expected/master/include/tl/expected.hpp') +parser.add_argument('--polyfill', action='store_true', + help='replace std::except with a polyfill so it can be compiled on C++20 or older. https://raw.githubusercontent.com/TartanLlama/expected/master/include/tl/expected.hpp') # Python versions before 3.10 don't have the root_dir argument for glob, so we @@ -160,10 +161,21 @@ def merge_sources(*, source_dir: Path, covered_headers: Set[Path]): return output + def do_polyfill(content): - return content.replace('#include ', '#if __has_include("expected.hpp")\n#include \n#else\n#include \n#endif\n') \ - .replace('std::expected', 'tl::expected') \ - .replace('std::unexpected', 'tl::unexpected') + return content.replace('#include ', + dedent(''' + #if __has_include("tl/expected.hpp") + #include "tl/expected.hpp" + #elif __has_include("expected.hpp") + #include "expected.hpp" + #else + #error "No polyfill found" + #endif + ''')) \ + .replace('std::expected', 'tl::expected') \ + .replace('std::unexpected', 'tl::unexpected') + def main(): args = parser.parse_args() diff --git a/cmake.toml b/cmake.toml index 70abb24..df17b9f 100644 --- a/cmake.toml +++ b/cmake.toml @@ -97,7 +97,7 @@ set(AMALGAMATE_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/amalgamate.py) add_custom_command( OUTPUT ${AMALGAMATED_FILE} ${AMALGAMATED_HEADER} DEPENDS ${HEADER_FILES} ${SOURCE_FILES} ${AMALGAMATE_SCRIPT} - COMMAND ${Python3_EXECUTABLE} ${AMALGAMATE_SCRIPT} ${AMALGAMATED_FILE} ${AMALGAMATED_HEADER} + COMMAND ${Python3_EXECUTABLE} ${AMALGAMATE_SCRIPT} MAIN_DEPENDENCY ${AMALGAMATE_SCRIPT} COMMENT "Amalgamating" )