blob: c721b91fc5c3617c87476425d325a0e6b7db7e8a [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_POOL_OFFSET_FREELIST_H_
#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_POOL_OFFSET_FREELIST_H_
#include <cstddef>
#include <cstdint>
#include "base/allocator/partition_allocator/src/partition_alloc/partition_address_space.h"
#include "base/allocator/partition_allocator/src/partition_alloc/partition_alloc-inl.h"
#include "base/allocator/partition_allocator/src/partition_alloc/partition_alloc_base/compiler_specific.h"
namespace partition_alloc::internal {
// This implementation of PartitionAlloc's freelist uses pool offsets
// rather than naked pointers. This is intended to prevent usage of
// freelist pointers to easily jump around to freed slots.
class PoolOffsetFreelistEntry {
private:
constexpr explicit PoolOffsetFreelistEntry(std::nullptr_t) {}
explicit PoolOffsetFreelistEntry(PoolOffsetFreelistEntry* next)
: next_(PoolOffset(reinterpret_cast<uintptr_t>(next))) {}
// For testing only.
PoolOffsetFreelistEntry(void* next, bool make_shadow_match)
: next_(PoolOffset(reinterpret_cast<uintptr_t>(next))) {}
public:
~PoolOffsetFreelistEntry() = delete;
PA_ALWAYS_INLINE static PoolOffsetFreelistEntry* EmplaceAndInitNull(
void* slot_start_tagged) {
auto* entry = new (slot_start_tagged) PoolOffsetFreelistEntry(nullptr);
return entry;
}
PA_ALWAYS_INLINE static PoolOffsetFreelistEntry* EmplaceAndInitNull(
uintptr_t slot_start) {
return EmplaceAndInitNull(SlotStartAddr2Ptr(slot_start));
}
PA_ALWAYS_INLINE static PoolOffsetFreelistEntry* EmplaceAndInitForThreadCache(
uintptr_t slot_start,
PoolOffsetFreelistEntry* next) {
auto* entry =
new (SlotStartAddr2Ptr(slot_start)) PoolOffsetFreelistEntry(next);
return entry;
}
PA_ALWAYS_INLINE static void EmplaceAndInitForTest(uintptr_t slot_start,
void* next,
bool make_shadow_match) {
new (SlotStartAddr2Ptr(slot_start))
PoolOffsetFreelistEntry(next, make_shadow_match);
}
void CorruptNextForTesting(uintptr_t v) {
// TODO(crbug.com/1461983): Make this do something useful.
next_ += 1ull << 31;
}
template <bool crash_on_corruption>
PA_ALWAYS_INLINE PoolOffsetFreelistEntry* GetNextForThreadCache(
size_t slot_size) const {
return GetNextInternal<crash_on_corruption, /*for_thread_cache=*/true>(
slot_size);
}
PA_ALWAYS_INLINE PoolOffsetFreelistEntry* GetNext(size_t slot_size) const {
return GetNextInternal<true, /*for_thread_cache=*/false>(slot_size);
}
PA_NOINLINE void CheckFreeList(size_t slot_size) const {
for (auto* entry = this; entry; entry = entry->GetNext(slot_size)) {
// `GetNext()` calls `IsWellFormed()`.
}
}
PA_NOINLINE void CheckFreeListForThreadCache(size_t slot_size) const {
for (auto* entry = this; entry;
entry = entry->GetNextForThreadCache<true>(slot_size)) {
// `GetNextForThreadCache()` calls `IsWellFormed()`.
}
}
PA_ALWAYS_INLINE void SetNext(PoolOffsetFreelistEntry* entry) {
next_ = PoolOffset(reinterpret_cast<uintptr_t>(entry));
}
PA_ALWAYS_INLINE uintptr_t ClearForAllocation() {
next_ = uintptr_t{0};
return SlotStartPtr2Addr(this);
}
PA_ALWAYS_INLINE constexpr bool IsEncodedNextPtrZero() const {
return !next_;
}
private:
// Determines the containing pool of `addr` and returns `addr`
// represented as an offset into that pool.
PA_ALWAYS_INLINE static uintptr_t PoolOffset(uintptr_t addr) {
return addr ? PartitionAddressSpace::GetPoolInfo(addr).offset : addr;
}
template <bool crash_on_corruption, bool for_thread_cache>
PA_ALWAYS_INLINE PoolOffsetFreelistEntry* GetNextInternal(
size_t slot_size) const {
if (IsEncodedNextPtrZero()) {
return nullptr;
}
auto* ret = reinterpret_cast<PoolOffsetFreelistEntry*>(
GetPoolInfo(reinterpret_cast<uintptr_t>(this)).base + next_);
if (PA_UNLIKELY(!IsWellFormed<for_thread_cache>(this, ret))) {
if constexpr (crash_on_corruption) {
PA_DEBUG_DATA_ON_STACK("first", static_cast<size_t>(next_));
FreelistCorruptionDetected(slot_size);
}
return nullptr;
}
PA_PREFETCH(ret);
return ret;
}
// TODO(crbug.com/1461983): Add support for freelist shadow entries
// (and freeslot bitmaps).
template <bool for_thread_cache>
PA_ALWAYS_INLINE static bool IsWellFormed(
const PoolOffsetFreelistEntry* here,
const PoolOffsetFreelistEntry* next) {
const uintptr_t here_address = SlotStartPtr2Addr(here);
const uintptr_t next_address = SlotStartPtr2Addr(next);
const bool not_in_metadata =
(next_address & kSuperPageOffsetMask) >= PartitionPageSize();
if constexpr (for_thread_cache) {
return not_in_metadata;
}
const bool same_super_page = (here_address & kSuperPageBaseMask) ==
(next_address & kSuperPageBaseMask);
return same_super_page && not_in_metadata;
}
// Expresses the next entry in the freelist as an offset in the
// same pool as `this`, except when 0, which (as an invalid pool
// offset) serves as a sentinel value.
uintptr_t next_ = uintptr_t{0};
};
} // namespace partition_alloc::internal
#endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_POOL_OFFSET_FREELIST_H_