xref: /aosp_15_r20/external/cronet/base/apple/scoped_mach_vm_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/apple/scoped_mach_vm.h"
6 
7 #include <mach/mach.h>
8 
9 #include "base/memory/page_size.h"
10 #include "base/test/gtest_util.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 
13 // Note: This test CANNOT be run multiple times within the same process (e.g.
14 // with --gtest_repeat). Allocating and deallocating in quick succession, even
15 // with different sizes, will typically result in the kernel returning the same
16 // address. If the allocation pattern is small->large->small, the second small
17 // allocation will report being part of the previously-deallocated large region.
18 // That will cause the GetRegionInfo() expectations to fail.
19 
20 namespace base::apple {
21 namespace {
22 
GetRegionInfo(vm_address_t * region_address,vm_size_t * region_size)23 void GetRegionInfo(vm_address_t* region_address, vm_size_t* region_size) {
24   vm_region_basic_info_64 region_info;
25   mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
26   mach_port_t object;
27   kern_return_t kr = vm_region_64(
28       mach_task_self(), region_address, region_size, VM_REGION_BASIC_INFO_64,
29       reinterpret_cast<vm_region_info_t>(&region_info), &count, &object);
30   EXPECT_EQ(KERN_SUCCESS, kr);
31 }
32 
TEST(ScopedMachVMTest,Basic)33 TEST(ScopedMachVMTest, Basic) {
34   vm_address_t address;
35   vm_size_t size = base::GetPageSize();
36   kern_return_t kr =
37       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
38   ASSERT_EQ(KERN_SUCCESS, kr);
39 
40   ScopedMachVM scoper(address, size);
41   EXPECT_EQ(address, scoper.address());
42   EXPECT_EQ(size, scoper.size());
43 
44   // Test the initial region. In some cases on some platforms (macOS 13 on
45   // Intel, for example), Darwin may combine the requested allocation with
46   // an existing one. As a result, the allocated region may live in a
47   // larger region. Therefore, when we GetRegionInfo(), we want to check
48   // that our original region is a subset of (region_address, region_size)
49   // rather than being exactly equal to it.
50   vm_address_t region_address = address;
51   vm_size_t region_size;
52   GetRegionInfo(&region_address, &region_size);
53   EXPECT_EQ(KERN_SUCCESS, kr);
54   EXPECT_GE(address, region_address);
55   EXPECT_LE(address + size, region_address + region_size);
56 
57   {
58     ScopedMachVM scoper2;
59     EXPECT_EQ(0u, scoper2.address());
60     EXPECT_EQ(0u, scoper2.size());
61 
62     scoper.swap(scoper2);
63 
64     EXPECT_EQ(address, scoper2.address());
65     EXPECT_EQ(size, scoper2.size());
66 
67     EXPECT_EQ(0u, scoper.address());
68     EXPECT_EQ(0u, scoper.size());
69   }
70 
71   // After deallocation, the kernel will return the next highest address.
72   region_address = address;
73   GetRegionInfo(&region_address, &region_size);
74   EXPECT_EQ(KERN_SUCCESS, kr);
75   EXPECT_LT(address, region_address);
76 }
77 
TEST(ScopedMachVMTest,Reset)78 TEST(ScopedMachVMTest, Reset) {
79   vm_address_t address;
80   vm_size_t size = base::GetPageSize();
81   kern_return_t kr =
82       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
83   ASSERT_EQ(KERN_SUCCESS, kr);
84 
85   ScopedMachVM scoper(address, size);
86 
87   // Test the initial region.
88   vm_address_t region_address = address;
89   vm_size_t region_size;
90   GetRegionInfo(&region_address, &region_size);
91   EXPECT_EQ(KERN_SUCCESS, kr);
92   EXPECT_GE(address, region_address);
93   EXPECT_LE(address + size, region_address + region_size);
94 
95   scoper.reset();
96 
97   // After deallocation, the kernel will return the next highest address.
98   region_address = address;
99   GetRegionInfo(&region_address, &region_size);
100   EXPECT_EQ(KERN_SUCCESS, kr);
101   EXPECT_LT(address, region_address);
102 }
103 
TEST(ScopedMachVMTest,ResetSmallerAddress)104 TEST(ScopedMachVMTest, ResetSmallerAddress) {
105   vm_address_t address;
106   vm_size_t size = 2 * base::GetPageSize();
107   kern_return_t kr =
108       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
109   ASSERT_EQ(KERN_SUCCESS, kr);
110 
111   ScopedMachVM scoper(address, base::GetPageSize());
112 
113   // Test the initial region.
114   vm_address_t region_address = address;
115   vm_size_t region_size;
116   GetRegionInfo(&region_address, &region_size);
117   EXPECT_EQ(KERN_SUCCESS, kr);
118   EXPECT_EQ(address, region_address);
119   EXPECT_EQ(2u * base::GetPageSize(), region_size);
120 
121   // This will free address..base::GetPageSize() that is currently in the
122   // scoper.
123   scoper.reset(address + base::GetPageSize(), base::GetPageSize());
124 
125   // Verify that the region is now only one page.
126   region_address = address;
127   GetRegionInfo(&region_address, &region_size);
128   EXPECT_EQ(address + base::GetPageSize(), region_address);
129   EXPECT_EQ(1u * base::GetPageSize(), region_size);
130 }
131 
TEST(ScopedMachVMTest,ResetLargerAddressAndSize)132 TEST(ScopedMachVMTest, ResetLargerAddressAndSize) {
133   const vm_size_t kOnePage = base::GetPageSize();
134   const vm_size_t kTwoPages = 2 * kOnePage;
135   const vm_size_t kThreePages = 3 * kOnePage;
136 
137   vm_address_t address;
138   kern_return_t kr =
139       vm_allocate(mach_task_self(), &address, kThreePages, VM_FLAGS_ANYWHERE);
140   ASSERT_EQ(KERN_SUCCESS, kr);
141 
142   // Test the initial region.
143   vm_address_t region_address = address;
144   vm_size_t region_size;
145   GetRegionInfo(&region_address, &region_size);
146   EXPECT_EQ(KERN_SUCCESS, kr);
147   EXPECT_GE(address, region_address);
148   EXPECT_LE(address + kThreePages, region_address + region_size);
149 
150   ScopedMachVM scoper(address + kTwoPages, kOnePage);
151   // Expand the region to be larger.
152   scoper.reset(address, kThreePages);
153 
154   // Verify that the region is still three pages.
155   region_address = address;
156   GetRegionInfo(&region_address, &region_size);
157   EXPECT_GE(address, region_address);
158   EXPECT_LE(address + kThreePages, region_address + region_size);
159 }
160 
TEST(ScopedMachVMTest,ResetLargerAddress)161 TEST(ScopedMachVMTest, ResetLargerAddress) {
162   const vm_size_t kThreePages = 3 * base::GetPageSize();
163   const vm_size_t kSixPages = 2 * kThreePages;
164 
165   vm_address_t address;
166   kern_return_t kr =
167       vm_allocate(mach_task_self(), &address, kSixPages, VM_FLAGS_ANYWHERE);
168   ASSERT_EQ(KERN_SUCCESS, kr);
169 
170   // Test the initial region.
171   vm_address_t region_address = address;
172   vm_size_t region_size;
173   GetRegionInfo(&region_address, &region_size);
174   EXPECT_EQ(KERN_SUCCESS, kr);
175   EXPECT_GE(address, region_address);
176   EXPECT_LE(address + kSixPages, region_address + region_size);
177 
178   ScopedMachVM scoper(address + kThreePages, kThreePages);
179 
180   // Shift the region by three pages; the last three pages should be
181   // deallocated, while keeping the first three.
182   scoper.reset(address, kThreePages);
183 
184   // Verify that the region is just three pages.
185   region_address = address;
186   GetRegionInfo(&region_address, &region_size);
187   EXPECT_GE(address, region_address);
188   EXPECT_LE(address + kThreePages, region_address + region_size);
189 }
190 
TEST(ScopedMachVMTest,ResetUnaligned)191 TEST(ScopedMachVMTest, ResetUnaligned) {
192   const vm_size_t kOnePage = base::GetPageSize();
193   const vm_size_t kTwoPages = 2 * kOnePage;
194 
195   vm_address_t address;
196   kern_return_t kr =
197       vm_allocate(mach_task_self(), &address, kTwoPages, VM_FLAGS_ANYWHERE);
198   ASSERT_EQ(KERN_SUCCESS, kr);
199 
200   ScopedMachVM scoper;
201 
202   // Test the initial region.
203   vm_address_t region_address = address;
204   vm_size_t region_size;
205   GetRegionInfo(&region_address, &region_size);
206   EXPECT_GE(address, region_address);
207   EXPECT_LE(address + kTwoPages, region_address + region_size);
208 
209   // Initialize with unaligned size.
210   scoper.reset_unaligned(address + kOnePage, kOnePage - 3);
211   // Reset with another unaligned size.
212   scoper.reset_unaligned(address + kOnePage, kOnePage - 11);
213 
214   // The entire unaligned page gets deallocated.
215   region_address = address;
216   GetRegionInfo(&region_address, &region_size);
217   EXPECT_GE(address, region_address);
218   EXPECT_LE(address + kOnePage, region_address + region_size);
219 
220   // Reset with the remaining page.
221   scoper.reset_unaligned(address, base::GetPageSize());
222 }
223 
224 #if DCHECK_IS_ON()
225 
TEST(ScopedMachVMTest,ResetMustBeAligned)226 TEST(ScopedMachVMTest, ResetMustBeAligned) {
227   const vm_size_t kOnePage = base::GetPageSize();
228   const vm_size_t kTwoPages = 2 * kOnePage;
229 
230   vm_address_t address;
231   kern_return_t kr =
232       vm_allocate(mach_task_self(), &address, kTwoPages, VM_FLAGS_ANYWHERE);
233   ASSERT_EQ(KERN_SUCCESS, kr);
234 
235   ScopedMachVM scoper;
236   EXPECT_DCHECK_DEATH(scoper.reset(address, kOnePage + 1));
237 }
238 
239 #endif  // DCHECK_IS_ON()
240 
241 }  // namespace
242 }  // namespace base::apple
243