#include "base/memory/discardable_shared_memory.h"
#include <fcntl.h>
#include <stdint.h>
#include <algorithm>
#include "base/files/scoped_file.h"
#include "base/memory/page_size.h"
#include "base/memory/shared_memory_tracker.h"
#include "base/trace_event/memory_allocator_dump.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/tracing_buildflags.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
class TestDiscardableSharedMemory : public DiscardableSharedMemory {
public:
TestDiscardableSharedMemory() = default;
explicit TestDiscardableSharedMemory(UnsafeSharedMemoryRegion region)
: DiscardableSharedMemory(std::move(region)) {}
void SetNow(Time now) { now_ = now; }
private:
Time Now() const override { return now_; }
Time now_;
};
TEST(DiscardableSharedMemoryTest, CreateAndMap) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
EXPECT_GE(memory.mapped_size(), kDataSize);
EXPECT_TRUE(memory.IsMemoryLocked());
}
TEST(DiscardableSharedMemoryTest, CreateFromHandle) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
EXPECT_TRUE(memory2.IsMemoryLocked());
}
TEST(DiscardableSharedMemoryTest, LockAndUnlock) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
memory1.SetNow(Time::FromSecondsSinceUnixEpoch(1));
memory1.Unlock(0, 0);
EXPECT_FALSE(memory1.IsMemoryLocked());
DiscardableSharedMemory::LockResult lock_rv = memory1.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
memory1.SetNow(Time::FromSecondsSinceUnixEpoch(2));
memory1.Unlock(0, 0);
lock_rv = memory1.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
EXPECT_TRUE(memory1.IsMemoryLocked());
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(3));
memory2.Unlock(0, 0);
EXPECT_FALSE(memory2.IsMemoryLocked());
EXPECT_FALSE(memory1.IsMemoryLocked());
lock_rv = memory2.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
rv = memory1.IsMemoryResident();
EXPECT_TRUE(rv);
EXPECT_TRUE(memory1.IsMemoryLocked());
memory1.SetNow(Time::FromSecondsSinceUnixEpoch(4));
memory1.Unlock(0, 0);
}
TEST(DiscardableSharedMemoryTest, Purge) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(1));
EXPECT_FALSE(rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(2));
memory2.Unlock(0, 0);
ASSERT_TRUE(memory2.IsMemoryResident());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(3));
EXPECT_FALSE(rv);
ASSERT_TRUE(memory2.IsMemoryResident());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(4));
EXPECT_TRUE(rv);
DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
ASSERT_FALSE(memory2.IsMemoryResident());
}
TEST(DiscardableSharedMemoryTest, PurgeAfterClose) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
memory.SetNow(Time::FromSecondsSinceUnixEpoch(2));
memory.Unlock(0, 0);
memory.Close();
rv = memory.Purge(Time::FromSecondsSinceUnixEpoch(4));
EXPECT_TRUE(rv);
}
TEST(DiscardableSharedMemoryTest, LastUsed) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(1));
memory2.Unlock(0, 0);
EXPECT_EQ(memory2.last_known_usage(), Time::FromSecondsSinceUnixEpoch(1));
DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(2));
ASSERT_FALSE(rv);
EXPECT_EQ(memory1.last_known_usage(), Time::FromSecondsSinceUnixEpoch(2));
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(3));
memory2.Unlock(0, 0);
EXPECT_EQ(memory2.last_known_usage(), Time::FromSecondsSinceUnixEpoch(3));
EXPECT_EQ(memory1.last_known_usage(), Time::FromSecondsSinceUnixEpoch(2));
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(4));
EXPECT_FALSE(rv);
EXPECT_EQ(memory1.last_known_usage(), Time::FromSecondsSinceUnixEpoch(3));
rv = memory2.Purge(Time::FromSecondsSinceUnixEpoch(5));
EXPECT_TRUE(rv);
EXPECT_TRUE(memory2.last_known_usage().is_null());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(6));
EXPECT_FALSE(rv);
EXPECT_TRUE(memory1.last_known_usage().is_null());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(7));
EXPECT_TRUE(rv);
}
TEST(DiscardableSharedMemoryTest, LockShouldAlwaysFailAfterSuccessfulPurge) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(1));
memory2.Unlock(0, 0);
rv = memory2.Purge(Time::FromSecondsSinceUnixEpoch(2));
EXPECT_TRUE(rv);
DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
}
#if BUILDFLAG(IS_ANDROID)
TEST(DiscardableSharedMemoryTest, LockShouldFailIfPlatformLockPagesFails) {
const uint32_t kDataSize = 1024;
if (!DiscardableSharedMemory::IsAshmemDeviceSupportedForTesting()) {
return;
}
DiscardableSharedMemory memory1;
bool rv1 = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv1);
base::UnsafeSharedMemoryRegion region = memory1.DuplicateRegion();
int fd = region.GetPlatformHandle();
DiscardableSharedMemory memory2(std::move(region));
bool rv2 = memory2.Map(kDataSize);
ASSERT_TRUE(rv2);
memory2.Unlock(0, base::GetPageSize());
base::ScopedFD null(open("/dev/null", O_RDONLY));
ASSERT_EQ(fd, dup2(null.get(), fd));
DiscardableSharedMemory::LockResult lock_rv =
memory2.Lock(0, base::GetPageSize());
EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
}
#endif
TEST(DiscardableSharedMemoryTest, LockAndUnlockRange) {
const size_t kDataSize = 32;
size_t data_size_in_bytes = kDataSize * base::GetPageSize();
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(data_size_in_bytes);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(data_size_in_bytes);
ASSERT_TRUE(rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(1));
memory2.Unlock(0, base::GetPageSize());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(2));
EXPECT_FALSE(rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(3));
DiscardableSharedMemory::LockResult lock_rv =
memory2.Lock(0, base::GetPageSize());
EXPECT_NE(DiscardableSharedMemory::FAILED, lock_rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(4));
memory2.Unlock(0, base::GetPageSize());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(5));
EXPECT_FALSE(rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(6));
memory2.Unlock(base::GetPageSize(), base::GetPageSize());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(7));
EXPECT_FALSE(rv);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(8));
memory2.Unlock(2 * base::GetPageSize(), 0);
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(9));
EXPECT_FALSE(rv);
EXPECT_EQ(Time::FromSecondsSinceUnixEpoch(8), memory1.last_known_usage());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(10));
EXPECT_TRUE(rv);
}
TEST(DiscardableSharedMemoryTest, MappedSize) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
EXPECT_LE(kDataSize, memory.mapped_size());
rv = memory.Unmap();
EXPECT_TRUE(rv);
EXPECT_EQ(0u, memory.mapped_size());
}
TEST(DiscardableSharedMemoryTest, Close) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
memory.Close();
EXPECT_LE(kDataSize, memory.mapped_size());
memory.SetNow(Time::FromSecondsSinceUnixEpoch(1));
memory.Unlock(0, 0);
DiscardableSharedMemory::LockResult lock_rv = memory.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
memory.SetNow(Time::FromSecondsSinceUnixEpoch(2));
memory.Unlock(0, 0);
}
TEST(DiscardableSharedMemoryTest, ZeroSize) {
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(0);
ASSERT_TRUE(rv);
EXPECT_LE(0u, memory.mapped_size());
memory.SetNow(Time::FromSecondsSinceUnixEpoch(1));
memory.Unlock(0, 0);
DiscardableSharedMemory::LockResult lock_rv = memory.Lock(0, 0);
EXPECT_NE(DiscardableSharedMemory::FAILED, lock_rv);
memory.SetNow(Time::FromSecondsSinceUnixEpoch(2));
memory.Unlock(0, 0);
}
#if defined(DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE)
TEST(DiscardableSharedMemoryTest, ZeroFilledPagesAfterPurge) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
std::ranges::fill(memory2.memory(), 0xaa);
memory2.SetNow(Time::FromSecondsSinceUnixEpoch(1));
memory2.Unlock(0, 0);
EXPECT_FALSE(memory1.IsMemoryLocked());
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(2));
EXPECT_FALSE(rv);
rv = memory1.Purge(Time::FromSecondsSinceUnixEpoch(3));
EXPECT_TRUE(rv);
uint8_t expected_data[kDataSize] = {};
EXPECT_EQ(base::span(expected_data), memory2.memory());
}
#endif
TEST(DiscardableSharedMemoryTest, TracingOwnershipEdges) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
base::trace_event::MemoryDumpArgs args = {
base::trace_event::MemoryDumpLevelOfDetail::kDetailed};
trace_event::ProcessMemoryDump pmd(args);
trace_event::MemoryAllocatorDump* client_dump =
pmd.CreateAllocatorDump("discardable_manager/map1");
const bool is_owned = false;
memory1.CreateSharedMemoryOwnershipEdge(client_dump, &pmd, is_owned);
const auto* shm_dump = pmd.GetAllocatorDump(
SharedMemoryTracker::GetDumpNameForTracing(memory1.mapped_id()));
EXPECT_TRUE(shm_dump);
EXPECT_EQ(shm_dump->GetSizeInternal(), client_dump->GetSizeInternal());
const auto edges = pmd.allocator_dumps_edges();
EXPECT_EQ(2u, edges.size());
EXPECT_NE(edges.end(), edges.find(shm_dump->guid()));
EXPECT_NE(edges.end(), edges.find(client_dump->guid()));
}
}