1use crate::{os, util, Error, Region, Result};
2
3pub struct QueryIter {
7 iterator: Option<os::QueryIter>,
8 origin: *const (),
9}
10
11impl QueryIter {
12 pub(crate) fn new<T>(origin: *const T, size: usize) -> Result<Self> {
13 let origin = origin.cast();
14
15 os::QueryIter::new(origin, size).map(|iterator| Self {
16 iterator: Some(iterator),
17 origin,
18 })
19 }
20}
21
22impl Iterator for QueryIter {
23 type Item = Result<Region>;
24
25 #[allow(clippy::missing_inline_in_public_items)]
32 fn next(&mut self) -> Option<Self::Item> {
33 let regions = self.iterator.as_mut()?;
34
35 while let Some(result) = regions.next() {
36 match result {
37 Ok(region) => {
38 let range = region.as_range();
39
40 if range.end <= self.origin as usize {
42 continue;
43 }
44
45 if range.start >= regions.upper_bound() {
47 break;
48 }
49
50 return Some(Ok(region));
51 }
52 Err(error) => {
53 self.iterator.take();
54 return Some(Err(error));
55 }
56 }
57 }
58
59 self.iterator.take();
60 None
61 }
62}
63
64impl std::iter::FusedIterator for QueryIter {}
65
66unsafe impl Send for QueryIter {}
67unsafe impl Sync for QueryIter {}
68
69#[inline]
99pub fn query<T>(address: *const T) -> Result<Region> {
100 let (address, size) = util::round_to_page_boundaries(address, 1)?;
102
103 QueryIter::new(address, size)?
104 .next()
105 .ok_or(Error::UnmappedRegion)?
106}
107
108#[inline]
154pub fn query_range<T>(address: *const T, size: usize) -> Result<QueryIter> {
155 let (address, size) = util::round_to_page_boundaries(address, size)?;
156 QueryIter::new(address, size)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::tests::util::alloc_pages;
163 use crate::{page, Protection};
164
165 const TEXT_SEGMENT_PROT: Protection = if cfg!(target_os = "openbsd") {
166 Protection::EXECUTE
167 } else {
168 Protection::READ_EXECUTE
169 };
170
171 #[test]
172 fn query_returns_unmapped_for_oob_address() {
173 let (min, max) = (std::ptr::null::<()>(), usize::max_value() as *const ());
174 assert!(matches!(query(min), Err(Error::UnmappedRegion)));
175 assert!(matches!(query(max), Err(Error::UnmappedRegion)));
176 }
177
178 #[test]
179 fn query_returns_correct_descriptor_for_text_segment() -> Result<()> {
180 let region = query(query_returns_correct_descriptor_for_text_segment as *const ())?;
181
182 assert_eq!(region.protection(), TEXT_SEGMENT_PROT);
183 assert_eq!(region.is_shared(), cfg!(windows));
184 assert!(!region.is_guarded());
185 Ok(())
186 }
187
188 #[test]
189 fn query_returns_one_region_for_multiple_page_allocation() -> Result<()> {
190 let alloc = crate::alloc(page::size() + 1, Protection::READ_EXECUTE)?;
191 let region = query(alloc.as_ptr::<()>())?;
192
193 assert_eq!(region.protection(), Protection::READ_EXECUTE);
194 assert_eq!(region.as_ptr::<()>(), alloc.as_ptr());
195 assert_eq!(region.len(), alloc.len());
196 assert!(!region.is_guarded());
197 Ok(())
198 }
199
200 #[test]
201 #[cfg(not(target_os = "android"))] fn query_is_not_off_by_one() -> Result<()> {
203 let pages = [Protection::READ, Protection::READ_EXECUTE, Protection::READ];
204 let map = alloc_pages(&pages);
205
206 let page_mid = unsafe { map.as_ptr().add(page::size()) };
207 let region = query(page_mid)?;
208
209 assert_eq!(region.protection(), Protection::READ_EXECUTE);
210 assert_eq!(region.len(), page::size());
211
212 let region = query(unsafe { page_mid.offset(-1) })?;
213
214 assert_eq!(region.protection(), Protection::READ);
215 assert_eq!(region.len(), page::size());
216 Ok(())
217 }
218
219 #[test]
220 fn query_range_does_not_return_unmapped_regions() -> Result<()> {
221 let regions = query_range(std::ptr::null::<()>(), 1)?.collect::<Result<Vec<_>>>()?;
222 assert!(regions.is_empty());
223 Ok(())
224 }
225
226 #[test]
227 fn query_range_returns_both_regions_for_straddling_range() -> Result<()> {
228 let pages = [Protection::READ_EXECUTE, Protection::READ_WRITE];
229 let map = alloc_pages(&pages);
230
231 let address = unsafe { map.as_ptr().offset(page::size() as isize - 1) };
233 let regions = query_range(address, 2)?.collect::<Result<Vec<_>>>()?;
234
235 assert_eq!(regions.len(), pages.len());
236 for (page, region) in pages.iter().zip(regions.iter()) {
237 assert_eq!(*page, region.protection);
238 }
239 Ok(())
240 }
241
242 #[test]
243 fn query_range_has_inclusive_lower_and_exclusive_upper_bound() -> Result<()> {
244 let pages = [Protection::READ, Protection::READ_WRITE, Protection::READ];
245 let map = alloc_pages(&pages);
246
247 let regions = query_range(map.as_ptr(), page::size())?.collect::<Result<Vec<_>>>()?;
248 assert_eq!(regions.len(), 1);
249 assert_eq!(regions[0].protection(), Protection::READ);
250
251 let regions = query_range(map.as_ptr(), page::size() + 1)?.collect::<Result<Vec<_>>>()?;
252 assert_eq!(regions.len(), 2);
253 assert_eq!(regions[0].protection(), Protection::READ);
254 assert_eq!(regions[1].protection(), Protection::READ_WRITE);
255 Ok(())
256 }
257
258 #[test]
259 fn query_range_can_iterate_over_entire_process() -> Result<()> {
260 let regions =
261 query_range(std::ptr::null::<()>(), usize::max_value())?.collect::<Result<Vec<_>>>()?;
262
263 assert!(regions
265 .iter()
266 .any(|region| region.protection() == Protection::READ));
267 assert!(regions
268 .iter()
269 .any(|region| region.protection() == Protection::READ_WRITE));
270 assert!(regions
271 .iter()
272 .any(|region| region.protection() == TEXT_SEGMENT_PROT));
273 assert!(regions.len() > 5);
274 Ok(())
275 }
276
277 #[test]
278 fn query_range_iterator_is_fused_after_exhaustion() -> Result<()> {
279 let pages = [Protection::READ, Protection::READ_WRITE];
280 let map = alloc_pages(&pages);
281 let mut iter = query_range(map.as_ptr(), page::size() + 1)?;
282
283 assert_eq!(
284 iter.next().transpose()?.map(|r| r.protection()),
285 Some(Protection::READ)
286 );
287 assert_eq!(
288 iter.next().transpose()?.map(|r| r.protection()),
289 Some(Protection::READ_WRITE)
290 );
291 assert_eq!(iter.next().transpose()?, None);
292 assert_eq!(iter.next().transpose()?, None);
293 Ok(())
294 }
295}