1 // Copyright 2019 The Chromium Authors. All rights reserved.
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/mac/scoped_mach_vm.h"
6 
7 #include <mach/mach.h>
8 
9 #include "base/test/gtest_util.h"
10 #include "testing/gtest/include/gtest/gtest.h"
11 
12 // Note: This test CANNOT be run multiple times within the same process (e.g.
13 // with --gtest_repeat). Allocating and deallocating in quick succession, even
14 // with different sizes, will typically result in the kernel returning the same
15 // address. If the allocation pattern is small->large->small, the second small
16 // allocation will report being part of the previously-deallocated large region.
17 // That will cause the GetRegionInfo() expectations to fail.
18 
19 namespace base {
20 namespace mac {
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 = PAGE_SIZE;
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.
45   vm_address_t region_address = address;
46   vm_size_t region_size;
47   GetRegionInfo(&region_address, &region_size);
48   EXPECT_EQ(KERN_SUCCESS, kr);
49   EXPECT_EQ(address, region_address);
50   EXPECT_EQ(1u * PAGE_SIZE, region_size);
51 
52   {
53     ScopedMachVM scoper2;
54     EXPECT_EQ(0u, scoper2.address());
55     EXPECT_EQ(0u, scoper2.size());
56 
57     scoper.swap(scoper2);
58 
59     EXPECT_EQ(address, scoper2.address());
60     EXPECT_EQ(size, scoper2.size());
61 
62     EXPECT_EQ(0u, scoper.address());
63     EXPECT_EQ(0u, scoper.size());
64   }
65 
66   // After deallocation, the kernel will return the next highest address.
67   region_address = address;
68   GetRegionInfo(&region_address, &region_size);
69   EXPECT_EQ(KERN_SUCCESS, kr);
70   EXPECT_LT(address, region_address);
71 }
72 
TEST(ScopedMachVMTest,Reset)73 TEST(ScopedMachVMTest, Reset) {
74   vm_address_t address;
75   vm_size_t size = PAGE_SIZE;
76   kern_return_t kr =
77       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
78   ASSERT_EQ(KERN_SUCCESS, kr);
79 
80   ScopedMachVM scoper(address, size);
81 
82   // Test the initial region.
83   vm_address_t region_address = address;
84   vm_size_t region_size;
85   GetRegionInfo(&region_address, &region_size);
86   EXPECT_EQ(KERN_SUCCESS, kr);
87   EXPECT_EQ(address, region_address);
88   EXPECT_EQ(1u * PAGE_SIZE, region_size);
89 
90   scoper.reset();
91 
92   // After deallocation, the kernel will return the next highest address.
93   region_address = address;
94   GetRegionInfo(&region_address, &region_size);
95   EXPECT_EQ(KERN_SUCCESS, kr);
96   EXPECT_LT(address, region_address);
97 }
98 
TEST(ScopedMachVMTest,ResetSmallerAddress)99 TEST(ScopedMachVMTest, ResetSmallerAddress) {
100   vm_address_t address;
101   vm_size_t size = 2 * PAGE_SIZE;
102   kern_return_t kr =
103       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
104   ASSERT_EQ(KERN_SUCCESS, kr);
105 
106   ScopedMachVM scoper(address, PAGE_SIZE);
107 
108   // Test the initial region.
109   vm_address_t region_address = address;
110   vm_size_t region_size;
111   GetRegionInfo(&region_address, &region_size);
112   EXPECT_EQ(KERN_SUCCESS, kr);
113   EXPECT_EQ(address, region_address);
114   EXPECT_EQ(2u * PAGE_SIZE, region_size);
115 
116   // This will free address..PAGE_SIZE that is currently in the scoper.
117   scoper.reset(address + PAGE_SIZE, PAGE_SIZE);
118 
119   // Verify that the region is now only one page.
120   region_address = address;
121   GetRegionInfo(&region_address, &region_size);
122   EXPECT_EQ(address + PAGE_SIZE, region_address);
123   EXPECT_EQ(1u * PAGE_SIZE, region_size);
124 }
125 
TEST(ScopedMachVMTest,ResetLargerAddressAndSize)126 TEST(ScopedMachVMTest, ResetLargerAddressAndSize) {
127   vm_address_t address;
128   vm_size_t size = 3 * PAGE_SIZE;
129   kern_return_t kr =
130       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
131   ASSERT_EQ(KERN_SUCCESS, kr);
132 
133   // Test the initial region.
134   vm_address_t region_address = address;
135   vm_size_t region_size;
136   GetRegionInfo(&region_address, &region_size);
137   EXPECT_EQ(KERN_SUCCESS, kr);
138   EXPECT_EQ(address, region_address);
139   EXPECT_EQ(3u * PAGE_SIZE, region_size);
140 
141   ScopedMachVM scoper(address + 2 * PAGE_SIZE, PAGE_SIZE);
142   // Expand the region to be larger.
143   scoper.reset(address, size);
144 
145   // Verify that the region is still three pages.
146   region_address = address;
147   GetRegionInfo(&region_address, &region_size);
148   EXPECT_EQ(address, region_address);
149   EXPECT_EQ(3u * PAGE_SIZE, region_size);
150 }
151 
TEST(ScopedMachVMTest,ResetLargerAddress)152 TEST(ScopedMachVMTest, ResetLargerAddress) {
153   vm_address_t address;
154   vm_size_t size = 6 * PAGE_SIZE;
155   kern_return_t kr =
156       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
157   ASSERT_EQ(KERN_SUCCESS, kr);
158 
159   // Test the initial region.
160   vm_address_t region_address = address;
161   vm_size_t region_size;
162   GetRegionInfo(&region_address, &region_size);
163   EXPECT_EQ(KERN_SUCCESS, kr);
164   EXPECT_EQ(address, region_address);
165   EXPECT_EQ(6u * PAGE_SIZE, region_size);
166 
167   ScopedMachVM scoper(address + 3 * PAGE_SIZE, 3 * PAGE_SIZE);
168 
169   // Shift the region by three pages; the last three pages should be
170   // deallocated, while keeping the first three.
171   scoper.reset(address, 3 * PAGE_SIZE);
172 
173   // Verify that the region is just three pages.
174   region_address = address;
175   GetRegionInfo(&region_address, &region_size);
176   EXPECT_EQ(address, region_address);
177   EXPECT_EQ(3u * PAGE_SIZE, region_size);
178 }
179 
TEST(ScopedMachVMTest,ResetUnaligned)180 TEST(ScopedMachVMTest, ResetUnaligned) {
181   vm_address_t address;
182   vm_size_t size = 2 * PAGE_SIZE;
183   kern_return_t kr =
184       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
185   ASSERT_EQ(KERN_SUCCESS, kr);
186 
187   ScopedMachVM scoper;
188 
189   // Test the initial region.
190   vm_address_t region_address = address;
191   vm_size_t region_size;
192   GetRegionInfo(&region_address, &region_size);
193   EXPECT_EQ(address, region_address);
194   EXPECT_EQ(2u * PAGE_SIZE, region_size);
195 
196   // Initialize with unaligned size.
197   scoper.reset_unaligned(address + PAGE_SIZE, PAGE_SIZE - 3);
198   // Reset with another unaligned size.
199   scoper.reset_unaligned(address + PAGE_SIZE, PAGE_SIZE - 11);
200 
201   // The entire unaligned page gets deallocated.
202   region_address = address;
203   GetRegionInfo(&region_address, &region_size);
204   EXPECT_EQ(address, region_address);
205   EXPECT_EQ(1u * PAGE_SIZE, region_size);
206 
207   // Reset with the remaining page.
208   scoper.reset_unaligned(address, PAGE_SIZE);
209 }
210 
211 #if DCHECK_IS_ON()
212 
TEST(ScopedMachVMTest,ResetMustBeAligned)213 TEST(ScopedMachVMTest, ResetMustBeAligned) {
214   vm_address_t address;
215   vm_size_t size = 2 * PAGE_SIZE;
216   kern_return_t kr =
217       vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
218   ASSERT_EQ(KERN_SUCCESS, kr);
219 
220   ScopedMachVM scoper;
221   EXPECT_DCHECK_DEATH(scoper.reset(address, PAGE_SIZE + 1));
222 }
223 
224 #endif  // DCHECK_IS_ON()
225 
226 }  // namespace
227 }  // namespace mac
228 }  // namespace base
229