240 lines
6.7 KiB
C++
240 lines
6.7 KiB
C++
#ifdef SNMALLOC_PASS_THROUGH
|
|
/*
|
|
* This test does not make sense with malloc pass-through, skip it.
|
|
*/
|
|
int main()
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
// The decommit strategy is currently a global policy and not per-allocator and
|
|
// so we need to tell Windows not to use the lazy strategy for this test.
|
|
# define USE_DECOMMIT_STRATEGY DecommitSuper
|
|
# include <snmalloc.h>
|
|
|
|
using namespace snmalloc;
|
|
|
|
namespace
|
|
{
|
|
/**
|
|
* Helper for Alloc that is never used as a thread-local allocator and so is
|
|
* always initialised.
|
|
*/
|
|
bool never_init(void*)
|
|
{
|
|
return false;
|
|
}
|
|
/**
|
|
* Helper for Alloc that never needs lazy initialisation.
|
|
*/
|
|
void* no_op_init(function_ref<void*(void*)>)
|
|
{
|
|
SNMALLOC_CHECK(0 && "Should never be called!");
|
|
return nullptr;
|
|
}
|
|
/**
|
|
* Sandbox class. Allocates a memory region and an allocator that can
|
|
* allocate into this from the outside.
|
|
*/
|
|
struct Sandbox
|
|
{
|
|
using NoOpPal = PALNoAlloc<DefaultPal>;
|
|
/**
|
|
* Type for the allocator that lives outside of the sandbox and allocates
|
|
* sandbox-owned memory.
|
|
*/
|
|
using ExternalAlloc = Allocator<
|
|
never_init,
|
|
no_op_init,
|
|
MemoryProviderStateMixin<NoOpPal>,
|
|
SNMALLOC_DEFAULT_CHUNKMAP,
|
|
false>;
|
|
/**
|
|
* Proxy class that forwards requests for large allocations to the real
|
|
* memory provider.
|
|
*
|
|
* In a real implementation, these would be cross-domain calls with the
|
|
* callee verifying the arguments.
|
|
*/
|
|
struct MemoryProviderProxy
|
|
{
|
|
/**
|
|
* The PAL that allocators using this memory provider should use.
|
|
*/
|
|
typedef NoOpPal Pal;
|
|
/**
|
|
* The pointer to the real state. In a real implementation there would
|
|
* likely be only one of these inside any given sandbox and so this would
|
|
* not have to be per-instance state.
|
|
*/
|
|
MemoryProviderStateMixin<NoOpPal>* real_state;
|
|
|
|
/**
|
|
* Pop an element from the large stack for the specified size class,
|
|
* proxies to the real implementation.
|
|
*
|
|
* This method must be implemented for `LargeAlloc` to work.
|
|
*/
|
|
CapPtr<Largeslab, CBArena> pop_large_stack(size_t large_class)
|
|
{
|
|
return real_state->pop_large_stack(large_class);
|
|
};
|
|
|
|
/**
|
|
* Push an element to the large stack for the specified size class,
|
|
* proxies to the real implementation.
|
|
*
|
|
* This method must be implemented for `LargeAlloc` to work.
|
|
*/
|
|
void push_large_stack(CapPtr<Largeslab, CBArena> slab, size_t large_class)
|
|
{
|
|
real_state->push_large_stack(slab, large_class);
|
|
}
|
|
|
|
/**
|
|
* Reserve (and optionally commit) memory for a large sizeclass, proxies
|
|
* to the real implementation.
|
|
*
|
|
* This method must be implemented for `LargeAlloc` to work.
|
|
*/
|
|
template<bool committed>
|
|
CapPtr<Largeslab, CBArena> reserve(size_t large_class) noexcept
|
|
{
|
|
return real_state->template reserve<committed>(large_class);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Type for the allocator that exists inside the sandbox.
|
|
*
|
|
* Note that a real version of this would not have access to the shared
|
|
* pagemap and would not be used outside of the sandbox.
|
|
*/
|
|
using InternalAlloc =
|
|
Allocator<never_init, no_op_init, MemoryProviderProxy>;
|
|
|
|
/**
|
|
* The start of the sandbox memory region.
|
|
*/
|
|
void* start;
|
|
|
|
/**
|
|
* The end of the sandbox memory region
|
|
*/
|
|
void* top;
|
|
|
|
/**
|
|
* State allocated in the sandbox that is shared between the inside and
|
|
* outside.
|
|
*/
|
|
struct SharedState
|
|
{
|
|
/**
|
|
* The message queue for the allocator that lives outside of the
|
|
* sandbox but allocates memory inside.
|
|
*/
|
|
struct RemoteAllocator queue;
|
|
} * shared_state;
|
|
|
|
/**
|
|
* The memory provider for this sandbox.
|
|
*/
|
|
MemoryProviderStateMixin<NoOpPal> state;
|
|
|
|
/**
|
|
* The allocator for callers outside the sandbox to allocate memory inside.
|
|
*/
|
|
ExternalAlloc alloc;
|
|
|
|
/**
|
|
* An allocator for callers inside the sandbox to allocate memory.
|
|
*/
|
|
InternalAlloc* internal_alloc;
|
|
|
|
/**
|
|
* Constructor. Takes the size of the sandbox as the argument.
|
|
*/
|
|
Sandbox(size_t sb_size)
|
|
: start(alloc_sandbox_heap(sb_size)),
|
|
top(pointer_offset(start, sb_size)),
|
|
shared_state(new (start) SharedState()),
|
|
state(
|
|
pointer_offset(CapPtr<void, CBArena>(start), sizeof(SharedState)),
|
|
sb_size - sizeof(SharedState)),
|
|
alloc(state, SNMALLOC_DEFAULT_CHUNKMAP(), &shared_state->queue)
|
|
{
|
|
auto* state_proxy = static_cast<MemoryProviderProxy*>(
|
|
alloc.alloc(sizeof(MemoryProviderProxy)));
|
|
state_proxy->real_state = &state;
|
|
// In real code, allocators should never be constructed like this, they
|
|
// should always come from an alloc pool. This is just to test that both
|
|
// kinds of allocator can be created.
|
|
internal_alloc =
|
|
new (alloc.alloc(sizeof(InternalAlloc))) InternalAlloc(*state_proxy);
|
|
}
|
|
|
|
Sandbox() = delete;
|
|
|
|
/**
|
|
* Predicate function for querying whether an object is entirely within the
|
|
* sandbox.
|
|
*/
|
|
bool is_in_sandbox(void* ptr, size_t sz)
|
|
{
|
|
return (ptr >= start) && (pointer_offset(ptr, sz) < top);
|
|
}
|
|
|
|
/**
|
|
* Predicate function for querying whether an object is entirely within the
|
|
* region of the sandbox allocated for its heap.
|
|
*/
|
|
bool is_in_sandbox_heap(void* ptr, size_t sz)
|
|
{
|
|
return (
|
|
ptr >= pointer_offset(start, sizeof(SharedState)) &&
|
|
(pointer_offset(ptr, sz) < top));
|
|
}
|
|
|
|
private:
|
|
template<typename PAL = DefaultPal>
|
|
void* alloc_sandbox_heap(size_t sb_size)
|
|
{
|
|
// Use the outside-sandbox snmalloc to allocate memory, rather than using
|
|
// the PAL directly, so that our out-of-sandbox can amplify sandbox
|
|
// pointers
|
|
return ThreadAlloc::get_noncachable()->alloc(sb_size);
|
|
}
|
|
};
|
|
}
|
|
|
|
int main()
|
|
{
|
|
static const size_t sb_size = 128 * 1024 * 1024;
|
|
|
|
// Check that we can create two sandboxes
|
|
Sandbox sb1(sb_size);
|
|
Sandbox sb2(sb_size);
|
|
|
|
auto check = [](Sandbox& sb, auto& alloc, size_t sz) {
|
|
void* ptr = alloc.alloc(sz);
|
|
SNMALLOC_CHECK(sb.is_in_sandbox_heap(ptr, sz));
|
|
ThreadAlloc::get_noncachable()->dealloc(ptr);
|
|
};
|
|
auto check_with_sb = [&](Sandbox& sb) {
|
|
// Check with a range of sizes
|
|
check(sb, sb.alloc, 32);
|
|
check(sb, *sb.internal_alloc, 32);
|
|
check(sb, sb.alloc, 240);
|
|
check(sb, *sb.internal_alloc, 240);
|
|
check(sb, sb.alloc, 513);
|
|
check(sb, *sb.internal_alloc, 513);
|
|
check(sb, sb.alloc, 10240);
|
|
check(sb, *sb.internal_alloc, 10240);
|
|
};
|
|
check_with_sb(sb1);
|
|
check_with_sb(sb2);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|