blob: a2931467081f1eb2cb48d1a712a88770c7d0077b [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#include "base/allocator/partition_alloc_support.h"
#include <array>
#include <string>
#include <utility>
#include <vector>
#include "base/allocator/partition_alloc_features.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/dangling_raw_ptr_checks.h"
#include "partition_alloc/partition_alloc_base/cpu.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base::allocator {
using testing::AllOf;
using testing::HasSubstr;
TEST(PartitionAllocSupportTest,
ProposeSyntheticFinchTrials_DanglingPointerDetector) {
std::string dpd_group =
ProposeSyntheticFinchTrials()["DanglingPointerDetector"];
#if PA_BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS)
EXPECT_EQ(dpd_group, "Enabled");
#else
EXPECT_EQ(dpd_group, "Disabled");
#endif
}
// - Death tests misbehave on Android, http://crbug.com/643760.
#if PA_BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS) && !BUILDFLAG(IS_ANDROID) && \
defined(GTEST_HAS_DEATH_TEST)
namespace {
// Install dangling raw_ptr handler and restore them when going out of scope.
class ScopedInstallDanglingRawPtrChecks {
public:
struct ConstructorParams {
std::string mode = "crash";
std::string type = "all";
};
explicit ScopedInstallDanglingRawPtrChecks(ConstructorParams params) {
enabled_feature_list_.InitWithFeaturesAndParameters(
{{features::kPartitionAllocDanglingPtr,
{{"mode", params.mode}, {"type", params.type}}}},
{/* disabled_features */});
old_detected_fn_ = partition_alloc::GetDanglingRawPtrDetectedFn();
old_dereferenced_fn_ = partition_alloc::GetDanglingRawPtrReleasedFn();
InstallDanglingRawPtrChecks();
}
ScopedInstallDanglingRawPtrChecks()
: ScopedInstallDanglingRawPtrChecks(ConstructorParams{}) {}
~ScopedInstallDanglingRawPtrChecks() {
InstallDanglingRawPtrChecks(); // Check for leaks.
partition_alloc::SetDanglingRawPtrDetectedFn(old_detected_fn_);
partition_alloc::SetDanglingRawPtrReleasedFn(old_dereferenced_fn_);
}
private:
test::ScopedFeatureList enabled_feature_list_;
partition_alloc::DanglingRawPtrDetectedFn* old_detected_fn_;
partition_alloc::DanglingRawPtrReleasedFn* old_dereferenced_fn_;
};
} // namespace
TEST(PartitionAllocDanglingPtrChecks, Basic) {
EXPECT_DEATH(
{
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks;
partition_alloc::GetDanglingRawPtrDetectedFn()(42);
partition_alloc::GetDanglingRawPtrReleasedFn()(42);
},
AllOf(HasSubstr("[DanglingSignature]\t"),
HasSubstr("[DanglingPtr](1/3) A raw_ptr/raw_ref is dangling."),
HasSubstr("[DanglingPtr](2/3) First, the memory was freed at:"),
HasSubstr("[DanglingPtr](3/3) Later, the dangling raw_ptr was "
"released at:")));
}
// The StackTrace buffer might run out of storage and not record where the
// memory was freed. Anyway, it must still report the error.
TEST(PartitionAllocDanglingPtrChecks, FreeNotRecorded) {
EXPECT_DEATH(
{
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks;
partition_alloc::GetDanglingRawPtrReleasedFn()(42);
},
AllOf(HasSubstr("[DanglingSignature]\tmissing\tmissing\t"),
HasSubstr("[DanglingPtr](1/3) A raw_ptr/raw_ref is dangling."),
HasSubstr("[DanglingPtr](2/3) It was not recorded where the memory "
"was freed."),
HasSubstr("[DanglingPtr](3/3) Later, the dangling raw_ptr was "
"released at:")));
}
// TODO(crbug.com/40260713): Check for leaked refcount on Android.
#if BUILDFLAG(IS_ANDROID)
// Some raw_ptr might never release their refcount. Make sure this cause a
// crash on exit.
TEST(PartitionAllocDanglingPtrChecks, ReleaseNotRecorded) {
EXPECT_DEATH(
{
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks;
partition_alloc::GetDanglingRawPtrDetectedFn()(42);
},
HasSubstr("A freed allocation is still referenced by a dangling pointer "
"at exit, or at test end. Leaked raw_ptr/raw_ref "
"could cause PartitionAlloc's quarantine memory bloat."
"\n\n"
"Memory was released on:"));
}
#endif
// Getting the same allocation reported twice in a row, without matching
// `DanglingRawPtrReleased` in between is unexpected. Make sure this kind of
// potential regression would be detected.
TEST(PartitionAllocDanglingPtrChecks, DoubleDetection) {
EXPECT_DCHECK_DEATH_WITH(
{
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks;
partition_alloc::GetDanglingRawPtrDetectedFn()(42);
partition_alloc::GetDanglingRawPtrDetectedFn()(42);
},
"Check failed: !entry \\|\\| entry->id != id");
}
// Free and release from two different tasks with cross task dangling pointer
// detection enabled.
TEST(PartitionAllocDanglingPtrChecks, CrossTask) {
BASE_EXPECT_DEATH(
{
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
.type = "cross_task",
});
base::test::TaskEnvironment task_environment;
task_environment.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(partition_alloc::GetDanglingRawPtrDetectedFn(), 42));
task_environment.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(partition_alloc::GetDanglingRawPtrReleasedFn(), 42));
task_environment.RunUntilIdle();
},
AllOf(HasSubstr("[DanglingSignature]\t"),
HasSubstr("[DanglingPtr](1/3) A raw_ptr/raw_ref is dangling."),
HasSubstr("[DanglingPtr](2/3) First, the memory was freed at:"),
HasSubstr("[DanglingPtr](3/3) Later, the dangling raw_ptr was "
"released at:")));
}
TEST(PartitionAllocDanglingPtrChecks, CrossTaskIgnoredFailuresClearsCache) {
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
.type = "cross_task",
});
base::test::TaskEnvironment task_environment;
partition_alloc::GetDanglingRawPtrDetectedFn()(42);
partition_alloc::GetDanglingRawPtrReleasedFn()(42);
task_environment.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(partition_alloc::GetDanglingRawPtrReleasedFn(), 42));
task_environment.RunUntilIdle();
}
TEST(PartitionAllocDanglingPtrChecks, CrossTaskIgnoresNoTask) {
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
.type = "cross_task",
});
partition_alloc::GetDanglingRawPtrDetectedFn()(42);
partition_alloc::GetDanglingRawPtrReleasedFn()(42);
}
TEST(PartitionAllocDanglingPtrChecks, CrossTaskIgnoresSameTask) {
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
.type = "cross_task",
});
base::test::TaskEnvironment task_environment;
task_environment.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce([] {
partition_alloc::GetDanglingRawPtrDetectedFn()(37);
partition_alloc::GetDanglingRawPtrReleasedFn()(37);
}));
task_environment.RunUntilIdle();
}
TEST(PartitionAllocDanglingPtrChecks, CrossTaskNoFreeConsideredCrossTask) {
ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
.type = "cross_task",
});
partition_alloc::GetDanglingRawPtrReleasedFn()(42);
}
TEST(PartitionAllocDanglingPtrChecks,
ExtractDanglingPtrSignatureMacStackTrace) {
const std::string stack_trace_output =
"0 lib_1 0x0000000115fdfa12 base::F1(**) + 18\r\n"
"1 lib_1 0x0000000115ec0043 base::F2() + 19\r\n"
"2 lib_1 0x000000011601fb01 "
"allocator_shim::internal::PartitionFree(foo) + 13265\r\n"
"3 lib_1 0x0000000114831027 base::F3(bar) + 42\r\n"
"4 lib_2 0x00000001148eae35 base::F4() + 437\r\n";
EXPECT_EQ("base::F3(bar)",
PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
stack_trace_output));
}
TEST(PartitionAllocDanglingPtrChecks, ExtractDanglingPtrSignatureMacTaskTrace) {
const std::string task_trace_output =
"Task trace:\r\n"
"0 lib_1 0x00000001161fd431 base::F1() + 257\r\n"
"1 lib_1 0x0000000115a49404 base::F2() + 68\r\n";
EXPECT_EQ("base::F1()",
PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
task_trace_output));
}
TEST(PartitionAllocDanglingPtrChecks,
ExtractDanglingPtrSignatureWindowsStackTrace) {
const std::string stack_trace_output =
"\tbase::F1 [0x055643C3+19] (o:\\base\\F1.cc:329)\r\n"
"\tallocator_shim::internal::PartitionFree [0x0648F87B+5243] "
"(o:\\path.cc:441)\r\n"
"\t_free_base [0x0558475D+29] (o:\\file_path.cc:142)\r\n"
"\tbase::F2 [0x04E5B317+23] (o:\\base\\F2.cc:91)\r\n"
"\tbase::F3 [0x04897800+544] (o:\\base\\F3.cc:638)\r\n";
EXPECT_EQ("base::F2",
PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
stack_trace_output));
}
TEST(PartitionAllocDanglingPtrChecks,
ExtractDanglingPtrSignatureWindowsTaskTrace) {
const std::string task_trace_output =
"Task trace:\r\n"
"\tbase::F1 [0x049068A3+813] (o:\\base\\F1.cc:207)\r\n"
"\tbase::F2 [0x0490614C+192] (o:\\base\\F2.cc:116)\r\n";
EXPECT_EQ("base::F1",
PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
task_trace_output));
}
#endif
#if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
TEST(PartitionAllocSupportTest,
ProposeSyntheticFinchTrials_MemoryTaggingDogfood) {
{
test::ScopedFeatureList scope;
scope.InitWithFeatures({}, {features::kPartitionAllocMemoryTagging});
auto trials = ProposeSyntheticFinchTrials();
auto group_iter = trials.find("MemoryTaggingDogfood");
EXPECT_EQ(group_iter, trials.end());
}
{
test::ScopedFeatureList scope;
scope.InitWithFeatures({features::kPartitionAllocMemoryTagging}, {});
auto trials = ProposeSyntheticFinchTrials();
std::string expectation =
partition_alloc::internal::base::CPU::GetInstanceNoAllocation()
.has_mte()
? "Enabled"
: "Disabled";
auto group_iter = trials.find("MemoryTaggingDogfood");
EXPECT_NE(group_iter, trials.end());
EXPECT_EQ(group_iter->second, expectation);
}
}
#endif // PA_BUILDFLAG(HAS_MEMORY_TAGGING)
class MemoryReclaimerSupportTest : public ::testing::Test {
public:
void SetUp() override {
MemoryReclaimerSupport::Instance().ResetForTesting();
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
test::ScopedFeatureList feature_list_;
};
TEST_F(MemoryReclaimerSupportTest, StartSeveralTimes) {
test::ScopedFeatureList feature_list{
base::features::kPartitionAllocMemoryReclaimer};
auto& instance = MemoryReclaimerSupport::Instance();
EXPECT_FALSE(instance.has_pending_task_for_testing());
instance.Start(task_environment_.GetMainThreadTaskRunner());
instance.Start(task_environment_.GetMainThreadTaskRunner());
instance.Start(task_environment_.GetMainThreadTaskRunner());
// Only one task.
EXPECT_TRUE(instance.has_pending_task_for_testing());
EXPECT_EQ(1u, task_environment_.GetPendingMainThreadTaskCount());
}
TEST_F(MemoryReclaimerSupportTest, ForegroundToBackground) {
test::ScopedFeatureList feature_list{
base::features::kPartitionAllocMemoryReclaimer};
auto& instance = MemoryReclaimerSupport::Instance();
EXPECT_FALSE(instance.has_pending_task_for_testing());
instance.Start(task_environment_.GetMainThreadTaskRunner());
EXPECT_TRUE(instance.has_pending_task_for_testing());
EXPECT_EQ(1u, task_environment_.GetPendingMainThreadTaskCount());
task_environment_.FastForwardBy(
MemoryReclaimerSupport::kFirstPAPurgeOrReclaimDelay);
// Task gets reposted.
EXPECT_TRUE(instance.has_pending_task_for_testing());
EXPECT_EQ(1u, task_environment_.GetPendingMainThreadTaskCount());
instance.SetForegrounded(false);
task_environment_.FastForwardBy(MemoryReclaimerSupport::GetInterval());
// But not once in background.
EXPECT_FALSE(instance.has_pending_task_for_testing());
EXPECT_EQ(0u, task_environment_.GetPendingMainThreadTaskCount());
}
TEST_F(MemoryReclaimerSupportTest, ForegroundToBackgroundAndBack) {
test::ScopedFeatureList feature_list{
base::features::kPartitionAllocMemoryReclaimer};
auto& instance = MemoryReclaimerSupport::Instance();
instance.Start(task_environment_.GetMainThreadTaskRunner());
task_environment_.FastForwardBy(
MemoryReclaimerSupport::kFirstPAPurgeOrReclaimDelay);
// Task gets reposted.
EXPECT_TRUE(instance.has_pending_task_for_testing());
EXPECT_EQ(1u, task_environment_.GetPendingMainThreadTaskCount());
instance.SetForegrounded(false);
task_environment_.FastForwardBy(MemoryReclaimerSupport::GetInterval());
// But not once in background.
EXPECT_FALSE(instance.has_pending_task_for_testing());
EXPECT_EQ(0u, task_environment_.GetPendingMainThreadTaskCount());
// Until we go to foreground again.
instance.SetForegrounded(true);
EXPECT_TRUE(instance.has_pending_task_for_testing());
EXPECT_EQ(1u, task_environment_.GetPendingMainThreadTaskCount());
}
} // namespace base::allocator