//! Custom allocation framekwork for frozen (RO) and/or secure allocations, shared allocations, aliased allocation, etc. (polymorphic.) #ifndef _ALLOC_H #define _ALLOC_H #ifdef __cplusplus #include #include #endif #include "constraints.hh" #include "types.h" #include "macros.h" LINK_START(C) typedef struct base_allocator alloc_t; //TODO: C API anonlogue for allocator interface below. LINK_END #if $CXX extern "C" { struct base_allocator { //TODO: Allocator interface... virtual ~base_allocator(); } } namespace alloc { class FrozenAllocator : public alloc_t { struct _impl; protected: struct anon_raw_secmem; // Untyped deleter struct deleter { friend class anon_raw_secmem; constexpr deleter(const deleter&) noexcept = default; constexpr deleter& operator=(const deleter&) noexcept = default; constexpr deleter(deleter&&) noexcept = default; constexpr deleter& operator=(deleter&&) noexcept = default; [[gnu::nonnull(2)]] constexpr void operator()(void* restrict p) const noexcept { apply_delete(p); } virtual ~deleter(); protected: explicit deleter(std::shared_ptr&& p); [[gnu::nonnull(2)]] virtual std::pair apply_delete(void* restrict, bool = true) const noexcept; virtual void scramble_memory(std::initializer_list> ptrs) const noexcept; inline void scramble_memory(std::pair ptr) const noexcept { return scramble_memory({ ptr }); } // To prevent anon_raw_secmem being destroyed while there are still allocated values, the base class for the deleter for those values contains a refcount. e.g: `std::unique_ptr>` where: `deleter_for final : public deleter { virtual ~deleter_for(); ... };`, or `std::shared_ptr`, where: `std::shared_ptr>` aliases-ctor(`old, old->value_ptr()`) -> `std::shared_ptr` std::shared_ptr m_manager_ref; }; struct alloc_vt; struct alloc_info; struct alloc_value; template struct deleter_for; template struct deleter_for_value; public: FrozenAllocator(FrozenAllocator &&) noexcept; FrozenAllocator& operator=(FrozenAllocator &&); FrozenAllocator(const FrozenAllocator&) = delete; FrozenAllocator& operator=(const FrozenAllocator&) = delete; virtual ~FrozenAllocator(); private: std::unique_ptr<_impl> inner_; /// Manages everything about the actual allocations //std::shared_ptr m_manager; /// A map of values inside the allocator. This is destroyed in reverse order, meaning all the living values are destroyed *before* `m_manager`'d destruction deallocates them. //std::map> m_values; }; template struct FrozenAllocator::deleter_for : virtual deleter { inline deleter_for(std::shared_ptr&& m) : deleter(std::move(m)) {} virtual ~deleter_for() = default; // This will use deleter's dtor to remove allocations. [[gnu::nonnull(2)]] inline void operator()(T* ptr) const noexcept { apply_delete(deleter::erase_type_unsafe(ptr)); } protected: inline virtual void apply_delete_typed(T* ptr) const noexcept { ptr->~T(); } private: [[gnu::nonnull(2)]] inline std::pair apply_delete(void* restrict up) const noexcept override final { if constexpr(std::is_trivially_destructible_v) { return deleter::apply_delete(up); // If the dtor is trivial, ignore it and use default behaviour. } else { auto pair = deleter::apply_delete(up, false); // Unlock the memory and remove the allocation from `anon_raw_secmem`, but do *not* bzero it. apply_delete_typed(static_cast(up)); // Apply the destructor for `T` to the alligned pointer `up`. deleter::scramble_memory(pair); // *now* bzero the unaligned pointer (full range, including alignment padding.) return pair; } } }; template struct FrozenAllocator::deleter_for_value final : deleter_for { //TODO: Re-work this? Is it useful? Is it needed? inline virtual ~deleter_for_value() { deleter_for::apply_delete(m_value_ptr); } private: T* m_value_ptr; }; } #endif #endif /* _ALLOC_H */