From cd7f17fbc71305a1c0e9d73b3eb2c86a96a5eb99 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 17:07:36 +0000 Subject: [PATCH 1/9] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/netkeep80/PersistMemoryManager/issues/312 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 00000000..9a0f4b0a --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-04-19T17:07:36.213Z for PR creation at branch issue-312-65d10bb90ae2 for issue https://github.com/netkeep80/PersistMemoryManager/issues/312 \ No newline at end of file From ef0343b4551b7505da3964eb0407072b0d943792 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 17:40:53 +0000 Subject: [PATCH 2/9] Clarify pptr access modes --- .gitkeep | 1 - include/pmm/forest_domain_mixin.inc | 13 ++- include/pmm/persist_memory_manager.h | 112 +++++++++++++++----- include/pmm/pptr.h | 21 ++-- single_include/pmm/pmm.h | 146 +++++++++++++++++++-------- tests/CMakeLists.txt | 3 + tests/test_issue312_access_modes.cpp | 73 ++++++++++++++ 7 files changed, 288 insertions(+), 81 deletions(-) delete mode 100644 .gitkeep create mode 100644 tests/test_issue312_access_modes.cpp diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index 9a0f4b0a..00000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-04-19T17:07:36.213Z for PR creation at branch issue-312-65d10bb90ae2 for issue https://github.com/netkeep80/PersistMemoryManager/issues/312 \ No newline at end of file diff --git a/include/pmm/forest_domain_mixin.inc b/include/pmm/forest_domain_mixin.inc index c9df22eb..b41e1904 100644 --- a/include/pmm/forest_domain_mixin.inc +++ b/include/pmm/forest_domain_mixin.inc @@ -67,7 +67,7 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) { if ( symbol.is_null() ) return nullptr; - pstringview* sym = resolve( symbol ); + pstringview* sym = resolve_unchecked( symbol ); if ( sym == nullptr ) return nullptr; forest_registry* reg = forest_registry_root_unlocked(); @@ -194,10 +194,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept symbol_domain->root_offset, [&]( pptr cur ) -> int { - pstringview* obj = resolve( cur ); + pstringview* obj = resolve_unchecked( cur ); return ( obj != nullptr ) ? std::strcmp( s, obj->c_str() ) : 0; }, - []( pptr p ) -> pstringview* { return resolve( p ); } ); + []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); if ( !found.is_null() ) return found; @@ -207,8 +207,7 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept if ( raw == nullptr ) return pptr(); - std::uint8_t* base = _backend.base_ptr(); - pptr new_node( detail::ptr_to_granule_idx( base, raw ) ); + pptr new_node = make_pptr_from_raw( raw ); // Use memcpy to avoid UB on potentially misaligned raw pointer (ASan/UBSan fix). std::memcpy( raw, &len, sizeof( len ) ); char* str_dst = static_cast( raw ) + offsetof( pstringview, str ); @@ -224,10 +223,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool { - pstringview* cur_obj = resolve( cur ); + pstringview* cur_obj = resolve_unchecked( cur ); return ( cur_obj != nullptr ) && ( std::strcmp( new_str, cur_obj->c_str() ) < 0 ); }, - []( pptr p ) -> pstringview* { return resolve( p ); } ); + []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); return new_node; } diff --git a/include/pmm/persist_memory_manager.h b/include/pmm/persist_memory_manager.h index 5149ed2b..15dad1c0 100644 --- a/include/pmm/persist_memory_manager.h +++ b/include/pmm/persist_memory_manager.h @@ -522,8 +522,7 @@ template cla { if ( p.is_null() || !_initialized ) return; - std::uint8_t* base = _backend.base_ptr(); - void* raw = base + static_cast( p.offset() ) * address_traits::granule_size; + void* raw = resolve_unchecked( p ); deallocate( raw ); } @@ -558,10 +557,7 @@ template cla } std::uint8_t* base = _backend.base_ptr(); detail::ManagerHeader* hdr = get_header( base ); - // blk_idx = pptr.offset - floor(sizeof(Block) / granule) - static constexpr index_type kBlkHdrFloorGran = - static_cast( sizeof( Block ) / address_traits::granule_size ); - index_type blk_idx = static_cast( p.offset() - kBlkHdrFloorGran ); + index_type blk_idx = block_idx_from_pptr( p ); void* blk_raw = detail::block_at( base, blk_idx ); index_type old_data_gran = BlockStateBase::get_weight( blk_raw ); index_type new_data_gran = detail::bytes_to_granules_t( new_user_size ); @@ -593,8 +589,6 @@ template cla } } // Fallback: allocate new + memmove + free old (under same lock). - static constexpr index_type kBlkHdrFloorGranFb = - static_cast( sizeof( Block ) / address_traits::granule_size ); index_type new_data_gran_alloc = detail::bytes_to_granules_t( new_user_size ); if ( new_data_gran_alloc == 0 ) new_data_gran_alloc = 1; @@ -630,12 +624,12 @@ template cla return pptr(); } pptr new_p = make_pptr_from_raw( new_raw ); - void* new_dst = base + static_cast( new_p.offset() ) * address_traits::granule_size; - void* old_src = base + static_cast( p.offset() ) * address_traits::granule_size; + void* new_dst = resolve_unchecked( new_p ); + void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); std::memmove( new_dst, old_src, copy_sz ); // Free old block - index_type old_blk_idx = static_cast( p.offset() - kBlkHdrFloorGranFb ); + index_type old_blk_idx = block_idx_from_pptr( p ); void* old_blk_raw = detail::block_at( base, old_blk_idx ); index_type freed_w = BlockStateBase::get_weight( old_blk_raw ); if ( BlockStateBase::get_node_type( old_blk_raw ) != pmm::kNodeReadOnly ) @@ -708,8 +702,7 @@ template cla if ( p.is_null() || !_initialized ) return; - std::uint8_t* base = _backend.base_ptr(); - void* raw = base + static_cast( p.offset() ) * address_traits::granule_size; + void* raw = resolve_unchecked( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } @@ -723,15 +716,16 @@ template cla } /** - * @brief Разыменовать pptr — получить сырой указатель T*. + * @brief Быстро разыменовать pptr без проверки состояния блока. * - * Этот статический метод вызывается из `pptr::resolve()`. + * Проверяет только null, инициализацию менеджера и границы буфера. Не проверяет, + * что pptr указывает на текущий выделенный блок. * * @tparam T Тип данных. * @param p Персистентный указатель. - * @return T* — указатель на данные или nullptr при ошибке. + * @return T* — указатель на данные или nullptr при грубой ошибке адреса. */ - template static T* resolve( pptr p ) noexcept + template static T* resolve_unchecked( pptr p ) noexcept { if ( p.is_null() || !_initialized ) return nullptr; @@ -743,9 +737,71 @@ template cla _last_error = PmmError::InvalidPointer; return nullptr; } - return reinterpret_cast( base + byte_off ); + if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) + { + return reinterpret_cast( base + byte_off ); + } + else + { + constexpr std::size_t hdr_granules = + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( p.offset() < hdr_granules ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + return reinterpret_cast( base + blk_off + sizeof( Block ) ); + } + } + + /** + * @brief Разыменовать pptr с публичной проверкой live allocated block. + * + * Этот путь проверяет не только границы буфера, но и заголовок блока: + * pptr должен указывать на текущий занятый блок. Stale pptr после + * deallocate_typed() возвращает nullptr. + * + * @tparam T Тип данных. + * @param p Персистентный указатель. + * @return T* — указатель на данные или nullptr при ошибке. + */ + template static T* resolve_checked( pptr p ) noexcept + { + T* raw = resolve_unchecked( p ); + if ( raw == nullptr ) + return nullptr; + + const void* blk_raw = find_block_from_user_ptr( raw ); + if ( blk_raw == nullptr ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + if ( BlockStateBase::get_weight( blk_raw ) == 0 || + BlockStateBase::get_root_offset( blk_raw ) == 0 ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + _last_error = PmmError::Ok; + return raw; } + /** + * @brief Совместимый публичный checked access. + * + * Старое имя сохранено как alias, но его семантика теперь совпадает с + * resolve_checked(). Внутренний код, которому нужен только offset->address, + * должен явно использовать resolve_unchecked(). + */ + template static T* resolve( pptr p ) noexcept { return resolve_checked( p ); } + /** * @brief Разыменовать pptr и получить указатель на i-й элемент массива. * @@ -756,7 +812,7 @@ template cla */ template static T* resolve_at( pptr p, std::size_t i ) noexcept { - T* base_elem = resolve( p ); + T* base_elem = resolve_checked( p ); return ( base_elem == nullptr ) ? nullptr : base_elem + i; } @@ -792,13 +848,7 @@ template cla * @param p Персистентный указатель. * @return true если pptr валиден (в пределах кучи), false если null или вне границ. */ - template static bool is_valid_ptr( pptr p ) noexcept - { - if ( p.is_null() || !_initialized ) - return false; - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - return byte_off + sizeof( T ) <= _backend.total_size(); - } + template static bool is_valid_ptr( pptr p ) noexcept { return resolve_checked( p ) != nullptr; } // ─── Root object API ────────────────────────────── @@ -1399,7 +1449,8 @@ template cla if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) return pptr(); std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = byte_off / address_traits::granule_size; + std::size_t idx = + ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; if ( idx > static_cast( std::numeric_limits::max() ) ) return pptr(); return pptr( static_cast( idx ) ); @@ -1436,6 +1487,13 @@ template cla return base + blk_off; } + template static constexpr index_type block_idx_from_pptr( pptr p ) noexcept + { + constexpr index_type kHdrGranules = static_cast( + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); + return static_cast( p.offset() - kHdrGranules ); + } + // ─── Address-traits-specific layout constants ────────────────── // These compute the correct granule indices based on the actual address_traits // granule size, rather than using the hardcoded DefaultAddressTraits constants. diff --git a/include/pmm/pptr.h b/include/pmm/pptr.h index 29faef3c..14441a0d 100644 --- a/include/pmm/pptr.h +++ b/include/pmm/pptr.h @@ -169,27 +169,27 @@ class pptr /** * @brief Разыменование указателя (статическая модель). * - * Вызывает `ManagerT::resolve(*this)` без аргументов. + * Вызывает `ManagerT::resolve_checked(*this)` без аргументов. * Доступно только для менеджеров со статическим API (например, PersistMemoryManager). * * @return T& — ссылка на данные. */ - T& operator*() const noexcept { return *ManagerT::template resolve( *this ); } + T& operator*() const noexcept { return *ManagerT::template resolve_checked( *this ); } /** * @brief Доступ к членам через персистентный указатель (статическая модель). * - * Вызывает `ManagerT::resolve(*this)` без аргументов. + * Вызывает `ManagerT::resolve_checked(*this)` без аргументов. * Доступно только для менеджеров со статическим API. * * @return T* — указатель на данные. */ - T* operator->() const noexcept { return ManagerT::template resolve( *this ); } + T* operator->() const noexcept { return ManagerT::template resolve_checked( *this ); } /** * @brief Получить сырой указатель (низкоуровневый доступ). * - * Вызывает `ManagerT::resolve(*this)`. + * Вызывает `ManagerT::resolve_checked(*this)`. * Используйте `*p` или `p->field` вместо этого метода для обычных операций. * Для доступа к элементам массива используйте `ManagerT::resolve_at(p, i)`. * @@ -198,7 +198,16 @@ class pptr * * @return T* — указатель на данные или nullptr если is_null(). */ - T* resolve() const noexcept { return ManagerT::template resolve( *this ); } + T* resolve() const noexcept { return ManagerT::template resolve_checked( *this ); } + + /** + * @brief Получить сырой указатель через unchecked manager path. + * + * Проверяет только грубую адресуемость pptr. Не проверяет, что блок сейчас + * выделен. Предназначено для внутреннего кода менеджера и низкоуровневой + * диагностики, где stale/free-block access выбран явно. + */ + T* resolve_unchecked() const noexcept { return ManagerT::template resolve_unchecked( *this ); } // ─── Доступ к узлу AVL-дерева ──────────────────────────────── diff --git a/single_include/pmm/pmm.h b/single_include/pmm/pmm.h index d7294d0a..bf0bc285 100644 --- a/single_include/pmm/pmm.h +++ b/single_include/pmm/pmm.h @@ -6257,27 +6257,27 @@ class pptr /** * @brief Разыменование указателя (статическая модель). * - * Вызывает `ManagerT::resolve(*this)` без аргументов. + * Вызывает `ManagerT::resolve_checked(*this)` без аргументов. * Доступно только для менеджеров со статическим API (например, PersistMemoryManager). * * @return T& — ссылка на данные. */ - T& operator*() const noexcept { return *ManagerT::template resolve( *this ); } + T& operator*() const noexcept { return *ManagerT::template resolve_checked( *this ); } /** * @brief Доступ к членам через персистентный указатель (статическая модель). * - * Вызывает `ManagerT::resolve(*this)` без аргументов. + * Вызывает `ManagerT::resolve_checked(*this)` без аргументов. * Доступно только для менеджеров со статическим API. * * @return T* — указатель на данные. */ - T* operator->() const noexcept { return ManagerT::template resolve( *this ); } + T* operator->() const noexcept { return ManagerT::template resolve_checked( *this ); } /** * @brief Получить сырой указатель (низкоуровневый доступ). * - * Вызывает `ManagerT::resolve(*this)`. + * Вызывает `ManagerT::resolve_checked(*this)`. * Используйте `*p` или `p->field` вместо этого метода для обычных операций. * Для доступа к элементам массива используйте `ManagerT::resolve_at(p, i)`. * @@ -6286,7 +6286,16 @@ class pptr * * @return T* — указатель на данные или nullptr если is_null(). */ - T* resolve() const noexcept { return ManagerT::template resolve( *this ); } + T* resolve() const noexcept { return ManagerT::template resolve_checked( *this ); } + + /** + * @brief Получить сырой указатель через unchecked manager path. + * + * Проверяет только грубую адресуемость pptr. Не проверяет, что блок сейчас + * выделен. Предназначено для внутреннего кода менеджера и низкоуровневой + * диагностики, где stale/free-block access выбран явно. + */ + T* resolve_unchecked() const noexcept { return ManagerT::template resolve_unchecked( *this ); } // ─── Доступ к узлу AVL-дерева ──────────────────────────────── @@ -7552,8 +7561,7 @@ template cla { if ( p.is_null() || !_initialized ) return; - std::uint8_t* base = _backend.base_ptr(); - void* raw = base + static_cast( p.offset() ) * address_traits::granule_size; + void* raw = resolve_unchecked( p ); deallocate( raw ); } @@ -7588,10 +7596,7 @@ template cla } std::uint8_t* base = _backend.base_ptr(); detail::ManagerHeader* hdr = get_header( base ); - // blk_idx = pptr.offset - floor(sizeof(Block) / granule) - static constexpr index_type kBlkHdrFloorGran = - static_cast( sizeof( Block ) / address_traits::granule_size ); - index_type blk_idx = static_cast( p.offset() - kBlkHdrFloorGran ); + index_type blk_idx = block_idx_from_pptr( p ); void* blk_raw = detail::block_at( base, blk_idx ); index_type old_data_gran = BlockStateBase::get_weight( blk_raw ); index_type new_data_gran = detail::bytes_to_granules_t( new_user_size ); @@ -7623,8 +7628,6 @@ template cla } } // Fallback: allocate new + memmove + free old (under same lock). - static constexpr index_type kBlkHdrFloorGranFb = - static_cast( sizeof( Block ) / address_traits::granule_size ); index_type new_data_gran_alloc = detail::bytes_to_granules_t( new_user_size ); if ( new_data_gran_alloc == 0 ) new_data_gran_alloc = 1; @@ -7660,12 +7663,12 @@ template cla return pptr(); } pptr new_p = make_pptr_from_raw( new_raw ); - void* new_dst = base + static_cast( new_p.offset() ) * address_traits::granule_size; - void* old_src = base + static_cast( p.offset() ) * address_traits::granule_size; + void* new_dst = resolve_unchecked( new_p ); + void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); std::memmove( new_dst, old_src, copy_sz ); // Free old block - index_type old_blk_idx = static_cast( p.offset() - kBlkHdrFloorGranFb ); + index_type old_blk_idx = block_idx_from_pptr( p ); void* old_blk_raw = detail::block_at( base, old_blk_idx ); index_type freed_w = BlockStateBase::get_weight( old_blk_raw ); if ( BlockStateBase::get_node_type( old_blk_raw ) != pmm::kNodeReadOnly ) @@ -7738,8 +7741,7 @@ template cla if ( p.is_null() || !_initialized ) return; - std::uint8_t* base = _backend.base_ptr(); - void* raw = base + static_cast( p.offset() ) * address_traits::granule_size; + void* raw = resolve_unchecked( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } @@ -7753,15 +7755,16 @@ template cla } /** - * @brief Разыменовать pptr — получить сырой указатель T*. + * @brief Быстро разыменовать pptr без проверки состояния блока. * - * Этот статический метод вызывается из `pptr::resolve()`. + * Проверяет только null, инициализацию менеджера и границы буфера. Не проверяет, + * что pptr указывает на текущий выделенный блок. * * @tparam T Тип данных. * @param p Персистентный указатель. - * @return T* — указатель на данные или nullptr при ошибке. + * @return T* — указатель на данные или nullptr при грубой ошибке адреса. */ - template static T* resolve( pptr p ) noexcept + template static T* resolve_unchecked( pptr p ) noexcept { if ( p.is_null() || !_initialized ) return nullptr; @@ -7773,9 +7776,71 @@ template cla _last_error = PmmError::InvalidPointer; return nullptr; } - return reinterpret_cast( base + byte_off ); + if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) + { + return reinterpret_cast( base + byte_off ); + } + else + { + constexpr std::size_t hdr_granules = + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( p.offset() < hdr_granules ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + return reinterpret_cast( base + blk_off + sizeof( Block ) ); + } } + /** + * @brief Разыменовать pptr с публичной проверкой live allocated block. + * + * Этот путь проверяет не только границы буфера, но и заголовок блока: + * pptr должен указывать на текущий занятый блок. Stale pptr после + * deallocate_typed() возвращает nullptr. + * + * @tparam T Тип данных. + * @param p Персистентный указатель. + * @return T* — указатель на данные или nullptr при ошибке. + */ + template static T* resolve_checked( pptr p ) noexcept + { + T* raw = resolve_unchecked( p ); + if ( raw == nullptr ) + return nullptr; + + const void* blk_raw = find_block_from_user_ptr( raw ); + if ( blk_raw == nullptr ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + if ( BlockStateBase::get_weight( blk_raw ) == 0 || + BlockStateBase::get_root_offset( blk_raw ) == 0 ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + _last_error = PmmError::Ok; + return raw; + } + + /** + * @brief Совместимый публичный checked access. + * + * Старое имя сохранено как alias, но его семантика теперь совпадает с + * resolve_checked(). Внутренний код, которому нужен только offset->address, + * должен явно использовать resolve_unchecked(). + */ + template static T* resolve( pptr p ) noexcept { return resolve_checked( p ); } + /** * @brief Разыменовать pptr и получить указатель на i-й элемент массива. * @@ -7786,7 +7851,7 @@ template cla */ template static T* resolve_at( pptr p, std::size_t i ) noexcept { - T* base_elem = resolve( p ); + T* base_elem = resolve_checked( p ); return ( base_elem == nullptr ) ? nullptr : base_elem + i; } @@ -7822,13 +7887,7 @@ template cla * @param p Персистентный указатель. * @return true если pptr валиден (в пределах кучи), false если null или вне границ. */ - template static bool is_valid_ptr( pptr p ) noexcept - { - if ( p.is_null() || !_initialized ) - return false; - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - return byte_off + sizeof( T ) <= _backend.total_size(); - } + template static bool is_valid_ptr( pptr p ) noexcept { return resolve_checked( p ) != nullptr; } // ─── Root object API ────────────────────────────── @@ -8464,7 +8523,7 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) { if ( symbol.is_null() ) return nullptr; - pstringview* sym = resolve( symbol ); + pstringview* sym = resolve_unchecked( symbol ); if ( sym == nullptr ) return nullptr; forest_registry* reg = forest_registry_root_unlocked(); @@ -8591,10 +8650,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept symbol_domain->root_offset, [&]( pptr cur ) -> int { - pstringview* obj = resolve( cur ); + pstringview* obj = resolve_unchecked( cur ); return ( obj != nullptr ) ? std::strcmp( s, obj->c_str() ) : 0; }, - []( pptr p ) -> pstringview* { return resolve( p ); } ); + []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); if ( !found.is_null() ) return found; @@ -8604,8 +8663,7 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept if ( raw == nullptr ) return pptr(); - std::uint8_t* base = _backend.base_ptr(); - pptr new_node( detail::ptr_to_granule_idx( base, raw ) ); + pptr new_node = make_pptr_from_raw( raw ); // Use memcpy to avoid UB on potentially misaligned raw pointer (ASan/UBSan fix). std::memcpy( raw, &len, sizeof( len ) ); char* str_dst = static_cast( raw ) + offsetof( pstringview, str ); @@ -8621,10 +8679,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool { - pstringview* cur_obj = resolve( cur ); + pstringview* cur_obj = resolve_unchecked( cur ); return ( cur_obj != nullptr ) && ( std::strcmp( new_str, cur_obj->c_str() ) < 0 ); }, - []( pptr p ) -> pstringview* { return resolve( p ); } ); + []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); return new_node; } @@ -9037,7 +9095,8 @@ static void verify_forest_registry_unlocked( VerifyResult& result ) noexcept if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) return pptr(); std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = byte_off / address_traits::granule_size; + std::size_t idx = + ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; if ( idx > static_cast( std::numeric_limits::max() ) ) return pptr(); return pptr( static_cast( idx ) ); @@ -9074,6 +9133,13 @@ static void verify_forest_registry_unlocked( VerifyResult& result ) noexcept return base + blk_off; } + template static constexpr index_type block_idx_from_pptr( pptr p ) noexcept + { + constexpr index_type kHdrGranules = static_cast( + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); + return static_cast( p.offset() - kHdrGranules ); + } + // ─── Address-traits-specific layout constants ────────────────── // These compute the correct granule indices based on the actual address_traits // granule size, rather than using the hardcoded DefaultAddressTraits constants. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0003e808..caf3f4c2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -296,6 +296,9 @@ target_compile_definitions(test_issue295_layout_compaction PRIVATE PMM_SOURCE_DI pmm_add_test(test_issue306_repo_guard_gitkeep test_issue306_repo_guard_gitkeep.cpp) target_compile_definitions(test_issue306_repo_guard_gitkeep PRIVATE PMM_SOURCE_DIR="${CMAKE_SOURCE_DIR}") +# ─── Issue 312: explicit pptr checked/unchecked access modes ──── +pmm_add_test(test_issue312_access_modes test_issue312_access_modes.cpp) + # ─── Issue 314: build graph compaction contract ───────────────── add_test( NAME test_issue314_build_graph_contract diff --git a/tests/test_issue312_access_modes.cpp b/tests/test_issue312_access_modes.cpp new file mode 100644 index 00000000..13c5d1d9 --- /dev/null +++ b/tests/test_issue312_access_modes.cpp @@ -0,0 +1,73 @@ +/** + * @file test_issue312_access_modes.cpp + * @brief Tests for explicit pptr checked/unchecked access modes. + */ + +#include "pmm/persist_memory_manager.h" +#include "pmm/pmm_presets.h" + +#include +#include + +namespace +{ +using Mgr = pmm::PersistMemoryManager; +} + +TEST_CASE( "I312: valid pptr resolves through checked and unchecked paths", "[test_issue312]" ) +{ + REQUIRE( Mgr::create( 64 * 1024 ) ); + + Mgr::pptr p = Mgr::allocate_typed(); + REQUIRE( !p.is_null() ); + + REQUIRE( Mgr::resolve_checked( p ) != nullptr ); + REQUIRE( Mgr::resolve_unchecked( p ) != nullptr ); + REQUIRE( Mgr::resolve( p ) == Mgr::resolve_checked( p ) ); + REQUIRE( p.resolve() == Mgr::resolve_checked( p ) ); + REQUIRE( p.resolve_unchecked() == Mgr::resolve_unchecked( p ) ); + + *p = 0x312u; + REQUIRE( *Mgr::resolve_checked( p ) == 0x312u ); + + Mgr::deallocate_typed( p ); + Mgr::destroy(); +} + +TEST_CASE( "I312: invalid out-of-range pptr is rejected by both access modes", "[test_issue312]" ) +{ + REQUIRE( Mgr::create( 64 * 1024 ) ); + + Mgr::pptr invalid( static_cast( Mgr::total_size() ) ); + + REQUIRE( Mgr::resolve_checked( invalid ) == nullptr ); + REQUIRE( Mgr::resolve_unchecked( invalid ) == nullptr ); + REQUIRE_FALSE( Mgr::is_valid_ptr( invalid ) ); + + Mgr::destroy(); +} + +TEST_CASE( "I312: stale pptr is rejected by checked access but explicit unchecked access remains raw", + "[test_issue312]" ) +{ + REQUIRE( Mgr::create( 64 * 1024 ) ); + + Mgr::pptr stale = Mgr::allocate_typed(); + REQUIRE( !stale.is_null() ); + *stale = 0xDEAD312u; + + std::uint32_t* raw_before_free = Mgr::resolve_unchecked( stale ); + REQUIRE( raw_before_free != nullptr ); + + Mgr::deallocate_typed( stale ); + + REQUIRE( Mgr::resolve_checked( stale ) == nullptr ); + REQUIRE( Mgr::resolve( stale ) == nullptr ); + REQUIRE( stale.resolve() == nullptr ); + REQUIRE_FALSE( Mgr::is_valid_ptr( stale ) ); + + REQUIRE( Mgr::resolve_unchecked( stale ) == raw_before_free ); + REQUIRE( stale.resolve_unchecked() == raw_before_free ); + + Mgr::destroy(); +} From 81535d0867c43a29e096c902afe2f43bb36a38c4 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 17:49:07 +0000 Subject: [PATCH 3/9] Satisfy header guard checks --- include/pmm/forest_domain_mixin.inc | 74 +++++++++ include/pmm/persist_memory_manager.h | 85 +---------- single_include/pmm/pmm.h | 159 ++++++++++---------- single_include/pmm/pmm_no_comments.h | 217 ++++++++++++++++----------- 4 files changed, 286 insertions(+), 249 deletions(-) diff --git a/include/pmm/forest_domain_mixin.inc b/include/pmm/forest_domain_mixin.inc index b41e1904..d3d98c7b 100644 --- a/include/pmm/forest_domain_mixin.inc +++ b/include/pmm/forest_domain_mixin.inc @@ -495,3 +495,77 @@ static void for_each_free_block_inorder( const std::uint8_t* base, const detail: // Visit right subtree (larger blocks) for_each_free_block_inorder( base, hdr, right_off, depth + 1, callback ); } + +/// @brief Find the mutable block header for a user-data pointer (or nullptr). +static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + detail::ManagerHeader* hdr = get_header( base ); + return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); +} + +/// @brief Find the const block header for a user-data pointer. +/// Returns nullptr if ptr is out of range or the block header is invalid. +static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept +{ + const std::uint8_t* base = _backend.base_ptr(); + return detail::header_from_ptr_t( + const_cast( base ), const_cast( ptr ), + static_cast( get_header_c( base )->total_size ) ); +} + +// ─── raw ↔ pptr helpers ─────────────────────────────────────── + +/// @brief Convert a raw user-data pointer returned by allocate() into a pptr. +/// Caller must ensure raw != nullptr and _initialized before calling. +/// Returns null pptr if the pointer is not within the managed region. +template static pptr make_pptr_from_raw( void* raw ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + auto* raw_byte = static_cast( raw ); + if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) + return pptr(); + std::size_t byte_off = static_cast( raw_byte - base ); + std::size_t idx = ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( idx > static_cast( std::numeric_limits::max() ) ) + return pptr(); + return pptr( static_cast( idx ) ); +} + +// ─── blk_raw helpers ────────────────────────────────────────── +// base + offset * granule_size - sizeof(Block) → block header before user data. + +/// @brief Return a const pointer to the block header for the given pptr. +/// Returns nullptr if offset is invalid (would place block header before base). +template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept +{ + const std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off < sizeof( Block ) ) + return nullptr; + std::size_t blk_off = byte_off - sizeof( Block ); + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off; +} + +/// @brief Return a mutable pointer to the block header for the given pptr. +/// Returns nullptr if offset is invalid (would place block header before base). +template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off < sizeof( Block ) ) + return nullptr; + std::size_t blk_off = byte_off - sizeof( Block ); + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off; +} + +template static constexpr index_type block_idx_from_pptr( pptr p ) noexcept +{ + constexpr index_type kHdrGranules = static_cast( + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); + return static_cast( p.offset() - kHdrGranules ); +} diff --git a/include/pmm/persist_memory_manager.h b/include/pmm/persist_memory_manager.h index 15dad1c0..7c56fec7 100644 --- a/include/pmm/persist_memory_manager.h +++ b/include/pmm/persist_memory_manager.h @@ -555,11 +555,11 @@ template cla _last_error = PmmError::NotInitialized; return pptr(); } - std::uint8_t* base = _backend.base_ptr(); - detail::ManagerHeader* hdr = get_header( base ); - index_type blk_idx = block_idx_from_pptr( p ); - void* blk_raw = detail::block_at( base, blk_idx ); - index_type old_data_gran = BlockStateBase::get_weight( blk_raw ); + std::uint8_t* base = _backend.base_ptr(); + detail::ManagerHeader* hdr = get_header( base ); + index_type blk_idx = block_idx_from_pptr( p ); + void* blk_raw = detail::block_at( base, blk_idx ); + index_type old_data_gran = BlockStateBase::get_weight( blk_raw ); index_type new_data_gran = detail::bytes_to_granules_t( new_user_size ); if ( new_data_gran == 0 ) new_data_gran = 1; @@ -1419,81 +1419,6 @@ template cla // Verify/repair methods — extracted to verify_repair_mixin.inc. #include "pmm/verify_repair_mixin.inc" - /// @brief Find the mutable block header for a user-data pointer (or nullptr). - static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - detail::ManagerHeader* hdr = get_header( base ); - return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); - } - - /// @brief Find the const block header for a user-data pointer. - /// Returns nullptr if ptr is out of range or the block header is invalid. - static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept - { - const std::uint8_t* base = _backend.base_ptr(); - return detail::header_from_ptr_t( - const_cast( base ), const_cast( ptr ), - static_cast( get_header_c( base )->total_size ) ); - } - - // ─── raw ↔ pptr helpers ─────────────────────────────────────── - - /// @brief Convert a raw user-data pointer returned by allocate() into a pptr. - /// Caller must ensure raw != nullptr and _initialized before calling. - /// Returns null pptr if the pointer is not within the managed region. - template static pptr make_pptr_from_raw( void* raw ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - auto* raw_byte = static_cast( raw ); - if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) - return pptr(); - std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = - ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; - if ( idx > static_cast( std::numeric_limits::max() ) ) - return pptr(); - return pptr( static_cast( idx ) ); - } - - // ─── blk_raw helpers ────────────────────────────────────────── - // base + offset * granule_size - sizeof(Block) → block header before user data. - - /// @brief Return a const pointer to the block header for the given pptr. - /// Returns nullptr if offset is invalid (would place block header before base). - template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept - { - const std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) - return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - return nullptr; - return base + blk_off; - } - - /// @brief Return a mutable pointer to the block header for the given pptr. - /// Returns nullptr if offset is invalid (would place block header before base). - template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) - return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - return nullptr; - return base + blk_off; - } - - template static constexpr index_type block_idx_from_pptr( pptr p ) noexcept - { - constexpr index_type kHdrGranules = static_cast( - ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); - return static_cast( p.offset() - kHdrGranules ); - } - // ─── Address-traits-specific layout constants ────────────────── // These compute the correct granule indices based on the actual address_traits // granule size, rather than using the hardcoded DefaultAddressTraits constants. diff --git a/single_include/pmm/pmm.h b/single_include/pmm/pmm.h index bf0bc285..8fbee208 100644 --- a/single_include/pmm/pmm.h +++ b/single_include/pmm/pmm.h @@ -7594,11 +7594,11 @@ template cla _last_error = PmmError::NotInitialized; return pptr(); } - std::uint8_t* base = _backend.base_ptr(); - detail::ManagerHeader* hdr = get_header( base ); - index_type blk_idx = block_idx_from_pptr( p ); - void* blk_raw = detail::block_at( base, blk_idx ); - index_type old_data_gran = BlockStateBase::get_weight( blk_raw ); + std::uint8_t* base = _backend.base_ptr(); + detail::ManagerHeader* hdr = get_header( base ); + index_type blk_idx = block_idx_from_pptr( p ); + void* blk_raw = detail::block_at( base, blk_idx ); + index_type old_data_gran = BlockStateBase::get_weight( blk_raw ); index_type new_data_gran = detail::bytes_to_granules_t( new_user_size ); if ( new_data_gran == 0 ) new_data_gran = 1; @@ -8952,6 +8952,80 @@ static void for_each_free_block_inorder( const std::uint8_t* base, const detail: for_each_free_block_inorder( base, hdr, right_off, depth + 1, callback ); } +/// @brief Find the mutable block header for a user-data pointer (or nullptr). +static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + detail::ManagerHeader* hdr = get_header( base ); + return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); +} + +/// @brief Find the const block header for a user-data pointer. +/// Returns nullptr if ptr is out of range or the block header is invalid. +static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept +{ + const std::uint8_t* base = _backend.base_ptr(); + return detail::header_from_ptr_t( + const_cast( base ), const_cast( ptr ), + static_cast( get_header_c( base )->total_size ) ); +} + +// ─── raw ↔ pptr helpers ─────────────────────────────────────── + +/// @brief Convert a raw user-data pointer returned by allocate() into a pptr. +/// Caller must ensure raw != nullptr and _initialized before calling. +/// Returns null pptr if the pointer is not within the managed region. +template static pptr make_pptr_from_raw( void* raw ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + auto* raw_byte = static_cast( raw ); + if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) + return pptr(); + std::size_t byte_off = static_cast( raw_byte - base ); + std::size_t idx = ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( idx > static_cast( std::numeric_limits::max() ) ) + return pptr(); + return pptr( static_cast( idx ) ); +} + +// ─── blk_raw helpers ────────────────────────────────────────── +// base + offset * granule_size - sizeof(Block) → block header before user data. + +/// @brief Return a const pointer to the block header for the given pptr. +/// Returns nullptr if offset is invalid (would place block header before base). +template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept +{ + const std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off < sizeof( Block ) ) + return nullptr; + std::size_t blk_off = byte_off - sizeof( Block ); + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off; +} + +/// @brief Return a mutable pointer to the block header for the given pptr. +/// Returns nullptr if offset is invalid (would place block header before base). +template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off < sizeof( Block ) ) + return nullptr; + std::size_t blk_off = byte_off - sizeof( Block ); + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off; +} + +template static constexpr index_type block_idx_from_pptr( pptr p ) noexcept +{ + constexpr index_type kHdrGranules = static_cast( + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); + return static_cast( p.offset() - kHdrGranules ); +} + // Verify/repair methods — extracted to verify_repair_mixin.inc. // ─── Verify / Repair mixin ────────────────────────────────────── // Included inside PersistMemoryManager private section. @@ -9065,81 +9139,6 @@ static void verify_forest_registry_unlocked( VerifyResult& result ) noexcept } } - /// @brief Find the mutable block header for a user-data pointer (or nullptr). - static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - detail::ManagerHeader* hdr = get_header( base ); - return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); - } - - /// @brief Find the const block header for a user-data pointer. - /// Returns nullptr if ptr is out of range or the block header is invalid. - static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept - { - const std::uint8_t* base = _backend.base_ptr(); - return detail::header_from_ptr_t( - const_cast( base ), const_cast( ptr ), - static_cast( get_header_c( base )->total_size ) ); - } - - // ─── raw ↔ pptr helpers ─────────────────────────────────────── - - /// @brief Convert a raw user-data pointer returned by allocate() into a pptr. - /// Caller must ensure raw != nullptr and _initialized before calling. - /// Returns null pptr if the pointer is not within the managed region. - template static pptr make_pptr_from_raw( void* raw ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - auto* raw_byte = static_cast( raw ); - if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) - return pptr(); - std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = - ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; - if ( idx > static_cast( std::numeric_limits::max() ) ) - return pptr(); - return pptr( static_cast( idx ) ); - } - - // ─── blk_raw helpers ────────────────────────────────────────── - // base + offset * granule_size - sizeof(Block) → block header before user data. - - /// @brief Return a const pointer to the block header for the given pptr. - /// Returns nullptr if offset is invalid (would place block header before base). - template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept - { - const std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) - return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - return nullptr; - return base + blk_off; - } - - /// @brief Return a mutable pointer to the block header for the given pptr. - /// Returns nullptr if offset is invalid (would place block header before base). - template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) - return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - return nullptr; - return base + blk_off; - } - - template static constexpr index_type block_idx_from_pptr( pptr p ) noexcept - { - constexpr index_type kHdrGranules = static_cast( - ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); - return static_cast( p.offset() - kHdrGranules ); - } - // ─── Address-traits-specific layout constants ────────────────── // These compute the correct granule indices based on the actual address_traits // granule size, rather than using the hardcoded DefaultAddressTraits constants. diff --git a/single_include/pmm/pmm_no_comments.h b/single_include/pmm/pmm_no_comments.h index 59047197..bd3ff1e1 100644 --- a/single_include/pmm/pmm_no_comments.h +++ b/single_include/pmm/pmm_no_comments.h @@ -3682,11 +3682,13 @@ class pptr return **this < *other; } - T& operator*() const noexcept { return *ManagerT::template resolve( *this ); } + T& operator*() const noexcept { return *ManagerT::template resolve_checked( *this ); } - T* operator->() const noexcept { return ManagerT::template resolve( *this ); } + T* operator->() const noexcept { return ManagerT::template resolve_checked( *this ); } - T* resolve() const noexcept { return ManagerT::template resolve( *this ); } + T* resolve() const noexcept { return ManagerT::template resolve_checked( *this ); } + + T* resolve_unchecked() const noexcept { return ManagerT::template resolve_unchecked( *this ); } auto& tree_node() const noexcept { return ManagerT::tree_node( *this ); } }; @@ -4388,8 +4390,7 @@ template cla { if ( p.is_null() || !_initialized ) return; - std::uint8_t* base = _backend.base_ptr(); - void* raw = base + static_cast( p.offset() ) * address_traits::granule_size; + void* raw = resolve_unchecked( p ); deallocate( raw ); } @@ -4417,14 +4418,11 @@ template cla _last_error = PmmError::NotInitialized; return pptr(); } - std::uint8_t* base = _backend.base_ptr(); - detail::ManagerHeader* hdr = get_header( base ); - - static constexpr index_type kBlkHdrFloorGran = - static_cast( sizeof( Block ) / address_traits::granule_size ); - index_type blk_idx = static_cast( p.offset() - kBlkHdrFloorGran ); - void* blk_raw = detail::block_at( base, blk_idx ); - index_type old_data_gran = BlockStateBase::get_weight( blk_raw ); + std::uint8_t* base = _backend.base_ptr(); + detail::ManagerHeader* hdr = get_header( base ); + index_type blk_idx = block_idx_from_pptr( p ); + void* blk_raw = detail::block_at( base, blk_idx ); + index_type old_data_gran = BlockStateBase::get_weight( blk_raw ); index_type new_data_gran = detail::bytes_to_granules_t( new_user_size ); if ( new_data_gran == 0 ) new_data_gran = 1; @@ -4454,8 +4452,6 @@ template cla } } - static constexpr index_type kBlkHdrFloorGranFb = - static_cast( sizeof( Block ) / address_traits::granule_size ); index_type new_data_gran_alloc = detail::bytes_to_granules_t( new_user_size ); if ( new_data_gran_alloc == 0 ) new_data_gran_alloc = 1; @@ -4491,12 +4487,12 @@ template cla return pptr(); } pptr new_p = make_pptr_from_raw( new_raw ); - void* new_dst = base + static_cast( new_p.offset() ) * address_traits::granule_size; - void* old_src = base + static_cast( p.offset() ) * address_traits::granule_size; + void* new_dst = resolve_unchecked( new_p ); + void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); std::memmove( new_dst, old_src, copy_sz ); - index_type old_blk_idx = static_cast( p.offset() - kBlkHdrFloorGranFb ); + index_type old_blk_idx = block_idx_from_pptr( p ); void* old_blk_raw = detail::block_at( base, old_blk_idx ); index_type freed_w = BlockStateBase::get_weight( old_blk_raw ); if ( BlockStateBase::get_node_type( old_blk_raw ) != pmm::kNodeReadOnly ) @@ -4537,8 +4533,7 @@ template cla if ( p.is_null() || !_initialized ) return; - std::uint8_t* base = _backend.base_ptr(); - void* raw = base + static_cast( p.offset() ) * address_traits::granule_size; + void* raw = resolve_unchecked( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } @@ -4548,7 +4543,7 @@ template cla return typed_guard( create_typed( static_cast( args )... ) ); } - template static T* resolve( pptr p ) noexcept + template static T* resolve_unchecked( pptr p ) noexcept { if ( p.is_null() || !_initialized ) return nullptr; @@ -4560,12 +4555,56 @@ template cla _last_error = PmmError::InvalidPointer; return nullptr; } - return reinterpret_cast( base + byte_off ); + if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) + { + return reinterpret_cast( base + byte_off ); + } + else + { + constexpr std::size_t hdr_granules = + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( p.offset() < hdr_granules ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + return reinterpret_cast( base + blk_off + sizeof( Block ) ); + } + } + + template static T* resolve_checked( pptr p ) noexcept + { + T* raw = resolve_unchecked( p ); + if ( raw == nullptr ) + return nullptr; + + const void* blk_raw = find_block_from_user_ptr( raw ); + if ( blk_raw == nullptr ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + if ( BlockStateBase::get_weight( blk_raw ) == 0 || + BlockStateBase::get_root_offset( blk_raw ) == 0 ) + { + _last_error = PmmError::InvalidPointer; + return nullptr; + } + _last_error = PmmError::Ok; + return raw; } + template static T* resolve( pptr p ) noexcept { return resolve_checked( p ); } + template static T* resolve_at( pptr p, std::size_t i ) noexcept { - T* base_elem = resolve( p ); + T* base_elem = resolve_checked( p ); return ( base_elem == nullptr ) ? nullptr : base_elem + i; } @@ -4587,13 +4626,7 @@ template cla return pptr( static_cast( idx ) ); } - template static bool is_valid_ptr( pptr p ) noexcept - { - if ( p.is_null() || !_initialized ) - return false; - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - return byte_off + sizeof( T ) <= _backend.total_size(); - } + template static bool is_valid_ptr( pptr p ) noexcept { return resolve_checked( p ) != nullptr; } template static void set_root( pptr p ) noexcept { @@ -5124,7 +5157,7 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) { if ( symbol.is_null() ) return nullptr; - pstringview* sym = resolve( symbol ); + pstringview* sym = resolve_unchecked( symbol ); if ( sym == nullptr ) return nullptr; forest_registry* reg = forest_registry_root_unlocked(); @@ -5243,10 +5276,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept symbol_domain->root_offset, [&]( pptr cur ) -> int { - pstringview* obj = resolve( cur ); + pstringview* obj = resolve_unchecked( cur ); return ( obj != nullptr ) ? std::strcmp( s, obj->c_str() ) : 0; }, - []( pptr p ) -> pstringview* { return resolve( p ); } ); + []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); if ( !found.is_null() ) return found; @@ -5256,8 +5289,7 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept if ( raw == nullptr ) return pptr(); - std::uint8_t* base = _backend.base_ptr(); - pptr new_node( detail::ptr_to_granule_idx( base, raw ) ); + pptr new_node = make_pptr_from_raw( raw ); std::memcpy( raw, &len, sizeof( len ) ); char* str_dst = static_cast( raw ) + offsetof( pstringview, str ); @@ -5272,10 +5304,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool { - pstringview* cur_obj = resolve( cur ); + pstringview* cur_obj = resolve_unchecked( cur ); return ( cur_obj != nullptr ) && ( std::strcmp( new_str, cur_obj->c_str() ) < 0 ); }, - []( pptr p ) -> pstringview* { return resolve( p ); } ); + []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); return new_node; } @@ -5530,6 +5562,65 @@ static void for_each_free_block_inorder( const std::uint8_t* base, const detail: for_each_free_block_inorder( base, hdr, right_off, depth + 1, callback ); } +static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + detail::ManagerHeader* hdr = get_header( base ); + return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); +} + +static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept +{ + const std::uint8_t* base = _backend.base_ptr(); + return detail::header_from_ptr_t( + const_cast( base ), const_cast( ptr ), + static_cast( get_header_c( base )->total_size ) ); +} + +template static pptr make_pptr_from_raw( void* raw ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + auto* raw_byte = static_cast( raw ); + if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) + return pptr(); + std::size_t byte_off = static_cast( raw_byte - base ); + std::size_t idx = ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( idx > static_cast( std::numeric_limits::max() ) ) + return pptr(); + return pptr( static_cast( idx ) ); +} + +template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept +{ + const std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off < sizeof( Block ) ) + return nullptr; + std::size_t blk_off = byte_off - sizeof( Block ); + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off; +} + +template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept +{ + std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off < sizeof( Block ) ) + return nullptr; + std::size_t blk_off = byte_off - sizeof( Block ); + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off; +} + +template static constexpr index_type block_idx_from_pptr( pptr p ) noexcept +{ + constexpr index_type kHdrGranules = static_cast( + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); + return static_cast( p.offset() - kHdrGranules ); +} + static void verify_image_unlocked( VerifyResult& result ) noexcept { result.mode = RecoveryMode::Verify; @@ -5614,58 +5705,6 @@ static void verify_forest_registry_unlocked( VerifyResult& result ) noexcept } } - static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - detail::ManagerHeader* hdr = get_header( base ); - return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); - } - - static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept - { - const std::uint8_t* base = _backend.base_ptr(); - return detail::header_from_ptr_t( - const_cast( base ), const_cast( ptr ), - static_cast( get_header_c( base )->total_size ) ); - } - - template static pptr make_pptr_from_raw( void* raw ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - auto* raw_byte = static_cast( raw ); - if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) - return pptr(); - std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = byte_off / address_traits::granule_size; - if ( idx > static_cast( std::numeric_limits::max() ) ) - return pptr(); - return pptr( static_cast( idx ) ); - } - - template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept - { - const std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) - return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - return nullptr; - return base + blk_off; - } - - template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept - { - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) - return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - return nullptr; - return base + blk_off; - } - static constexpr std::size_t kBlockHdrByteSize = ( ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ) * address_traits::granule_size; From 6a410478939c463f66e6dbcc0be36294e667143e Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 18:19:37 +0000 Subject: [PATCH 4/9] Fix small-traits checked access --- include/pmm/forest_domain_mixin.inc | 52 ++++++++++++++---- include/pmm/persist_memory_manager.h | 29 ++-------- single_include/pmm/pmm.h | 81 ++++++++++++++++------------ single_include/pmm/pmm_no_comments.h | 81 ++++++++++++++++------------ 4 files changed, 144 insertions(+), 99 deletions(-) diff --git a/include/pmm/forest_domain_mixin.inc b/include/pmm/forest_domain_mixin.inc index d3d98c7b..abc33880 100644 --- a/include/pmm/forest_domain_mixin.inc +++ b/include/pmm/forest_domain_mixin.inc @@ -67,8 +67,8 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) { if ( symbol.is_null() ) return nullptr; - pstringview* sym = resolve_unchecked( symbol ); - if ( sym == nullptr ) + const char* sym_str = pstringview_c_str_unlocked( symbol ); + if ( sym_str == nullptr ) return nullptr; forest_registry* reg = forest_registry_root_unlocked(); if ( reg == nullptr ) @@ -76,7 +76,7 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) for ( std::uint16_t i = 0; i < reg->domain_count; ++i ) { if ( reg->domains[i].symbol_offset == symbol.offset() || - std::strncmp( reg->domains[i].name, sym->c_str(), detail::kForestDomainNameCapacity ) == 0 ) + std::strncmp( reg->domains[i].name, sym_str, detail::kForestDomainNameCapacity ) == 0 ) { reg->domains[i].symbol_offset = symbol.offset(); return ®->domains[i]; @@ -194,10 +194,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept symbol_domain->root_offset, [&]( pptr cur ) -> int { - pstringview* obj = resolve_unchecked( cur ); - return ( obj != nullptr ) ? std::strcmp( s, obj->c_str() ) : 0; + const char* cur_str = pstringview_c_str_unlocked( cur ); + return ( cur_str != nullptr ) ? std::strcmp( s, cur_str ) : 0; }, - []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); + []( pptr p ) -> const char* { return pstringview_c_str_unlocked( p ); } ); if ( !found.is_null() ) return found; @@ -223,10 +223,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool { - pstringview* cur_obj = resolve_unchecked( cur ); - return ( cur_obj != nullptr ) && ( std::strcmp( new_str, cur_obj->c_str() ) < 0 ); + const char* cur_str = pstringview_c_str_unlocked( cur ); + return ( cur_str != nullptr ) && ( std::strcmp( new_str, cur_str ) < 0 ); }, - []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); + []( pptr p ) -> const char* { return pstringview_c_str_unlocked( p ); } ); return new_node; } @@ -569,3 +569,37 @@ template static constexpr index_type block_idx_from_pptr( pptr p ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); return static_cast( p.offset() - kHdrGranules ); } + +template static void* raw_user_ptr_from_pptr( pptr p ) noexcept +{ + if ( p.is_null() || !_initialized ) + return nullptr; + + std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) + { + if ( byte_off + sizeof( T ) > _backend.total_size() ) + return nullptr; + return base + byte_off; + } + else + { + constexpr std::size_t hdr_granules = + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( p.offset() < hdr_granules ) + return nullptr; + std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off + sizeof( Block ); + } +} + +static const char* pstringview_c_str_unlocked( pptr p ) noexcept +{ + const void* raw = raw_user_ptr_from_pptr( p ); + if ( raw == nullptr ) + return nullptr; + return static_cast( raw ) + offsetof( pstringview, str ); +} diff --git a/include/pmm/persist_memory_manager.h b/include/pmm/persist_memory_manager.h index 7c56fec7..61404e8e 100644 --- a/include/pmm/persist_memory_manager.h +++ b/include/pmm/persist_memory_manager.h @@ -522,7 +522,7 @@ template cla { if ( p.is_null() || !_initialized ) return; - void* raw = resolve_unchecked( p ); + void* raw = raw_user_ptr_from_pptr( p ); deallocate( raw ); } @@ -702,7 +702,7 @@ template cla if ( p.is_null() || !_initialized ) return; - void* raw = resolve_unchecked( p ); + void* raw = raw_user_ptr_from_pptr( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } @@ -737,27 +737,7 @@ template cla _last_error = PmmError::InvalidPointer; return nullptr; } - if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) - { - return reinterpret_cast( base + byte_off ); - } - else - { - constexpr std::size_t hdr_granules = - ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; - if ( p.offset() < hdr_granules ) - { - _last_error = PmmError::InvalidPointer; - return nullptr; - } - std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - { - _last_error = PmmError::InvalidPointer; - return nullptr; - } - return reinterpret_cast( base + blk_off + sizeof( Block ) ); - } + return reinterpret_cast( base + byte_off ); } /** @@ -777,7 +757,8 @@ template cla if ( raw == nullptr ) return nullptr; - const void* blk_raw = find_block_from_user_ptr( raw ); + const void* user_raw = raw_user_ptr_from_pptr( p ); + const void* blk_raw = find_block_from_user_ptr( user_raw ); if ( blk_raw == nullptr ) { _last_error = PmmError::InvalidPointer; diff --git a/single_include/pmm/pmm.h b/single_include/pmm/pmm.h index 8fbee208..96ecfe33 100644 --- a/single_include/pmm/pmm.h +++ b/single_include/pmm/pmm.h @@ -7561,7 +7561,7 @@ template cla { if ( p.is_null() || !_initialized ) return; - void* raw = resolve_unchecked( p ); + void* raw = raw_user_ptr_from_pptr( p ); deallocate( raw ); } @@ -7741,7 +7741,7 @@ template cla if ( p.is_null() || !_initialized ) return; - void* raw = resolve_unchecked( p ); + void* raw = raw_user_ptr_from_pptr( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } @@ -7776,27 +7776,7 @@ template cla _last_error = PmmError::InvalidPointer; return nullptr; } - if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) - { - return reinterpret_cast( base + byte_off ); - } - else - { - constexpr std::size_t hdr_granules = - ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; - if ( p.offset() < hdr_granules ) - { - _last_error = PmmError::InvalidPointer; - return nullptr; - } - std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - { - _last_error = PmmError::InvalidPointer; - return nullptr; - } - return reinterpret_cast( base + blk_off + sizeof( Block ) ); - } + return reinterpret_cast( base + byte_off ); } /** @@ -7816,7 +7796,8 @@ template cla if ( raw == nullptr ) return nullptr; - const void* blk_raw = find_block_from_user_ptr( raw ); + const void* user_raw = raw_user_ptr_from_pptr( p ); + const void* blk_raw = find_block_from_user_ptr( user_raw ); if ( blk_raw == nullptr ) { _last_error = PmmError::InvalidPointer; @@ -8523,8 +8504,8 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) { if ( symbol.is_null() ) return nullptr; - pstringview* sym = resolve_unchecked( symbol ); - if ( sym == nullptr ) + const char* sym_str = pstringview_c_str_unlocked( symbol ); + if ( sym_str == nullptr ) return nullptr; forest_registry* reg = forest_registry_root_unlocked(); if ( reg == nullptr ) @@ -8532,7 +8513,7 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) for ( std::uint16_t i = 0; i < reg->domain_count; ++i ) { if ( reg->domains[i].symbol_offset == symbol.offset() || - std::strncmp( reg->domains[i].name, sym->c_str(), detail::kForestDomainNameCapacity ) == 0 ) + std::strncmp( reg->domains[i].name, sym_str, detail::kForestDomainNameCapacity ) == 0 ) { reg->domains[i].symbol_offset = symbol.offset(); return ®->domains[i]; @@ -8650,10 +8631,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept symbol_domain->root_offset, [&]( pptr cur ) -> int { - pstringview* obj = resolve_unchecked( cur ); - return ( obj != nullptr ) ? std::strcmp( s, obj->c_str() ) : 0; + const char* cur_str = pstringview_c_str_unlocked( cur ); + return ( cur_str != nullptr ) ? std::strcmp( s, cur_str ) : 0; }, - []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); + []( pptr p ) -> const char* { return pstringview_c_str_unlocked( p ); } ); if ( !found.is_null() ) return found; @@ -8679,10 +8660,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool { - pstringview* cur_obj = resolve_unchecked( cur ); - return ( cur_obj != nullptr ) && ( std::strcmp( new_str, cur_obj->c_str() ) < 0 ); + const char* cur_str = pstringview_c_str_unlocked( cur ); + return ( cur_str != nullptr ) && ( std::strcmp( new_str, cur_str ) < 0 ); }, - []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); + []( pptr p ) -> const char* { return pstringview_c_str_unlocked( p ); } ); return new_node; } @@ -9026,6 +9007,40 @@ template static constexpr index_type block_idx_from_pptr( pptr p return static_cast( p.offset() - kHdrGranules ); } +template static void* raw_user_ptr_from_pptr( pptr p ) noexcept +{ + if ( p.is_null() || !_initialized ) + return nullptr; + + std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) + { + if ( byte_off + sizeof( T ) > _backend.total_size() ) + return nullptr; + return base + byte_off; + } + else + { + constexpr std::size_t hdr_granules = + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( p.offset() < hdr_granules ) + return nullptr; + std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off + sizeof( Block ); + } +} + +static const char* pstringview_c_str_unlocked( pptr p ) noexcept +{ + const void* raw = raw_user_ptr_from_pptr( p ); + if ( raw == nullptr ) + return nullptr; + return static_cast( raw ) + offsetof( pstringview, str ); +} + // Verify/repair methods — extracted to verify_repair_mixin.inc. // ─── Verify / Repair mixin ────────────────────────────────────── // Included inside PersistMemoryManager private section. diff --git a/single_include/pmm/pmm_no_comments.h b/single_include/pmm/pmm_no_comments.h index bd3ff1e1..f2410471 100644 --- a/single_include/pmm/pmm_no_comments.h +++ b/single_include/pmm/pmm_no_comments.h @@ -4390,7 +4390,7 @@ template cla { if ( p.is_null() || !_initialized ) return; - void* raw = resolve_unchecked( p ); + void* raw = raw_user_ptr_from_pptr( p ); deallocate( raw ); } @@ -4533,7 +4533,7 @@ template cla if ( p.is_null() || !_initialized ) return; - void* raw = resolve_unchecked( p ); + void* raw = raw_user_ptr_from_pptr( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } @@ -4555,27 +4555,7 @@ template cla _last_error = PmmError::InvalidPointer; return nullptr; } - if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) - { - return reinterpret_cast( base + byte_off ); - } - else - { - constexpr std::size_t hdr_granules = - ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; - if ( p.offset() < hdr_granules ) - { - _last_error = PmmError::InvalidPointer; - return nullptr; - } - std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; - if ( blk_off + sizeof( Block ) > _backend.total_size() ) - { - _last_error = PmmError::InvalidPointer; - return nullptr; - } - return reinterpret_cast( base + blk_off + sizeof( Block ) ); - } + return reinterpret_cast( base + byte_off ); } template static T* resolve_checked( pptr p ) noexcept @@ -4584,7 +4564,8 @@ template cla if ( raw == nullptr ) return nullptr; - const void* blk_raw = find_block_from_user_ptr( raw ); + const void* user_raw = raw_user_ptr_from_pptr( p ); + const void* blk_raw = find_block_from_user_ptr( user_raw ); if ( blk_raw == nullptr ) { _last_error = PmmError::InvalidPointer; @@ -5157,8 +5138,8 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) { if ( symbol.is_null() ) return nullptr; - pstringview* sym = resolve_unchecked( symbol ); - if ( sym == nullptr ) + const char* sym_str = pstringview_c_str_unlocked( symbol ); + if ( sym_str == nullptr ) return nullptr; forest_registry* reg = forest_registry_root_unlocked(); if ( reg == nullptr ) @@ -5166,7 +5147,7 @@ static forest_domain* find_domain_by_symbol_unlocked( pptr symbol ) for ( std::uint16_t i = 0; i < reg->domain_count; ++i ) { if ( reg->domains[i].symbol_offset == symbol.offset() || - std::strncmp( reg->domains[i].name, sym->c_str(), detail::kForestDomainNameCapacity ) == 0 ) + std::strncmp( reg->domains[i].name, sym_str, detail::kForestDomainNameCapacity ) == 0 ) { reg->domains[i].symbol_offset = symbol.offset(); return ®->domains[i]; @@ -5276,10 +5257,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept symbol_domain->root_offset, [&]( pptr cur ) -> int { - pstringview* obj = resolve_unchecked( cur ); - return ( obj != nullptr ) ? std::strcmp( s, obj->c_str() ) : 0; + const char* cur_str = pstringview_c_str_unlocked( cur ); + return ( cur_str != nullptr ) ? std::strcmp( s, cur_str ) : 0; }, - []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); + []( pptr p ) -> const char* { return pstringview_c_str_unlocked( p ); } ); if ( !found.is_null() ) return found; @@ -5304,10 +5285,10 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool { - pstringview* cur_obj = resolve_unchecked( cur ); - return ( cur_obj != nullptr ) && ( std::strcmp( new_str, cur_obj->c_str() ) < 0 ); + const char* cur_str = pstringview_c_str_unlocked( cur ); + return ( cur_str != nullptr ) && ( std::strcmp( new_str, cur_str ) < 0 ); }, - []( pptr p ) -> pstringview* { return resolve_unchecked( p ); } ); + []( pptr p ) -> const char* { return pstringview_c_str_unlocked( p ); } ); return new_node; } @@ -5621,6 +5602,40 @@ template static constexpr index_type block_idx_from_pptr( pptr p return static_cast( p.offset() - kHdrGranules ); } +template static void* raw_user_ptr_from_pptr( pptr p ) noexcept +{ + if ( p.is_null() || !_initialized ) + return nullptr; + + std::uint8_t* base = _backend.base_ptr(); + std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) + { + if ( byte_off + sizeof( T ) > _backend.total_size() ) + return nullptr; + return base + byte_off; + } + else + { + constexpr std::size_t hdr_granules = + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; + if ( p.offset() < hdr_granules ) + return nullptr; + std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + if ( blk_off + sizeof( Block ) > _backend.total_size() ) + return nullptr; + return base + blk_off + sizeof( Block ); + } +} + +static const char* pstringview_c_str_unlocked( pptr p ) noexcept +{ + const void* raw = raw_user_ptr_from_pptr( p ); + if ( raw == nullptr ) + return nullptr; + return static_cast( raw ) + offsetof( pstringview, str ); +} + static void verify_image_unlocked( VerifyResult& result ) noexcept { result.mode = RecoveryMode::Verify; From ace5d51fdd38fa01097f10911fe4f05742b6ce6a Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 18:29:07 +0000 Subject: [PATCH 5/9] Add changelog fragment for pptr access modes --- changelog.d/20260419_182500_issue312_pptr_access_modes.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog.d/20260419_182500_issue312_pptr_access_modes.md diff --git a/changelog.d/20260419_182500_issue312_pptr_access_modes.md b/changelog.d/20260419_182500_issue312_pptr_access_modes.md new file mode 100644 index 00000000..a30526ba --- /dev/null +++ b/changelog.d/20260419_182500_issue312_pptr_access_modes.md @@ -0,0 +1,6 @@ +--- +bump: minor +--- + +### Added +- Added explicit checked and unchecked `pptr` resolution APIs so stale persistent pointers can be detected without removing internal raw access. From 0ba03a35ae2c01efedab82cea7e53442e110f943 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 18:50:55 +0000 Subject: [PATCH 6/9] Fix small-traits pptr resolution --- include/pmm/persist_memory_manager.h | 18 ++++++++--------- single_include/pmm/pmm.h | 18 ++++++++--------- single_include/pmm/pmm_no_comments.h | 18 ++++++++--------- tests/test_issue312_access_modes.cpp | 30 ++++++++++++++++++++++++++-- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/include/pmm/persist_memory_manager.h b/include/pmm/persist_memory_manager.h index 61404e8e..b59e4366 100644 --- a/include/pmm/persist_memory_manager.h +++ b/include/pmm/persist_memory_manager.h @@ -729,15 +729,14 @@ template cla { if ( p.is_null() || !_initialized ) return nullptr; - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - // Safety: reject out-of-bounds offsets instead of UB. - if ( byte_off + sizeof( T ) > _backend.total_size() ) + void* raw = raw_user_ptr_from_pptr( p ); + if ( raw == nullptr ) { _last_error = PmmError::InvalidPointer; return nullptr; } - return reinterpret_cast( base + byte_off ); + _last_error = PmmError::Ok; + return reinterpret_cast( raw ); } /** @@ -753,12 +752,11 @@ template cla */ template static T* resolve_checked( pptr p ) noexcept { - T* raw = resolve_unchecked( p ); - if ( raw == nullptr ) + T* raw = resolve_unchecked( p ); + const void* user_raw = raw; + if ( user_raw == nullptr ) return nullptr; - - const void* user_raw = raw_user_ptr_from_pptr( p ); - const void* blk_raw = find_block_from_user_ptr( user_raw ); + const void* blk_raw = find_block_from_user_ptr( user_raw ); if ( blk_raw == nullptr ) { _last_error = PmmError::InvalidPointer; diff --git a/single_include/pmm/pmm.h b/single_include/pmm/pmm.h index 96ecfe33..1f09a5b2 100644 --- a/single_include/pmm/pmm.h +++ b/single_include/pmm/pmm.h @@ -7768,15 +7768,14 @@ template cla { if ( p.is_null() || !_initialized ) return nullptr; - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - // Safety: reject out-of-bounds offsets instead of UB. - if ( byte_off + sizeof( T ) > _backend.total_size() ) + void* raw = raw_user_ptr_from_pptr( p ); + if ( raw == nullptr ) { _last_error = PmmError::InvalidPointer; return nullptr; } - return reinterpret_cast( base + byte_off ); + _last_error = PmmError::Ok; + return reinterpret_cast( raw ); } /** @@ -7792,12 +7791,11 @@ template cla */ template static T* resolve_checked( pptr p ) noexcept { - T* raw = resolve_unchecked( p ); - if ( raw == nullptr ) + T* raw = resolve_unchecked( p ); + const void* user_raw = raw; + if ( user_raw == nullptr ) return nullptr; - - const void* user_raw = raw_user_ptr_from_pptr( p ); - const void* blk_raw = find_block_from_user_ptr( user_raw ); + const void* blk_raw = find_block_from_user_ptr( user_raw ); if ( blk_raw == nullptr ) { _last_error = PmmError::InvalidPointer; diff --git a/single_include/pmm/pmm_no_comments.h b/single_include/pmm/pmm_no_comments.h index f2410471..d85d7536 100644 --- a/single_include/pmm/pmm_no_comments.h +++ b/single_include/pmm/pmm_no_comments.h @@ -4547,25 +4547,23 @@ template cla { if ( p.is_null() || !_initialized ) return nullptr; - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - - if ( byte_off + sizeof( T ) > _backend.total_size() ) + void* raw = raw_user_ptr_from_pptr( p ); + if ( raw == nullptr ) { _last_error = PmmError::InvalidPointer; return nullptr; } - return reinterpret_cast( base + byte_off ); + _last_error = PmmError::Ok; + return reinterpret_cast( raw ); } template static T* resolve_checked( pptr p ) noexcept { - T* raw = resolve_unchecked( p ); - if ( raw == nullptr ) + T* raw = resolve_unchecked( p ); + const void* user_raw = raw; + if ( user_raw == nullptr ) return nullptr; - - const void* user_raw = raw_user_ptr_from_pptr( p ); - const void* blk_raw = find_block_from_user_ptr( user_raw ); + const void* blk_raw = find_block_from_user_ptr( user_raw ); if ( blk_raw == nullptr ) { _last_error = PmmError::InvalidPointer; diff --git a/tests/test_issue312_access_modes.cpp b/tests/test_issue312_access_modes.cpp index 13c5d1d9..ec423ec0 100644 --- a/tests/test_issue312_access_modes.cpp +++ b/tests/test_issue312_access_modes.cpp @@ -11,8 +11,9 @@ namespace { -using Mgr = pmm::PersistMemoryManager; -} +using Mgr = pmm::PersistMemoryManager; +using SmallMgr = pmm::PersistMemoryManager, 312>; +} // namespace TEST_CASE( "I312: valid pptr resolves through checked and unchecked paths", "[test_issue312]" ) { @@ -34,6 +35,31 @@ TEST_CASE( "I312: valid pptr resolves through checked and unchecked paths", "[te Mgr::destroy(); } +TEST_CASE( "I312: SmallAddressTraits resolves to canonical non-aligned user pointer", "[test_issue312]" ) +{ + static_assert( sizeof( pmm::Block ) % pmm::SmallAddressTraits::granule_size != 0, + "SmallAddressTraits must exercise the non-aligned block-header path" ); + + REQUIRE( SmallMgr::create() ); + + SmallMgr::pptr p = SmallMgr::allocate_typed(); + REQUIRE( !p.is_null() ); + + auto* checked = SmallMgr::resolve_checked( p ); + auto* unchecked = SmallMgr::resolve_unchecked( p ); + REQUIRE( checked != nullptr ); + REQUIRE( unchecked != nullptr ); + REQUIRE( checked == unchecked ); + REQUIRE( p.resolve() == checked ); + REQUIRE( p.resolve_unchecked() == unchecked ); + + *checked = 0x51312u; + REQUIRE( *unchecked == 0x51312u ); + + SmallMgr::deallocate_typed( p ); + SmallMgr::destroy(); +} + TEST_CASE( "I312: invalid out-of-range pptr is rejected by both access modes", "[test_issue312]" ) { REQUIRE( Mgr::create( 64 * 1024 ) ); From 6acc8cc8abbcb2820cb6752bf0c5c9f9e6a2540f Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 19:18:28 +0000 Subject: [PATCH 7/9] Fix sanitizer-safe small traits pptr alignment --- include/pmm/forest_domain_mixin.inc | 65 +++++++++++++++++---- include/pmm/persist_memory_manager.h | 20 +++++-- single_include/pmm/pmm.h | 85 ++++++++++++++++++++++------ tests/test_issue312_access_modes.cpp | 3 + 4 files changed, 141 insertions(+), 32 deletions(-) diff --git a/include/pmm/forest_domain_mixin.inc b/include/pmm/forest_domain_mixin.inc index abc33880..bd346126 100644 --- a/include/pmm/forest_domain_mixin.inc +++ b/include/pmm/forest_domain_mixin.inc @@ -501,6 +501,23 @@ static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcep { std::uint8_t* base = _backend.base_ptr(); detail::ManagerHeader* hdr = get_header( base ); + if constexpr ( sizeof( Block ) % address_traits::granule_size != 0 ) + { + constexpr std::size_t rounded_header_size = + static_cast( kBlockHdrGranules ) * address_traits::granule_size; + if ( ptr != nullptr && base != nullptr ) + { + auto* raw = static_cast( ptr ); + if ( raw >= base + rounded_header_size && raw < base + static_cast( hdr->total_size ) ) + { + std::uint8_t* cand = raw - rounded_header_size; + if ( ( static_cast( cand - base ) % address_traits::granule_size ) == 0 && + cand + sizeof( Block ) <= base + static_cast( hdr->total_size ) && + BlockStateBase::get_weight( cand ) != 0 ) + return reinterpret_cast*>( cand ); + } + } + } return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); } @@ -509,9 +526,26 @@ static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcep static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept { const std::uint8_t* base = _backend.base_ptr(); - return detail::header_from_ptr_t( - const_cast( base ), const_cast( ptr ), - static_cast( get_header_c( base )->total_size ) ); + const auto* hdr = get_header_c( base ); + if constexpr ( sizeof( Block ) % address_traits::granule_size != 0 ) + { + constexpr std::size_t rounded_header_size = + static_cast( kBlockHdrGranules ) * address_traits::granule_size; + if ( ptr != nullptr && base != nullptr ) + { + const auto* raw = static_cast( ptr ); + if ( raw >= base + rounded_header_size && raw < base + static_cast( hdr->total_size ) ) + { + const std::uint8_t* cand = raw - rounded_header_size; + if ( ( static_cast( cand - base ) % address_traits::granule_size ) == 0 && + cand + sizeof( Block ) <= base + static_cast( hdr->total_size ) && + BlockStateBase::get_weight( cand ) != 0 ) + return reinterpret_cast*>( cand ); + } + } + } + return detail::header_from_ptr_t( const_cast( base ), const_cast( ptr ), + static_cast( hdr->total_size ) ); } // ─── raw ↔ pptr helpers ─────────────────────────────────────── @@ -526,7 +560,7 @@ template static pptr make_pptr_from_raw( void* raw ) noexcept if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) return pptr(); std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; + std::size_t idx = byte_off / address_traits::granule_size; if ( idx > static_cast( std::numeric_limits::max() ) ) return pptr(); return pptr( static_cast( idx ) ); @@ -577,19 +611,28 @@ template static void* raw_user_ptr_from_pptr( pptr p ) noexcept std::uint8_t* base = _backend.base_ptr(); std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off + sizeof( T ) > _backend.total_size() ) + return nullptr; + return base + byte_off; +} + +template static void* raw_block_user_ptr_from_pptr( pptr p ) noexcept +{ + if ( p.is_null() || !_initialized ) + return nullptr; + + std::uint8_t* base = _backend.base_ptr(); if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) { - if ( byte_off + sizeof( T ) > _backend.total_size() ) - return nullptr; - return base + byte_off; + return raw_user_ptr_from_pptr( p ); } else { - constexpr std::size_t hdr_granules = - ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; - if ( p.offset() < hdr_granules ) + constexpr index_type kHdrGranules = static_cast( + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); + if ( p.offset() < kHdrGranules ) return nullptr; - std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + std::size_t blk_off = static_cast( p.offset() - kHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off + sizeof( Block ); diff --git a/include/pmm/persist_memory_manager.h b/include/pmm/persist_memory_manager.h index b59e4366..772116c9 100644 --- a/include/pmm/persist_memory_manager.h +++ b/include/pmm/persist_memory_manager.h @@ -486,7 +486,9 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - return make_pptr_from_raw( raw ); + pmm::Block* blk = find_block_from_user_ptr( raw ); + return ( blk == nullptr ) ? pptr() : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + + kBlockHdrGranules ); } /** @@ -506,7 +508,9 @@ template cla void* raw = allocate( sizeof( T ) * count ); if ( raw == nullptr ) return pptr(); - return make_pptr_from_raw( raw ); + pmm::Block* blk = find_block_from_user_ptr( raw ); + return ( blk == nullptr ) ? pptr() : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + + kBlockHdrGranules ); } /** @@ -522,7 +526,7 @@ template cla { if ( p.is_null() || !_initialized ) return; - void* raw = raw_user_ptr_from_pptr( p ); + void* raw = raw_block_user_ptr_from_pptr( p ); deallocate( raw ); } @@ -623,7 +627,13 @@ template cla _last_error = PmmError::OutOfMemory; return pptr(); } - pptr new_p = make_pptr_from_raw( new_raw ); + pmm::Block* new_blk = find_block_from_user_ptr( new_raw ); + if ( new_blk == nullptr ) + { + _last_error = PmmError::InvalidPointer; + return pptr(); + } + pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); void* new_dst = resolve_unchecked( new_p ); void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); @@ -702,7 +712,7 @@ template cla if ( p.is_null() || !_initialized ) return; - void* raw = raw_user_ptr_from_pptr( p ); + void* raw = raw_block_user_ptr_from_pptr( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } diff --git a/single_include/pmm/pmm.h b/single_include/pmm/pmm.h index 1f09a5b2..3bbe7633 100644 --- a/single_include/pmm/pmm.h +++ b/single_include/pmm/pmm.h @@ -7525,7 +7525,9 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - return make_pptr_from_raw( raw ); + pmm::Block* blk = find_block_from_user_ptr( raw ); + return ( blk == nullptr ) ? pptr() : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + + kBlockHdrGranules ); } /** @@ -7545,7 +7547,9 @@ template cla void* raw = allocate( sizeof( T ) * count ); if ( raw == nullptr ) return pptr(); - return make_pptr_from_raw( raw ); + pmm::Block* blk = find_block_from_user_ptr( raw ); + return ( blk == nullptr ) ? pptr() : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + + kBlockHdrGranules ); } /** @@ -7561,7 +7565,7 @@ template cla { if ( p.is_null() || !_initialized ) return; - void* raw = raw_user_ptr_from_pptr( p ); + void* raw = raw_block_user_ptr_from_pptr( p ); deallocate( raw ); } @@ -7662,7 +7666,13 @@ template cla _last_error = PmmError::OutOfMemory; return pptr(); } - pptr new_p = make_pptr_from_raw( new_raw ); + pmm::Block* new_blk = find_block_from_user_ptr( new_raw ); + if ( new_blk == nullptr ) + { + _last_error = PmmError::InvalidPointer; + return pptr(); + } + pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); void* new_dst = resolve_unchecked( new_p ); void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); @@ -7741,7 +7751,7 @@ template cla if ( p.is_null() || !_initialized ) return; - void* raw = raw_user_ptr_from_pptr( p ); + void* raw = raw_block_user_ptr_from_pptr( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } @@ -8936,6 +8946,23 @@ static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcep { std::uint8_t* base = _backend.base_ptr(); detail::ManagerHeader* hdr = get_header( base ); + if constexpr ( sizeof( Block ) % address_traits::granule_size != 0 ) + { + constexpr std::size_t rounded_header_size = + static_cast( kBlockHdrGranules ) * address_traits::granule_size; + if ( ptr != nullptr && base != nullptr ) + { + auto* raw = static_cast( ptr ); + if ( raw >= base + rounded_header_size && raw < base + static_cast( hdr->total_size ) ) + { + std::uint8_t* cand = raw - rounded_header_size; + if ( ( static_cast( cand - base ) % address_traits::granule_size ) == 0 && + cand + sizeof( Block ) <= base + static_cast( hdr->total_size ) && + BlockStateBase::get_weight( cand ) != 0 ) + return reinterpret_cast*>( cand ); + } + } + } return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); } @@ -8944,9 +8971,26 @@ static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcep static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept { const std::uint8_t* base = _backend.base_ptr(); - return detail::header_from_ptr_t( - const_cast( base ), const_cast( ptr ), - static_cast( get_header_c( base )->total_size ) ); + const auto* hdr = get_header_c( base ); + if constexpr ( sizeof( Block ) % address_traits::granule_size != 0 ) + { + constexpr std::size_t rounded_header_size = + static_cast( kBlockHdrGranules ) * address_traits::granule_size; + if ( ptr != nullptr && base != nullptr ) + { + const auto* raw = static_cast( ptr ); + if ( raw >= base + rounded_header_size && raw < base + static_cast( hdr->total_size ) ) + { + const std::uint8_t* cand = raw - rounded_header_size; + if ( ( static_cast( cand - base ) % address_traits::granule_size ) == 0 && + cand + sizeof( Block ) <= base + static_cast( hdr->total_size ) && + BlockStateBase::get_weight( cand ) != 0 ) + return reinterpret_cast*>( cand ); + } + } + } + return detail::header_from_ptr_t( const_cast( base ), const_cast( ptr ), + static_cast( hdr->total_size ) ); } // ─── raw ↔ pptr helpers ─────────────────────────────────────── @@ -8961,7 +9005,7 @@ template static pptr make_pptr_from_raw( void* raw ) noexcept if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) return pptr(); std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; + std::size_t idx = byte_off / address_traits::granule_size; if ( idx > static_cast( std::numeric_limits::max() ) ) return pptr(); return pptr( static_cast( idx ) ); @@ -9012,19 +9056,28 @@ template static void* raw_user_ptr_from_pptr( pptr p ) noexcept std::uint8_t* base = _backend.base_ptr(); std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off + sizeof( T ) > _backend.total_size() ) + return nullptr; + return base + byte_off; +} + +template static void* raw_block_user_ptr_from_pptr( pptr p ) noexcept +{ + if ( p.is_null() || !_initialized ) + return nullptr; + + std::uint8_t* base = _backend.base_ptr(); if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) { - if ( byte_off + sizeof( T ) > _backend.total_size() ) - return nullptr; - return base + byte_off; + return raw_user_ptr_from_pptr( p ); } else { - constexpr std::size_t hdr_granules = - ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; - if ( p.offset() < hdr_granules ) + constexpr index_type kHdrGranules = static_cast( + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); + if ( p.offset() < kHdrGranules ) return nullptr; - std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + std::size_t blk_off = static_cast( p.offset() - kHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off + sizeof( Block ); diff --git a/tests/test_issue312_access_modes.cpp b/tests/test_issue312_access_modes.cpp index ec423ec0..ed7a04f0 100644 --- a/tests/test_issue312_access_modes.cpp +++ b/tests/test_issue312_access_modes.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace { @@ -50,6 +51,8 @@ TEST_CASE( "I312: SmallAddressTraits resolves to canonical non-aligned user poin REQUIRE( checked != nullptr ); REQUIRE( unchecked != nullptr ); REQUIRE( checked == unchecked ); + REQUIRE( reinterpret_cast( checked ) % alignof( std::uint32_t ) == 0 ); + REQUIRE( reinterpret_cast( checked ) % pmm::SmallAddressTraits::granule_size == 0 ); REQUIRE( p.resolve() == checked ); REQUIRE( p.resolve_unchecked() == unchecked ); From d9e80a23a5075a22005bda7622bc094c04422a88 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 19:20:28 +0000 Subject: [PATCH 8/9] Regenerate formatted single headers --- include/pmm/persist_memory_manager.h | 12 ++-- single_include/pmm/pmm.h | 12 ++-- single_include/pmm/pmm_no_comments.h | 87 +++++++++++++++++++++++----- 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/include/pmm/persist_memory_manager.h b/include/pmm/persist_memory_manager.h index 772116c9..a4d7fdf2 100644 --- a/include/pmm/persist_memory_manager.h +++ b/include/pmm/persist_memory_manager.h @@ -487,8 +487,9 @@ template cla if ( raw == nullptr ) return pptr(); pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) ? pptr() : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + - kBlockHdrGranules ); + return ( blk == nullptr ) + ? pptr() + : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); } /** @@ -509,8 +510,9 @@ template cla if ( raw == nullptr ) return pptr(); pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) ? pptr() : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + - kBlockHdrGranules ); + return ( blk == nullptr ) + ? pptr() + : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); } /** @@ -633,7 +635,7 @@ template cla _last_error = PmmError::InvalidPointer; return pptr(); } - pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); + pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); void* new_dst = resolve_unchecked( new_p ); void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); diff --git a/single_include/pmm/pmm.h b/single_include/pmm/pmm.h index 3bbe7633..4aa12659 100644 --- a/single_include/pmm/pmm.h +++ b/single_include/pmm/pmm.h @@ -7526,8 +7526,9 @@ template cla if ( raw == nullptr ) return pptr(); pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) ? pptr() : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + - kBlockHdrGranules ); + return ( blk == nullptr ) + ? pptr() + : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); } /** @@ -7548,8 +7549,9 @@ template cla if ( raw == nullptr ) return pptr(); pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) ? pptr() : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + - kBlockHdrGranules ); + return ( blk == nullptr ) + ? pptr() + : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); } /** @@ -7672,7 +7674,7 @@ template cla _last_error = PmmError::InvalidPointer; return pptr(); } - pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); + pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); void* new_dst = resolve_unchecked( new_p ); void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); diff --git a/single_include/pmm/pmm_no_comments.h b/single_include/pmm/pmm_no_comments.h index d85d7536..b7ecffe3 100644 --- a/single_include/pmm/pmm_no_comments.h +++ b/single_include/pmm/pmm_no_comments.h @@ -4370,7 +4370,10 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - return make_pptr_from_raw( raw ); + pmm::Block* blk = find_block_from_user_ptr( raw ); + return ( blk == nullptr ) + ? pptr() + : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); } template static pptr allocate_typed( std::size_t count ) noexcept @@ -4383,14 +4386,17 @@ template cla void* raw = allocate( sizeof( T ) * count ); if ( raw == nullptr ) return pptr(); - return make_pptr_from_raw( raw ); + pmm::Block* blk = find_block_from_user_ptr( raw ); + return ( blk == nullptr ) + ? pptr() + : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); } template static void deallocate_typed( pptr p ) noexcept { if ( p.is_null() || !_initialized ) return; - void* raw = raw_user_ptr_from_pptr( p ); + void* raw = raw_block_user_ptr_from_pptr( p ); deallocate( raw ); } @@ -4486,7 +4492,13 @@ template cla _last_error = PmmError::OutOfMemory; return pptr(); } - pptr new_p = make_pptr_from_raw( new_raw ); + pmm::Block* new_blk = find_block_from_user_ptr( new_raw ); + if ( new_blk == nullptr ) + { + _last_error = PmmError::InvalidPointer; + return pptr(); + } + pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); void* new_dst = resolve_unchecked( new_p ); void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); @@ -4533,7 +4545,7 @@ template cla if ( p.is_null() || !_initialized ) return; - void* raw = raw_user_ptr_from_pptr( p ); + void* raw = raw_block_user_ptr_from_pptr( p ); reinterpret_cast( raw )->~T(); deallocate( raw ); } @@ -5545,15 +5557,49 @@ static pmm::Block* find_block_from_user_ptr( void* ptr ) noexcep { std::uint8_t* base = _backend.base_ptr(); detail::ManagerHeader* hdr = get_header( base ); + if constexpr ( sizeof( Block ) % address_traits::granule_size != 0 ) + { + constexpr std::size_t rounded_header_size = + static_cast( kBlockHdrGranules ) * address_traits::granule_size; + if ( ptr != nullptr && base != nullptr ) + { + auto* raw = static_cast( ptr ); + if ( raw >= base + rounded_header_size && raw < base + static_cast( hdr->total_size ) ) + { + std::uint8_t* cand = raw - rounded_header_size; + if ( ( static_cast( cand - base ) % address_traits::granule_size ) == 0 && + cand + sizeof( Block ) <= base + static_cast( hdr->total_size ) && + BlockStateBase::get_weight( cand ) != 0 ) + return reinterpret_cast*>( cand ); + } + } + } return detail::header_from_ptr_t( base, ptr, static_cast( hdr->total_size ) ); } static const pmm::Block* find_block_from_user_ptr( const void* ptr ) noexcept { const std::uint8_t* base = _backend.base_ptr(); - return detail::header_from_ptr_t( - const_cast( base ), const_cast( ptr ), - static_cast( get_header_c( base )->total_size ) ); + const auto* hdr = get_header_c( base ); + if constexpr ( sizeof( Block ) % address_traits::granule_size != 0 ) + { + constexpr std::size_t rounded_header_size = + static_cast( kBlockHdrGranules ) * address_traits::granule_size; + if ( ptr != nullptr && base != nullptr ) + { + const auto* raw = static_cast( ptr ); + if ( raw >= base + rounded_header_size && raw < base + static_cast( hdr->total_size ) ) + { + const std::uint8_t* cand = raw - rounded_header_size; + if ( ( static_cast( cand - base ) % address_traits::granule_size ) == 0 && + cand + sizeof( Block ) <= base + static_cast( hdr->total_size ) && + BlockStateBase::get_weight( cand ) != 0 ) + return reinterpret_cast*>( cand ); + } + } + } + return detail::header_from_ptr_t( const_cast( base ), const_cast( ptr ), + static_cast( hdr->total_size ) ); } template static pptr make_pptr_from_raw( void* raw ) noexcept @@ -5563,7 +5609,7 @@ template static pptr make_pptr_from_raw( void* raw ) noexcept if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) return pptr(); std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = ( byte_off + address_traits::granule_size - 1 ) / address_traits::granule_size; + std::size_t idx = byte_off / address_traits::granule_size; if ( idx > static_cast( std::numeric_limits::max() ) ) return pptr(); return pptr( static_cast( idx ) ); @@ -5607,19 +5653,28 @@ template static void* raw_user_ptr_from_pptr( pptr p ) noexcept std::uint8_t* base = _backend.base_ptr(); std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; + if ( byte_off + sizeof( T ) > _backend.total_size() ) + return nullptr; + return base + byte_off; +} + +template static void* raw_block_user_ptr_from_pptr( pptr p ) noexcept +{ + if ( p.is_null() || !_initialized ) + return nullptr; + + std::uint8_t* base = _backend.base_ptr(); if constexpr ( sizeof( Block ) % address_traits::granule_size == 0 ) { - if ( byte_off + sizeof( T ) > _backend.total_size() ) - return nullptr; - return base + byte_off; + return raw_user_ptr_from_pptr( p ); } else { - constexpr std::size_t hdr_granules = - ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size; - if ( p.offset() < hdr_granules ) + constexpr index_type kHdrGranules = static_cast( + ( sizeof( Block ) + address_traits::granule_size - 1 ) / address_traits::granule_size ); + if ( p.offset() < kHdrGranules ) return nullptr; - std::size_t blk_off = static_cast( p.offset() - hdr_granules ) * address_traits::granule_size; + std::size_t blk_off = static_cast( p.offset() - kHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off + sizeof( Block ); From 3a98d93d4ba75893d7217795aef88352f74f2c08 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 19 Apr 2026 19:52:21 +0000 Subject: [PATCH 9/9] Make pptr factories use canonical public offsets --- include/pmm/forest_domain_mixin.inc | 48 +++++----- include/pmm/persist_memory_manager.h | 42 +++++---- include/pmm/pstringview.h | 35 ++++++-- single_include/pmm/pmm.h | 125 ++++++++++++++++++--------- single_include/pmm/pmm_no_comments.h | 119 +++++++++++++++++-------- tests/test_issue312_access_modes.cpp | 43 +++++++++ 6 files changed, 292 insertions(+), 120 deletions(-) diff --git a/include/pmm/forest_domain_mixin.inc b/include/pmm/forest_domain_mixin.inc index bd346126..cf1d1e23 100644 --- a/include/pmm/forest_domain_mixin.inc +++ b/include/pmm/forest_domain_mixin.inc @@ -207,18 +207,24 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept if ( raw == nullptr ) return pptr(); - pptr new_node = make_pptr_from_raw( raw ); + pptr new_node = make_pptr_from_raw( raw ); + void* public_raw = raw_user_ptr_from_pptr( new_node ); + if ( public_raw == nullptr ) + { + deallocate_unlocked( raw ); + return pptr(); + } // Use memcpy to avoid UB on potentially misaligned raw pointer (ASan/UBSan fix). - std::memcpy( raw, &len, sizeof( len ) ); - char* str_dst = static_cast( raw ) + offsetof( pstringview, str ); + std::memcpy( public_raw, &len, sizeof( len ) ); + char* str_dst = static_cast( public_raw ) + offsetof( pstringview, str ); std::memcpy( str_dst, s, static_cast( len ) + 1 ); detail::avl_init_node( new_node ); - if ( !lock_block_permanent_unlocked( raw ) ) + if ( !lock_block_permanent_unlocked( public_raw ) ) return pptr(); // Re-derive c_str() pointer for comparisons using offset-based access. - const char* new_str = static_cast( raw ) + offsetof( pstringview, str ); + const char* new_str = static_cast( public_raw ) + offsetof( pstringview, str ); detail::avl_insert( new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool @@ -550,34 +556,37 @@ static const pmm::Block* find_block_from_user_ptr( const void* p // ─── raw ↔ pptr helpers ─────────────────────────────────────── -/// @brief Convert a raw user-data pointer returned by allocate() into a pptr. +/// @brief Convert a raw user-data pointer returned by allocate() into a canonical public pptr. /// Caller must ensure raw != nullptr and _initialized before calling. /// Returns null pptr if the pointer is not within the managed region. template static pptr make_pptr_from_raw( void* raw ) noexcept { + if ( raw == nullptr || !_initialized ) + return pptr(); std::uint8_t* base = _backend.base_ptr(); auto* raw_byte = static_cast( raw ); - if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) + if ( base == nullptr || raw_byte < base || raw_byte >= base + _backend.total_size() ) return pptr(); - std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = byte_off / address_traits::granule_size; - if ( idx > static_cast( std::numeric_limits::max() ) ) + pmm::Block* blk = find_block_from_user_ptr( raw ); + if ( blk == nullptr ) return pptr(); - return pptr( static_cast( idx ) ); + index_type blk_idx = detail::block_idx_t( base, blk ); + if ( blk_idx > std::numeric_limits::max() - kBlockHdrGranules ) + return pptr(); + return pptr( static_cast( blk_idx + kBlockHdrGranules ) ); } // ─── blk_raw helpers ────────────────────────────────────────── -// base + offset * granule_size - sizeof(Block) → block header before user data. +// base + (offset - kBlockHdrGranules) * granule_size → block header before public user data. /// @brief Return a const pointer to the block header for the given pptr. /// Returns nullptr if offset is invalid (would place block header before base). template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept { - const std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) + const std::uint8_t* base = _backend.base_ptr(); + if ( p.offset() < kBlockHdrGranules ) return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); + std::size_t blk_off = static_cast( p.offset() - kBlockHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off; @@ -587,11 +596,10 @@ template static const void* block_raw_ptr_from_pptr( pptr p ) no /// Returns nullptr if offset is invalid (would place block header before base). template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept { - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) + std::uint8_t* base = _backend.base_ptr(); + if ( p.offset() < kBlockHdrGranules ) return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); + std::size_t blk_off = static_cast( p.offset() - kBlockHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off; diff --git a/include/pmm/persist_memory_manager.h b/include/pmm/persist_memory_manager.h index a4d7fdf2..68e7c1c1 100644 --- a/include/pmm/persist_memory_manager.h +++ b/include/pmm/persist_memory_manager.h @@ -486,10 +486,7 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) - ? pptr() - : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); + return make_pptr_from_raw( raw ); } /** @@ -509,10 +506,7 @@ template cla void* raw = allocate( sizeof( T ) * count ); if ( raw == nullptr ) return pptr(); - pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) - ? pptr() - : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); + return make_pptr_from_raw( raw ); } /** @@ -629,13 +623,12 @@ template cla _last_error = PmmError::OutOfMemory; return pptr(); } - pmm::Block* new_blk = find_block_from_user_ptr( new_raw ); - if ( new_blk == nullptr ) + pptr new_p = make_pptr_from_raw( new_raw ); + if ( new_p.is_null() ) { _last_error = PmmError::InvalidPointer; return pptr(); } - pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); void* new_dst = resolve_unchecked( new_p ); void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); @@ -691,8 +684,15 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - ::new ( raw ) T( static_cast( args )... ); - return make_pptr_from_raw( raw ); + pptr p = make_pptr_from_raw( raw ); + T* obj = resolve_unchecked( p ); + if ( obj == nullptr ) + { + deallocate( raw ); + return pptr(); + } + ::new ( obj ) T( static_cast( args )... ); + return p; } /** @@ -714,8 +714,11 @@ template cla if ( p.is_null() || !_initialized ) return; + T* obj = resolve_unchecked( p ); void* raw = raw_block_user_ptr_from_pptr( p ); - reinterpret_cast( raw )->~T(); + if ( obj == nullptr || raw == nullptr ) + return; + obj->~T(); deallocate( raw ); } @@ -1400,8 +1403,15 @@ template cla void* raw = allocate_unlocked( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - ::new ( raw ) T( static_cast( args )... ); - return make_pptr_from_raw( raw ); + pptr p = make_pptr_from_raw( raw ); + T* obj = resolve_unchecked( p ); + if ( obj == nullptr ) + { + deallocate_unlocked( raw ); + return pptr(); + } + ::new ( obj ) T( static_cast( args )... ); + return p; } // Forest/domain registry private methods — extracted to forest_domain_mixin.inc diff --git a/include/pmm/pstringview.h b/include/pmm/pstringview.h index fb62c686..e5602b37 100644 --- a/include/pmm/pstringview.h +++ b/include/pmm/pstringview.h @@ -239,12 +239,37 @@ template struct pstringview if ( raw == nullptr ) return psview_pptr(); - // Создаём pptr вручную из raw указателя. - std::uint8_t* base = ManagerT::backend().base_ptr(); - psview_pptr new_node( detail::ptr_to_granule_idx( base, raw ) ); + // Создаём canonical public pptr из физического raw указателя allocate(). + using address_traits = typename ManagerT::address_traits; + std::uint8_t* base = ManagerT::backend().base_ptr(); + auto* raw_ptr = static_cast( raw ); + if ( base == nullptr || raw_ptr < base + sizeof( pmm::Block ) ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + std::size_t block_byte_off = static_cast( raw_ptr - base ) - sizeof( pmm::Block ); + if ( block_byte_off % address_traits::granule_size != 0 ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + std::size_t public_idx = + block_byte_off / address_traits::granule_size + detail::kBlockHeaderGranules_t; + if ( public_idx > static_cast( address_traits::no_block ) ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + psview_pptr new_node( static_cast( public_idx ) ); - pstringview* obj = static_cast( raw ); - obj->length = len; + pstringview* obj = ManagerT::template resolve_unchecked( new_node ); + if ( obj == nullptr ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + obj->length = len; // Копируем строку включая null-terminator. std::memcpy( obj->str, s, static_cast( len ) + 1 ); diff --git a/single_include/pmm/pmm.h b/single_include/pmm/pmm.h index 4aa12659..a8350399 100644 --- a/single_include/pmm/pmm.h +++ b/single_include/pmm/pmm.h @@ -6881,12 +6881,37 @@ template struct pstringview if ( raw == nullptr ) return psview_pptr(); - // Создаём pptr вручную из raw указателя. - std::uint8_t* base = ManagerT::backend().base_ptr(); - psview_pptr new_node( detail::ptr_to_granule_idx( base, raw ) ); + // Создаём canonical public pptr из физического raw указателя allocate(). + using address_traits = typename ManagerT::address_traits; + std::uint8_t* base = ManagerT::backend().base_ptr(); + auto* raw_ptr = static_cast( raw ); + if ( base == nullptr || raw_ptr < base + sizeof( pmm::Block ) ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + std::size_t block_byte_off = static_cast( raw_ptr - base ) - sizeof( pmm::Block ); + if ( block_byte_off % address_traits::granule_size != 0 ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + std::size_t public_idx = + block_byte_off / address_traits::granule_size + detail::kBlockHeaderGranules_t; + if ( public_idx > static_cast( address_traits::no_block ) ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + psview_pptr new_node( static_cast( public_idx ) ); - pstringview* obj = static_cast( raw ); - obj->length = len; + pstringview* obj = ManagerT::template resolve_unchecked( new_node ); + if ( obj == nullptr ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + obj->length = len; // Копируем строку включая null-terminator. std::memcpy( obj->str, s, static_cast( len ) + 1 ); @@ -7525,10 +7550,7 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) - ? pptr() - : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); + return make_pptr_from_raw( raw ); } /** @@ -7548,10 +7570,7 @@ template cla void* raw = allocate( sizeof( T ) * count ); if ( raw == nullptr ) return pptr(); - pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) - ? pptr() - : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); + return make_pptr_from_raw( raw ); } /** @@ -7668,13 +7687,12 @@ template cla _last_error = PmmError::OutOfMemory; return pptr(); } - pmm::Block* new_blk = find_block_from_user_ptr( new_raw ); - if ( new_blk == nullptr ) + pptr new_p = make_pptr_from_raw( new_raw ); + if ( new_p.is_null() ) { _last_error = PmmError::InvalidPointer; return pptr(); } - pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); void* new_dst = resolve_unchecked( new_p ); void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); @@ -7730,8 +7748,15 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - ::new ( raw ) T( static_cast( args )... ); - return make_pptr_from_raw( raw ); + pptr p = make_pptr_from_raw( raw ); + T* obj = resolve_unchecked( p ); + if ( obj == nullptr ) + { + deallocate( raw ); + return pptr(); + } + ::new ( obj ) T( static_cast( args )... ); + return p; } /** @@ -7753,8 +7778,11 @@ template cla if ( p.is_null() || !_initialized ) return; + T* obj = resolve_unchecked( p ); void* raw = raw_block_user_ptr_from_pptr( p ); - reinterpret_cast( raw )->~T(); + if ( obj == nullptr || raw == nullptr ) + return; + obj->~T(); deallocate( raw ); } @@ -8439,8 +8467,15 @@ template cla void* raw = allocate_unlocked( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - ::new ( raw ) T( static_cast( args )... ); - return make_pptr_from_raw( raw ); + pptr p = make_pptr_from_raw( raw ); + T* obj = resolve_unchecked( p ); + if ( obj == nullptr ) + { + deallocate_unlocked( raw ); + return pptr(); + } + ::new ( obj ) T( static_cast( args )... ); + return p; } // Forest/domain registry private methods — extracted to forest_domain_mixin.inc @@ -8654,18 +8689,24 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept if ( raw == nullptr ) return pptr(); - pptr new_node = make_pptr_from_raw( raw ); + pptr new_node = make_pptr_from_raw( raw ); + void* public_raw = raw_user_ptr_from_pptr( new_node ); + if ( public_raw == nullptr ) + { + deallocate_unlocked( raw ); + return pptr(); + } // Use memcpy to avoid UB on potentially misaligned raw pointer (ASan/UBSan fix). - std::memcpy( raw, &len, sizeof( len ) ); - char* str_dst = static_cast( raw ) + offsetof( pstringview, str ); + std::memcpy( public_raw, &len, sizeof( len ) ); + char* str_dst = static_cast( public_raw ) + offsetof( pstringview, str ); std::memcpy( str_dst, s, static_cast( len ) + 1 ); detail::avl_init_node( new_node ); - if ( !lock_block_permanent_unlocked( raw ) ) + if ( !lock_block_permanent_unlocked( public_raw ) ) return pptr(); // Re-derive c_str() pointer for comparisons using offset-based access. - const char* new_str = static_cast( raw ) + offsetof( pstringview, str ); + const char* new_str = static_cast( public_raw ) + offsetof( pstringview, str ); detail::avl_insert( new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool @@ -8997,34 +9038,37 @@ static const pmm::Block* find_block_from_user_ptr( const void* p // ─── raw ↔ pptr helpers ─────────────────────────────────────── -/// @brief Convert a raw user-data pointer returned by allocate() into a pptr. +/// @brief Convert a raw user-data pointer returned by allocate() into a canonical public pptr. /// Caller must ensure raw != nullptr and _initialized before calling. /// Returns null pptr if the pointer is not within the managed region. template static pptr make_pptr_from_raw( void* raw ) noexcept { + if ( raw == nullptr || !_initialized ) + return pptr(); std::uint8_t* base = _backend.base_ptr(); auto* raw_byte = static_cast( raw ); - if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) + if ( base == nullptr || raw_byte < base || raw_byte >= base + _backend.total_size() ) + return pptr(); + pmm::Block* blk = find_block_from_user_ptr( raw ); + if ( blk == nullptr ) return pptr(); - std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = byte_off / address_traits::granule_size; - if ( idx > static_cast( std::numeric_limits::max() ) ) + index_type blk_idx = detail::block_idx_t( base, blk ); + if ( blk_idx > std::numeric_limits::max() - kBlockHdrGranules ) return pptr(); - return pptr( static_cast( idx ) ); + return pptr( static_cast( blk_idx + kBlockHdrGranules ) ); } // ─── blk_raw helpers ────────────────────────────────────────── -// base + offset * granule_size - sizeof(Block) → block header before user data. +// base + (offset - kBlockHdrGranules) * granule_size → block header before public user data. /// @brief Return a const pointer to the block header for the given pptr. /// Returns nullptr if offset is invalid (would place block header before base). template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept { - const std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) + const std::uint8_t* base = _backend.base_ptr(); + if ( p.offset() < kBlockHdrGranules ) return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); + std::size_t blk_off = static_cast( p.offset() - kBlockHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off; @@ -9034,11 +9078,10 @@ template static const void* block_raw_ptr_from_pptr( pptr p ) no /// Returns nullptr if offset is invalid (would place block header before base). template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept { - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) + std::uint8_t* base = _backend.base_ptr(); + if ( p.offset() < kBlockHdrGranules ) return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); + std::size_t blk_off = static_cast( p.offset() - kBlockHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off; diff --git a/single_include/pmm/pmm_no_comments.h b/single_include/pmm/pmm_no_comments.h index b7ecffe3..cef12dcd 100644 --- a/single_include/pmm/pmm_no_comments.h +++ b/single_include/pmm/pmm_no_comments.h @@ -3962,11 +3962,36 @@ template struct pstringview if ( raw == nullptr ) return psview_pptr(); - std::uint8_t* base = ManagerT::backend().base_ptr(); - psview_pptr new_node( detail::ptr_to_granule_idx( base, raw ) ); + using address_traits = typename ManagerT::address_traits; + std::uint8_t* base = ManagerT::backend().base_ptr(); + auto* raw_ptr = static_cast( raw ); + if ( base == nullptr || raw_ptr < base + sizeof( pmm::Block ) ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + std::size_t block_byte_off = static_cast( raw_ptr - base ) - sizeof( pmm::Block ); + if ( block_byte_off % address_traits::granule_size != 0 ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + std::size_t public_idx = + block_byte_off / address_traits::granule_size + detail::kBlockHeaderGranules_t; + if ( public_idx > static_cast( address_traits::no_block ) ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + psview_pptr new_node( static_cast( public_idx ) ); - pstringview* obj = static_cast( raw ); - obj->length = len; + pstringview* obj = ManagerT::template resolve_unchecked( new_node ); + if ( obj == nullptr ) + { + ManagerT::deallocate( raw ); + return psview_pptr(); + } + obj->length = len; std::memcpy( obj->str, s, static_cast( len ) + 1 ); @@ -4370,10 +4395,7 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) - ? pptr() - : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); + return make_pptr_from_raw( raw ); } template static pptr allocate_typed( std::size_t count ) noexcept @@ -4386,10 +4408,7 @@ template cla void* raw = allocate( sizeof( T ) * count ); if ( raw == nullptr ) return pptr(); - pmm::Block* blk = find_block_from_user_ptr( raw ); - return ( blk == nullptr ) - ? pptr() - : pptr( detail::block_idx_t( _backend.base_ptr(), blk ) + kBlockHdrGranules ); + return make_pptr_from_raw( raw ); } template static void deallocate_typed( pptr p ) noexcept @@ -4492,13 +4511,12 @@ template cla _last_error = PmmError::OutOfMemory; return pptr(); } - pmm::Block* new_blk = find_block_from_user_ptr( new_raw ); - if ( new_blk == nullptr ) + pptr new_p = make_pptr_from_raw( new_raw ); + if ( new_p.is_null() ) { _last_error = PmmError::InvalidPointer; return pptr(); } - pptr new_p( detail::block_idx_t( base, new_blk ) + kBlockHdrGranules ); void* new_dst = resolve_unchecked( new_p ); void* old_src = resolve_unchecked( p ); std::size_t copy_sz = ( new_count < old_count ? new_count : old_count ) * sizeof( T ); @@ -4534,8 +4552,15 @@ template cla void* raw = allocate( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - ::new ( raw ) T( static_cast( args )... ); - return make_pptr_from_raw( raw ); + pptr p = make_pptr_from_raw( raw ); + T* obj = resolve_unchecked( p ); + if ( obj == nullptr ) + { + deallocate( raw ); + return pptr(); + } + ::new ( obj ) T( static_cast( args )... ); + return p; } template static void destroy_typed( pptr p ) noexcept @@ -4545,8 +4570,11 @@ template cla if ( p.is_null() || !_initialized ) return; + T* obj = resolve_unchecked( p ); void* raw = raw_block_user_ptr_from_pptr( p ); - reinterpret_cast( raw )->~T(); + if ( obj == nullptr || raw == nullptr ) + return; + obj->~T(); deallocate( raw ); } @@ -5094,8 +5122,15 @@ template cla void* raw = allocate_unlocked( sizeof( T ) ); if ( raw == nullptr ) return pptr(); - ::new ( raw ) T( static_cast( args )... ); - return make_pptr_from_raw( raw ); + pptr p = make_pptr_from_raw( raw ); + T* obj = resolve_unchecked( p ); + if ( obj == nullptr ) + { + deallocate_unlocked( raw ); + return pptr(); + } + ::new ( obj ) T( static_cast( args )... ); + return p; } static forest_registry* forest_registry_root_unlocked() noexcept @@ -5280,17 +5315,23 @@ static pptr intern_symbol_unlocked( const char* s ) noexcept if ( raw == nullptr ) return pptr(); - pptr new_node = make_pptr_from_raw( raw ); + pptr new_node = make_pptr_from_raw( raw ); + void* public_raw = raw_user_ptr_from_pptr( new_node ); + if ( public_raw == nullptr ) + { + deallocate_unlocked( raw ); + return pptr(); + } - std::memcpy( raw, &len, sizeof( len ) ); - char* str_dst = static_cast( raw ) + offsetof( pstringview, str ); + std::memcpy( public_raw, &len, sizeof( len ) ); + char* str_dst = static_cast( public_raw ) + offsetof( pstringview, str ); std::memcpy( str_dst, s, static_cast( len ) + 1 ); detail::avl_init_node( new_node ); - if ( !lock_block_permanent_unlocked( raw ) ) + if ( !lock_block_permanent_unlocked( public_raw ) ) return pptr(); - const char* new_str = static_cast( raw ) + offsetof( pstringview, str ); + const char* new_str = static_cast( public_raw ) + offsetof( pstringview, str ); detail::avl_insert( new_node, symbol_domain->root_offset, [&]( pptr cur ) -> bool @@ -5604,24 +5645,27 @@ static const pmm::Block* find_block_from_user_ptr( const void* p template static pptr make_pptr_from_raw( void* raw ) noexcept { + if ( raw == nullptr || !_initialized ) + return pptr(); std::uint8_t* base = _backend.base_ptr(); auto* raw_byte = static_cast( raw ); - if ( raw_byte < base || raw_byte >= base + _backend.total_size() ) + if ( base == nullptr || raw_byte < base || raw_byte >= base + _backend.total_size() ) + return pptr(); + pmm::Block* blk = find_block_from_user_ptr( raw ); + if ( blk == nullptr ) return pptr(); - std::size_t byte_off = static_cast( raw_byte - base ); - std::size_t idx = byte_off / address_traits::granule_size; - if ( idx > static_cast( std::numeric_limits::max() ) ) + index_type blk_idx = detail::block_idx_t( base, blk ); + if ( blk_idx > std::numeric_limits::max() - kBlockHdrGranules ) return pptr(); - return pptr( static_cast( idx ) ); + return pptr( static_cast( blk_idx + kBlockHdrGranules ) ); } template static const void* block_raw_ptr_from_pptr( pptr p ) noexcept { - const std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) + const std::uint8_t* base = _backend.base_ptr(); + if ( p.offset() < kBlockHdrGranules ) return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); + std::size_t blk_off = static_cast( p.offset() - kBlockHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off; @@ -5629,11 +5673,10 @@ template static const void* block_raw_ptr_from_pptr( pptr p ) no template static void* block_raw_mut_ptr_from_pptr( pptr p ) noexcept { - std::uint8_t* base = _backend.base_ptr(); - std::size_t byte_off = static_cast( p.offset() ) * address_traits::granule_size; - if ( byte_off < sizeof( Block ) ) + std::uint8_t* base = _backend.base_ptr(); + if ( p.offset() < kBlockHdrGranules ) return nullptr; - std::size_t blk_off = byte_off - sizeof( Block ); + std::size_t blk_off = static_cast( p.offset() - kBlockHdrGranules ) * address_traits::granule_size; if ( blk_off + sizeof( Block ) > _backend.total_size() ) return nullptr; return base + blk_off; diff --git a/tests/test_issue312_access_modes.cpp b/tests/test_issue312_access_modes.cpp index ed7a04f0..1f59b8e5 100644 --- a/tests/test_issue312_access_modes.cpp +++ b/tests/test_issue312_access_modes.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { @@ -63,6 +64,48 @@ TEST_CASE( "I312: SmallAddressTraits resolves to canonical non-aligned user poin SmallMgr::destroy(); } +TEST_CASE( "I312: SmallAddressTraits create_typed returns canonical public pptr", "[test_issue312]" ) +{ + static_assert( sizeof( pmm::Block ) % pmm::SmallAddressTraits::granule_size != 0, + "SmallAddressTraits must exercise the non-aligned block-header path" ); + + REQUIRE( SmallMgr::create() ); + + SmallMgr::pptr p = SmallMgr::create_typed( 0xC312u ); + REQUIRE( !p.is_null() ); + + auto* checked = SmallMgr::resolve_checked( p ); + auto* unchecked = SmallMgr::resolve_unchecked( p ); + REQUIRE( checked != nullptr ); + REQUIRE( unchecked != nullptr ); + REQUIRE( checked == unchecked ); + REQUIRE( reinterpret_cast( checked ) % pmm::SmallAddressTraits::granule_size == 0 ); + REQUIRE( *checked == 0xC312u ); + + SmallMgr::destroy_typed( p ); + SmallMgr::destroy(); +} + +TEST_CASE( "I312: SmallAddressTraits interned symbols use canonical public pptrs", "[test_issue312]" ) +{ + static_assert( sizeof( pmm::Block ) % pmm::SmallAddressTraits::granule_size != 0, + "SmallAddressTraits must exercise the non-aligned block-header path" ); + + REQUIRE( SmallMgr::create() ); + REQUIRE( SmallMgr::register_domain( "app/issue312" ) ); + + auto domain_id = SmallMgr::find_domain_by_name( "app/issue312" ); + REQUIRE( domain_id != 0 ); + + SmallMgr::pptr symbol = SmallMgr::pstringview( "app/issue312" ); + REQUIRE( !symbol.is_null() ); + REQUIRE( SmallMgr::resolve_checked( symbol ) != nullptr ); + REQUIRE( symbol->c_str() == std::string( "app/issue312" ) ); + REQUIRE( SmallMgr::find_domain_by_symbol( symbol ) == domain_id ); + + SmallMgr::destroy(); +} + TEST_CASE( "I312: invalid out-of-range pptr is rejected by both access modes", "[test_issue312]" ) { REQUIRE( Mgr::create( 64 * 1024 ) );