1 #[macro_export]
2 macro_rules! alloc_tests {
3     ( $TestRegion:path ) => {
4         use libc::c_void;
5         use rand::rngs::StdRng;
6         use rand::{thread_rng, Rng, SeedableRng};
7         use std::sync::{Arc, Mutex};
8         use $TestRegion as TestRegion;
9         use $crate::alloc::{AllocStrategy, Limits, MINSIGSTKSZ};
10         use $crate::context::{Context, ContextHandle};
11         use $crate::error::Error;
12         use $crate::instance::InstanceInternal;
13         use $crate::module::{
14             FunctionPointer, GlobalValue, HeapSpec, MockExportBuilder, MockModuleBuilder, Module,
15         };
16         use $crate::region::{Region, RegionCreate};
17         use $crate::sysdeps::host_page_size;
18         use $crate::val::Val;
19         use $crate::vmctx::lucet_vmctx;
20 
21         const LIMITS_HEAP_MEM_SIZE: usize = 16 * 64 * 1024;
22         const LIMITS_HEAP_ADDRSPACE_SIZE: usize = 8 * 1024 * 1024;
23         const LIMITS_STACK_SIZE: usize = 64 * 1024;
24         const LIMITS_GLOBALS_SIZE: usize = 4 * 1024;
25 
26         const LIMITS: Limits = Limits {
27             heap_memory_size: LIMITS_HEAP_MEM_SIZE,
28             heap_address_space_size: LIMITS_HEAP_ADDRSPACE_SIZE,
29             stack_size: LIMITS_STACK_SIZE,
30             globals_size: LIMITS_GLOBALS_SIZE,
31             ..Limits::default()
32         };
33 
34         const SPEC_HEAP_RESERVED_SIZE: u64 = LIMITS_HEAP_ADDRSPACE_SIZE as u64 / 2;
35         const SPEC_HEAP_GUARD_SIZE: u64 = LIMITS_HEAP_ADDRSPACE_SIZE as u64 / 2;
36 
37         // one wasm page, not host page
38         const ONEPAGE_INITIAL_SIZE: u64 = 64 * 1024;
39         const ONEPAGE_MAX_SIZE: u64 = 64 * 1024;
40 
41         const ONE_PAGE_HEAP: HeapSpec = HeapSpec {
42             reserved_size: SPEC_HEAP_RESERVED_SIZE,
43             guard_size: SPEC_HEAP_GUARD_SIZE,
44             initial_size: ONEPAGE_INITIAL_SIZE,
45             max_size: Some(ONEPAGE_MAX_SIZE),
46         };
47 
48         const THREEPAGE_INITIAL_SIZE: u64 = 64 * 1024;
49         const THREEPAGE_MAX_SIZE: u64 = 3 * 64 * 1024;
50 
51         const THREE_PAGE_MAX_HEAP: HeapSpec = HeapSpec {
52             reserved_size: SPEC_HEAP_RESERVED_SIZE,
53             guard_size: 0,
54             initial_size: THREEPAGE_INITIAL_SIZE,
55             max_size: Some(THREEPAGE_MAX_SIZE),
56         };
57 
58         /// This test shows an `AllocHandle` passed to `Region::allocate_runtime` will have its heap
59         /// and stack of the correct size and read/writability.
60         #[test]
61         fn allocate_runtime_works() {
62             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
63             let mut inst = region
64                 .new_instance(
65                     MockModuleBuilder::new()
66                         .with_heap_spec(ONE_PAGE_HEAP)
67                         .build(),
68                 )
69                 .expect("new_instance succeeds");
70 
71             let heap_len = inst.alloc().heap_len();
72             assert_eq!(heap_len, ONEPAGE_INITIAL_SIZE as usize);
73 
74             let heap = unsafe { inst.alloc_mut().heap_mut() };
75 
76             assert_eq!(heap[0], 0);
77             heap[0] = 0xFF;
78             assert_eq!(heap[0], 0xFF);
79 
80             assert_eq!(heap[heap_len - 1], 0);
81             heap[heap_len - 1] = 0xFF;
82             assert_eq!(heap[heap_len - 1], 0xFF);
83 
84             let stack = unsafe { inst.alloc_mut().stack_mut() };
85             assert_eq!(stack.len(), LIMITS_STACK_SIZE);
86 
87             assert_eq!(stack[0], 0);
88             stack[0] = 0xFF;
89             assert_eq!(stack[0], 0xFF);
90 
91             assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0);
92             stack[LIMITS_STACK_SIZE - 1] = 0xFF;
93             assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0xFF);
94         }
95 
96         /// This test shows the heap works properly after a single expand.
97         #[test]
98         fn expand_heap_once() {
99             expand_heap_once_template(THREE_PAGE_MAX_HEAP)
100         }
101 
102         fn expand_heap_once_template(heap_spec: HeapSpec) {
103             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
104             let module = MockModuleBuilder::new()
105                 .with_heap_spec(heap_spec.clone())
106                 .build();
107             let mut inst = region
108                 .new_instance(module.clone())
109                 .expect("new_instance succeeds");
110 
111             let heap_len = inst.alloc().heap_len();
112             assert_eq!(heap_len, heap_spec.initial_size as usize);
113 
114             let new_heap_area = inst
115                 .alloc_mut()
116                 .expand_heap(64 * 1024, module.as_ref())
117                 .expect("expand_heap succeeds");
118             assert_eq!(heap_len, new_heap_area as usize);
119 
120             let new_heap_len = inst.alloc().heap_len();
121             assert_eq!(new_heap_len, heap_len + (64 * 1024));
122 
123             let heap = unsafe { inst.alloc_mut().heap_mut() };
124             assert_eq!(heap[new_heap_len - 1], 0);
125             heap[new_heap_len - 1] = 0xFF;
126             assert_eq!(heap[new_heap_len - 1], 0xFF);
127         }
128 
129         /// This test shows the heap works properly after two expands.
130         #[test]
131         fn expand_heap_twice() {
132             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
133             let module = MockModuleBuilder::new()
134                 .with_heap_spec(THREE_PAGE_MAX_HEAP)
135                 .build();
136             let mut inst = region
137                 .new_instance(module.clone())
138                 .expect("new_instance succeeds");
139 
140             let heap_len = inst.alloc().heap_len();
141             assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
142 
143             let new_heap_area = inst
144                 .alloc_mut()
145                 .expand_heap(64 * 1024, module.as_ref())
146                 .expect("expand_heap succeeds");
147             assert_eq!(heap_len, new_heap_area as usize);
148 
149             let new_heap_len = inst.alloc().heap_len();
150             assert_eq!(new_heap_len, heap_len + (64 * 1024));
151 
152             let second_new_heap_area = inst
153                 .alloc_mut()
154                 .expand_heap(64 * 1024, module.as_ref())
155                 .expect("expand_heap succeeds");
156             assert_eq!(new_heap_len, second_new_heap_area as usize);
157 
158             let second_new_heap_len = inst.alloc().heap_len();
159             assert_eq!(second_new_heap_len as u64, THREEPAGE_MAX_SIZE);
160 
161             let heap = unsafe { inst.alloc_mut().heap_mut() };
162             assert_eq!(heap[new_heap_len - 1], 0);
163             heap[new_heap_len - 1] = 0xFF;
164             assert_eq!(heap[new_heap_len - 1], 0xFF);
165         }
166 
167         /// This test shows that if you try to expand past the max given by the heap spec, the
168         /// expansion fails, but the existing heap can still be used. This test uses a region with
169         /// multiple slots in order to exercise more edge cases with adjacent managed memory.
170         #[test]
171         fn expand_past_spec_max() {
172             let region = <TestRegion as RegionCreate>::create(10, &LIMITS).expect("region created");
173             let module = MockModuleBuilder::new()
174                 .with_heap_spec(THREE_PAGE_MAX_HEAP)
175                 .build();
176             let mut inst = region
177                 .new_instance(module.clone())
178                 .expect("new_instance succeeds");
179 
180             let heap_len = inst.alloc().heap_len();
181             assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
182 
183             let new_heap_area = inst
184                 .alloc_mut()
185                 .expand_heap(THREEPAGE_MAX_SIZE as u32, module.as_ref());
186             assert!(new_heap_area.is_err(), "heap expansion past spec fails");
187 
188             let new_heap_len = inst.alloc().heap_len();
189             assert_eq!(new_heap_len, heap_len);
190 
191             let heap = unsafe { inst.alloc_mut().heap_mut() };
192             assert_eq!(heap[new_heap_len - 1], 0);
193             heap[new_heap_len - 1] = 0xFF;
194             assert_eq!(heap[new_heap_len - 1], 0xFF);
195         }
196 
197         /// This test exercises custom limits on the heap_memory_size.
198         /// In this scenario, the Region has a limit on the heap memory
199         /// size, but the instance has a smaller limit. Attempts to expand
200         /// the heap fail, but the existing heap can still be used.
201         #[test]
202         fn expand_past_spec_max_with_custom_limit() {
203             let region = <TestRegion as RegionCreate>::create(10, &LIMITS).expect("region created");
204             let module = MockModuleBuilder::new()
205                 .with_heap_spec(THREE_PAGE_MAX_HEAP)
206                 .build();
207             let mut inst = region
208                 .new_instance_builder(module.clone())
209                 .with_heap_size_limit((THREEPAGE_INITIAL_SIZE) as usize)
210                 .build()
211                 .expect("new_instance succeeds");
212 
213             let heap_len = inst.alloc().heap_len();
214             assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
215 
216             // Expand the heap within the Region limit but past the
217             // instance limit.
218             let new_heap_area = inst.alloc_mut().expand_heap(
219                 (THREEPAGE_MAX_SIZE - THREEPAGE_INITIAL_SIZE) as u32,
220                 module.as_ref(),
221             );
222             assert!(
223                 new_heap_area.is_err(),
224                 "heap expansion past instance limit fails"
225             );
226 
227             // Affirm that the existing heap can still be used.
228             let new_heap_len = inst.alloc().heap_len();
229             assert_eq!(new_heap_len, heap_len);
230 
231             let heap = unsafe { inst.alloc_mut().heap_mut() };
232             assert_eq!(heap[new_heap_len - 1], 0);
233             heap[new_heap_len - 1] = 0xFF;
234             assert_eq!(heap[new_heap_len - 1], 0xFF);
235         }
236 
237         const EXPANDPASTLIMIT_INITIAL_SIZE: u64 = LIMITS_HEAP_MEM_SIZE as u64 - (64 * 1024);
238         const EXPANDPASTLIMIT_MAX_SIZE: u64 = LIMITS_HEAP_MEM_SIZE as u64 + (64 * 1024);
239         const EXPAND_PAST_LIMIT_SPEC: HeapSpec = HeapSpec {
240             reserved_size: SPEC_HEAP_RESERVED_SIZE,
241             guard_size: SPEC_HEAP_GUARD_SIZE,
242             initial_size: EXPANDPASTLIMIT_INITIAL_SIZE,
243             max_size: Some(EXPANDPASTLIMIT_MAX_SIZE),
244         };
245 
246         /// This test shows that a heap refuses to grow past the region's limits, even if the
247         /// runtime spec says it can grow bigger. This test uses a region with multiple slots in
248         /// order to exercise more edge cases with adjacent managed memory.
249         #[test]
250         fn expand_past_heap_limit() {
251             let region = <TestRegion as RegionCreate>::create(10, &LIMITS).expect("region created");
252             let module = MockModuleBuilder::new()
253                 .with_heap_spec(EXPAND_PAST_LIMIT_SPEC)
254                 .build();
255             let mut inst = region
256                 .new_instance(module.clone())
257                 .expect("new_instance succeeds");
258 
259             let heap_len = inst.alloc().heap_len();
260             assert_eq!(heap_len, EXPANDPASTLIMIT_INITIAL_SIZE as usize);
261 
262             let new_heap_area = inst
263                 .alloc_mut()
264                 .expand_heap(64 * 1024, module.as_ref())
265                 .expect("expand_heap succeeds");
266             assert_eq!(heap_len, new_heap_area as usize);
267 
268             let new_heap_len = inst.alloc().heap_len();
269             assert_eq!(new_heap_len, LIMITS_HEAP_MEM_SIZE);
270 
271             let past_limit_heap_area = inst.alloc_mut().expand_heap(64 * 1024, module.as_ref());
272             assert!(
273                 past_limit_heap_area.is_err(),
274                 "heap expansion past limit fails"
275             );
276 
277             let still_heap_len = inst.alloc().heap_len();
278             assert_eq!(still_heap_len, LIMITS_HEAP_MEM_SIZE);
279 
280             let heap = unsafe { inst.alloc_mut().heap_mut() };
281             assert_eq!(heap[new_heap_len - 1], 0);
282             heap[new_heap_len - 1] = 0xFF;
283             assert_eq!(heap[new_heap_len - 1], 0xFF);
284         }
285 
286         /// This test shows that a heap refuses to grow past the instance-specific heap limit, even
287         /// if both the region's limits and the runtime spec says it can grow bigger. This test uses
288         /// a region with multiple slots in order to exercise more edge cases with adjacent managed
289         /// memory.
290         #[test]
291         fn expand_past_instance_heap_limit() {
292             const INSTANCE_LIMIT: usize = LIMITS.heap_memory_size / 2;
293             const SPEC: HeapSpec = HeapSpec {
294                 initial_size: INSTANCE_LIMIT as u64 - 64 * 1024,
295                 ..EXPAND_PAST_LIMIT_SPEC
296             };
297             assert_eq!(INSTANCE_LIMIT % host_page_size(), 0);
298 
299             let region = <TestRegion as RegionCreate>::create(10, &LIMITS).expect("region created");
300             let module = MockModuleBuilder::new().with_heap_spec(SPEC).build();
301             let mut inst = region
302                 .new_instance_builder(module.clone())
303                 .with_heap_size_limit(INSTANCE_LIMIT)
304                 .build()
305                 .expect("instantiation succeeds");
306 
307             let heap_len = inst.alloc().heap_len();
308             assert_eq!(heap_len, SPEC.initial_size as usize);
309 
310             // fill up the rest of the per-instance limit area
311             let new_heap_area = inst
312                 .alloc_mut()
313                 .expand_heap(64 * 1024, module.as_ref())
314                 .expect("expand_heap succeeds");
315             assert_eq!(heap_len, new_heap_area as usize);
316 
317             let new_heap_len = inst.alloc().heap_len();
318             assert_eq!(new_heap_len, INSTANCE_LIMIT);
319 
320             let past_limit_heap_area = inst.alloc_mut().expand_heap(64 * 1024, module.as_ref());
321             assert!(
322                 past_limit_heap_area.is_err(),
323                 "heap expansion past limit fails"
324             );
325 
326             let still_heap_len = inst.alloc().heap_len();
327             assert_eq!(still_heap_len, INSTANCE_LIMIT);
328 
329             let heap = unsafe { inst.alloc_mut().heap_mut() };
330             assert_eq!(heap[new_heap_len - 1], 0);
331             heap[new_heap_len - 1] = 0xFF;
332             assert_eq!(heap[new_heap_len - 1], 0xFF);
333         }
334 
335         const INITIAL_OVERSIZE_HEAP: HeapSpec = HeapSpec {
336             reserved_size: SPEC_HEAP_RESERVED_SIZE,
337             guard_size: SPEC_HEAP_GUARD_SIZE,
338             initial_size: SPEC_HEAP_RESERVED_SIZE + (64 * 1024),
339             max_size: None,
340         };
341 
342         /// This test shows that a heap refuses to grow past the alloc limits, even if the runtime
343         /// spec says it can grow bigger. This test uses a region with multiple slots in order to
344         /// exercise more edge cases with adjacent managed memory.
345         #[test]
346         fn reject_initial_oversize_heap() {
347             let region = <TestRegion as RegionCreate>::create(10, &LIMITS).expect("region created");
348             let res = region.new_instance(
349                 MockModuleBuilder::new()
350                     .with_heap_spec(INITIAL_OVERSIZE_HEAP)
351                     .build(),
352             );
353             assert!(res.is_err(), "new_instance fails");
354         }
355 
356         /// This test shows that we reject limits with a larger memory size than address space size
357         #[test]
358         fn reject_undersized_address_space() {
359             const LIMITS: Limits = Limits {
360                 heap_memory_size: LIMITS_HEAP_ADDRSPACE_SIZE + 4096,
361                 heap_address_space_size: LIMITS_HEAP_ADDRSPACE_SIZE,
362                 stack_size: LIMITS_STACK_SIZE,
363                 globals_size: LIMITS_GLOBALS_SIZE,
364                 ..Limits::default()
365             };
366             let res = <TestRegion as RegionCreate>::create(10, &LIMITS);
367             assert!(res.is_err(), "region creation fails");
368         }
369 
370         const SMALL_GUARD_HEAP: HeapSpec = HeapSpec {
371             reserved_size: SPEC_HEAP_RESERVED_SIZE,
372             guard_size: SPEC_HEAP_GUARD_SIZE - 1,
373             initial_size: LIMITS_HEAP_MEM_SIZE as u64,
374             max_size: None,
375         };
376 
377         /// This test shows that a heap spec with a guard size smaller than the limits is
378         /// allowed.
379         #[test]
380         fn accept_small_guard_heap() {
381             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
382             let _inst = region
383                 .new_instance(
384                     MockModuleBuilder::new()
385                         .with_heap_spec(SMALL_GUARD_HEAP)
386                         .build(),
387                 )
388                 .expect("new_instance succeeds");
389         }
390 
391         const LARGE_GUARD_HEAP: HeapSpec = HeapSpec {
392             reserved_size: SPEC_HEAP_RESERVED_SIZE,
393             guard_size: SPEC_HEAP_GUARD_SIZE + 1,
394             initial_size: ONEPAGE_INITIAL_SIZE,
395             max_size: None,
396         };
397 
398         /// This test shows that a `HeapSpec` with a guard size larger than the limits is not
399         /// allowed.
400         #[test]
401         fn reject_large_guard_heap() {
402             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
403             let res = region.new_instance(
404                 MockModuleBuilder::new()
405                     .with_heap_spec(LARGE_GUARD_HEAP)
406                     .build(),
407             );
408             assert!(res.is_err(), "new_instance fails");
409         }
410 
411         /// This test shows that a `Slot` can be reused after an `AllocHandle` is dropped, and that
412         /// its memory is reset.
413         #[test]
414         fn reuse_slot_works() {
415             fn peek_n_poke(region: &Arc<TestRegion>) {
416                 let mut inst = region
417                     .new_instance(
418                         MockModuleBuilder::new()
419                             .with_heap_spec(ONE_PAGE_HEAP)
420                             .build(),
421                     )
422                     .expect("new_instance succeeds");
423 
424                 let heap_len = inst.alloc().heap_len();
425                 assert_eq!(heap_len, ONEPAGE_INITIAL_SIZE as usize);
426 
427                 let heap = unsafe { inst.alloc_mut().heap_mut() };
428 
429                 assert_eq!(heap[0], 0);
430                 heap[0] = 0xFF;
431                 assert_eq!(heap[0], 0xFF);
432 
433                 assert_eq!(heap[heap_len - 1], 0);
434                 heap[heap_len - 1] = 0xFF;
435                 assert_eq!(heap[heap_len - 1], 0xFF);
436 
437                 let stack = unsafe { inst.alloc_mut().stack_mut() };
438                 assert_eq!(stack.len(), LIMITS_STACK_SIZE);
439 
440                 assert_eq!(stack[0], 0);
441                 stack[0] = 0xFF;
442                 assert_eq!(stack[0], 0xFF);
443 
444                 assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0);
445                 stack[LIMITS_STACK_SIZE - 1] = 0xFF;
446                 assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0xFF);
447 
448                 let globals = unsafe { inst.alloc_mut().globals_mut() };
449                 assert_eq!(
450                     globals.len(),
451                     LIMITS_GLOBALS_SIZE / std::mem::size_of::<GlobalValue>()
452                 );
453 
454                 unsafe {
455                     assert_eq!(globals[0].i_64, 0);
456                     globals[0].i_64 = 0xFF;
457                     assert_eq!(globals[0].i_64, 0xFF);
458                 }
459 
460                 unsafe {
461                     assert_eq!(globals[globals.len() - 1].i_64, 0);
462                     globals[globals.len() - 1].i_64 = 0xFF;
463                     assert_eq!(globals[globals.len() - 1].i_64, 0xFF);
464                 }
465 
466                 let sigstack = unsafe { inst.alloc_mut().sigstack_mut() };
467                 assert_eq!(sigstack.len(), LIMITS.signal_stack_size);
468 
469                 assert_eq!(sigstack[0], 0);
470                 sigstack[0] = 0xFF;
471                 assert_eq!(sigstack[0], 0xFF);
472 
473                 assert_eq!(sigstack[sigstack.len() - 1], 0);
474                 sigstack[sigstack.len() - 1] = 0xFF;
475                 assert_eq!(sigstack[sigstack.len() - 1], 0xFF);
476             }
477 
478             // with a region size of 1, the slot must be reused
479             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
480 
481             peek_n_poke(&region);
482             peek_n_poke(&region);
483         }
484 
485         /// This test shows that the reset method clears the heap and resets its protections.
486         #[test]
487         fn alloc_reset() {
488             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
489             let module = MockModuleBuilder::new()
490                 .with_heap_spec(THREE_PAGE_MAX_HEAP)
491                 .build();
492             let mut inst = region
493                 .new_instance(module.clone())
494                 .expect("new_instance succeeds");
495 
496             let heap_len = inst.alloc().heap_len();
497             assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
498 
499             let heap = unsafe { inst.alloc_mut().heap_mut() };
500 
501             assert_eq!(heap[0], 0);
502             heap[0] = 0xFF;
503             assert_eq!(heap[0], 0xFF);
504 
505             assert_eq!(heap[heap_len - 1], 0);
506             heap[heap_len - 1] = 0xFF;
507             assert_eq!(heap[heap_len - 1], 0xFF);
508 
509             // Making a new mock module here because the borrow checker doesn't like referencing
510             // `inst.module` while `inst.alloc()` is borrowed mutably. The `Instance` tests don't have
511             // this weirdness
512             inst.alloc_mut()
513                 .reset_heap(module.as_ref())
514                 .expect("reset succeeds");
515 
516             let reset_heap_len = inst.alloc().heap_len();
517             assert_eq!(reset_heap_len, THREEPAGE_INITIAL_SIZE as usize);
518 
519             let heap = unsafe { inst.alloc_mut().heap_mut() };
520 
521             assert_eq!(heap[0], 0);
522             heap[0] = 0xFF;
523             assert_eq!(heap[0], 0xFF);
524 
525             assert_eq!(heap[reset_heap_len - 1], 0);
526             heap[reset_heap_len - 1] = 0xFF;
527             assert_eq!(heap[reset_heap_len - 1], 0xFF);
528         }
529 
530         /// This test shows that the reset method clears the heap and restores it to the spec
531         /// initial size after growing the heap.
532         #[test]
533         fn alloc_grow_reset() {
534             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
535             let module = MockModuleBuilder::new()
536                 .with_heap_spec(THREE_PAGE_MAX_HEAP)
537                 .build();
538             let mut inst = region
539                 .new_instance(module.clone())
540                 .expect("new_instance succeeds");
541 
542             let heap_len = inst.alloc().heap_len();
543             assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
544 
545             let heap = unsafe { inst.alloc_mut().heap_mut() };
546 
547             assert_eq!(heap[0], 0);
548             heap[0] = 0xFF;
549             assert_eq!(heap[0], 0xFF);
550 
551             assert_eq!(heap[heap_len - 1], 0);
552             heap[heap_len - 1] = 0xFF;
553             assert_eq!(heap[heap_len - 1], 0xFF);
554 
555             let new_heap_area = inst
556                 .alloc_mut()
557                 .expand_heap(
558                     (THREEPAGE_MAX_SIZE - THREEPAGE_INITIAL_SIZE) as u32,
559                     module.as_ref(),
560                 )
561                 .expect("expand_heap succeeds");
562             assert_eq!(heap_len, new_heap_area as usize);
563 
564             let new_heap_len = inst.alloc().heap_len();
565             assert_eq!(new_heap_len, THREEPAGE_MAX_SIZE as usize);
566 
567             // Making a new mock module here because the borrow checker doesn't like referencing
568             // `inst.module` while `inst.alloc()` is borrowed mutably. The `Instance` tests don't have
569             // this weirdness
570             inst.alloc_mut()
571                 .reset_heap(module.as_ref())
572                 .expect("reset succeeds");
573 
574             let reset_heap_len = inst.alloc().heap_len();
575             assert_eq!(reset_heap_len, THREEPAGE_INITIAL_SIZE as usize);
576 
577             let heap = unsafe { inst.alloc_mut().heap_mut() };
578 
579             assert_eq!(heap[0], 0);
580             heap[0] = 0xFF;
581             assert_eq!(heap[0], 0xFF);
582 
583             assert_eq!(heap[reset_heap_len - 1], 0);
584             heap[reset_heap_len - 1] = 0xFF;
585             assert_eq!(heap[reset_heap_len - 1], 0xFF);
586         }
587 
588         const GUARDLESS_HEAP: HeapSpec = HeapSpec {
589             reserved_size: SPEC_HEAP_RESERVED_SIZE,
590             guard_size: 0,
591             initial_size: ONEPAGE_INITIAL_SIZE,
592             max_size: None,
593         };
594 
595         /// This test shows the alloc works even with a zero guard size.
596         #[test]
597         fn guardless_heap_create() {
598             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
599             let mut inst = region
600                 .new_instance(
601                     MockModuleBuilder::new()
602                         .with_heap_spec(GUARDLESS_HEAP)
603                         .build(),
604                 )
605                 .expect("new_instance succeeds");
606 
607             let heap_len = inst.alloc().heap_len();
608             assert_eq!(heap_len, ONEPAGE_INITIAL_SIZE as usize);
609 
610             let heap = unsafe { inst.alloc_mut().heap_mut() };
611 
612             assert_eq!(heap[0], 0);
613             heap[0] = 0xFF;
614             assert_eq!(heap[0], 0xFF);
615 
616             assert_eq!(heap[heap_len - 1], 0);
617             heap[heap_len - 1] = 0xFF;
618             assert_eq!(heap[heap_len - 1], 0xFF);
619 
620             let stack = unsafe { inst.alloc_mut().stack_mut() };
621             assert_eq!(stack.len(), LIMITS_STACK_SIZE);
622 
623             assert_eq!(stack[0], 0);
624             stack[0] = 0xFF;
625             assert_eq!(stack[0], 0xFF);
626 
627             assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0);
628             stack[LIMITS_STACK_SIZE - 1] = 0xFF;
629             assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0xFF);
630         }
631 
632         /// This test shows a guardless heap works properly after a single expand.
633         #[test]
634         fn guardless_expand_heap_once() {
635             expand_heap_once_template(GUARDLESS_HEAP)
636         }
637 
638         const INITIAL_EMPTY_HEAP: HeapSpec = HeapSpec {
639             reserved_size: SPEC_HEAP_RESERVED_SIZE,
640             guard_size: SPEC_HEAP_GUARD_SIZE,
641             initial_size: 0,
642             max_size: None,
643         };
644 
645         /// This test shows an initially-empty heap works properly after a single expand.
646         #[test]
647         fn initial_empty_expand_heap_once() {
648             expand_heap_once_template(INITIAL_EMPTY_HEAP)
649         }
650 
651         const INITIAL_EMPTY_GUARDLESS_HEAP: HeapSpec = HeapSpec {
652             reserved_size: SPEC_HEAP_RESERVED_SIZE,
653             guard_size: 0,
654             initial_size: 0,
655             max_size: None,
656         };
657 
658         /// This test shows an initially-empty, guardless heap works properly after a single
659         /// expand.
660         #[test]
661         fn initial_empty_guardless_expand_heap_once() {
662             expand_heap_once_template(INITIAL_EMPTY_GUARDLESS_HEAP)
663         }
664 
665         const CONTEXT_TEST_LIMITS: Limits = Limits {
666             heap_memory_size: 4096,
667             heap_address_space_size: 2 * 4096,
668             stack_size: 4096,
669             globals_size: 4096,
670             ..Limits::default()
671         };
672         const CONTEXT_TEST_INITIAL_SIZE: u64 = 4096;
673         const CONTEXT_TEST_HEAP: HeapSpec = HeapSpec {
674             reserved_size: 4096,
675             guard_size: 4096,
676             initial_size: CONTEXT_TEST_INITIAL_SIZE,
677             max_size: Some(4096),
678         };
679 
680         /// This test shows that alloced memory will create a heap and a stack that child context
681         /// code can use.
682         #[test]
683         fn context_alloc_child() {
684             extern "C" fn heap_touching_child(heap: *mut u8) {
685                 let heap = unsafe {
686                     std::slice::from_raw_parts_mut(heap, CONTEXT_TEST_INITIAL_SIZE as usize)
687                 };
688                 heap[0] = 123;
689                 heap[4095] = 45;
690             }
691 
692             let region = <TestRegion as RegionCreate>::create(1, &CONTEXT_TEST_LIMITS)
693                 .expect("region created");
694             let mut inst = region
695                 .new_instance(
696                     MockModuleBuilder::new()
697                         .with_heap_spec(CONTEXT_TEST_HEAP)
698                         .build(),
699                 )
700                 .expect("new_instance succeeds");
701 
702             let mut parent = ContextHandle::new();
703             unsafe {
704                 let heap_ptr = inst.alloc_mut().heap_mut().as_ptr() as *mut c_void;
705                 let mut child = ContextHandle::create_and_init(
706                     inst.alloc_mut().stack_u64_mut(),
707                     heap_touching_child as usize,
708                     &[Val::CPtr(heap_ptr)],
709                 )
710                 .expect("context init succeeds");
711                 Context::swap(&mut parent, &mut child);
712                 assert_eq!(inst.alloc().heap()[0], 123);
713                 assert_eq!(inst.alloc().heap()[4095], 45);
714             }
715         }
716 
717         /// This test shows that an alloced memory will create a heap and stack, the child code can
718         /// write a pattern to that stack, and we can read back that same pattern after it is done
719         /// running.
720         #[test]
721         fn context_stack_pattern() {
722             const STACK_PATTERN_LENGTH: usize = 1024;
723             extern "C" fn stack_pattern_child(heap: *mut u64) {
724                 let heap = unsafe {
725                     std::slice::from_raw_parts_mut(heap, CONTEXT_TEST_INITIAL_SIZE as usize / 8)
726                 };
727                 let mut onthestack = [0u8; STACK_PATTERN_LENGTH];
728                 // While not used, this array is load-bearing! A function that executes after the
729                 // guest completes, `instance_kill_state_exit_guest_region`, may end up using
730                 // sufficient stack space to trample over values in this function's call frame.
731                 //
732                 // Padding it out with a duplicate pattern makes enough space for `onthestack` to
733                 // not be clobbered.
734                 let mut ignored = [0u8; STACK_PATTERN_LENGTH];
735                 for i in 0..STACK_PATTERN_LENGTH {
736                     ignored[i] = (i % 256) as u8;
737                     onthestack[i] = (i % 256) as u8;
738                 }
739                 heap[0] = onthestack.as_ptr() as u64;
740             }
741 
742             let region = <TestRegion as RegionCreate>::create(1, &CONTEXT_TEST_LIMITS)
743                 .expect("region created");
744             let mut inst = region
745                 .new_instance(
746                     MockModuleBuilder::new()
747                         .with_heap_spec(CONTEXT_TEST_HEAP)
748                         .build(),
749                 )
750                 .expect("new_instance succeeds");
751 
752             let mut parent = ContextHandle::new();
753             unsafe {
754                 let heap_ptr = inst.alloc_mut().heap_mut().as_ptr() as *mut c_void;
755                 let mut child = ContextHandle::create_and_init(
756                     inst.alloc_mut().stack_u64_mut(),
757                     stack_pattern_child as usize,
758                     &[Val::CPtr(heap_ptr)],
759                 )
760                 .expect("context init succeeds");
761                 Context::swap(&mut parent, &mut child);
762 
763                 let stack_pattern = inst.alloc().heap_u64()[0] as usize;
764                 assert!(stack_pattern > inst.alloc().slot().stack as usize);
765                 assert!(
766                     stack_pattern + STACK_PATTERN_LENGTH < inst.alloc().slot().stack_top() as usize
767                 );
768                 let stack_pattern =
769                     std::slice::from_raw_parts(stack_pattern as *const u8, STACK_PATTERN_LENGTH);
770                 for i in 0..STACK_PATTERN_LENGTH {
771                     assert_eq!(stack_pattern[i], (i % 256) as u8);
772                 }
773             }
774         }
775 
776         #[test]
777         fn drop_region_first() {
778             let region = <TestRegion as RegionCreate>::create(1, &Limits::default())
779                 .expect("region can be created");
780             let inst = region
781                 .new_instance(MockModuleBuilder::new().build())
782                 .expect("new_instance succeeds");
783             drop(region);
784             drop(inst);
785         }
786 
787         #[test]
788         fn badly_specced_instance_does_not_take_up_capacity() {
789             let module = MockModuleBuilder::new()
790                 .with_heap_spec(LARGE_GUARD_HEAP)
791                 .build();
792             let region = <TestRegion as RegionCreate>::create(2, &LIMITS).expect("region created");
793             assert_eq!(region.capacity(), 2);
794             assert_eq!(region.free_slots(), 2);
795             assert_eq!(region.used_slots(), 0);
796             let bad_inst_res = region.new_instance(module.clone());
797             assert!(bad_inst_res.is_err());
798             assert_eq!(region.capacity(), 2);
799             assert_eq!(region.free_slots(), 2);
800             assert_eq!(region.used_slots(), 0);
801         }
802 
803         #[test]
804         fn slot_counts_work() {
805             let module = MockModuleBuilder::new()
806                 .with_heap_spec(ONE_PAGE_HEAP)
807                 .build();
808             let region = <TestRegion as RegionCreate>::create(2, &LIMITS).expect("region created");
809             assert_eq!(region.capacity(), 2);
810             assert_eq!(region.free_slots(), 2);
811             assert_eq!(region.used_slots(), 0);
812             let inst1 = region
813                 .new_instance(module.clone())
814                 .expect("new_instance succeeds");
815             assert_eq!(region.capacity(), 2);
816             assert_eq!(region.free_slots(), 1);
817             assert_eq!(region.used_slots(), 1);
818             let inst2 = region.new_instance(module).expect("new_instance succeeds");
819             assert_eq!(region.capacity(), 2);
820             assert_eq!(region.free_slots(), 0);
821             assert_eq!(region.used_slots(), 2);
822             drop(inst1);
823             assert_eq!(region.capacity(), 2);
824             assert_eq!(region.free_slots(), 1);
825             assert_eq!(region.used_slots(), 1);
826             drop(inst2);
827             assert_eq!(region.capacity(), 2);
828             assert_eq!(region.free_slots(), 2);
829             assert_eq!(region.used_slots(), 0);
830         }
831 
832         /// This test exercises the AllocStrategy::Random. In this scenario,
833         /// the Region has a single slot which is "randomly" allocated and then dropped.
834         #[test]
835         fn slot_counts_work_with_random_alloc() {
836             let module = MockModuleBuilder::new()
837                 .with_heap_spec(ONE_PAGE_HEAP)
838                 .build();
839             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
840             assert_eq!(region.capacity(), 1);
841             assert_eq!(region.free_slots(), 1);
842             assert_eq!(region.used_slots(), 0);
843 
844             let inst = region
845                 .new_instance_builder(module.clone())
846                 .with_alloc_strategy(AllocStrategy::Random)
847                 .build()
848                 .expect("new_instance succeeds");
849             assert_eq!(region.capacity(), 1);
850             assert_eq!(region.free_slots(), 0);
851             assert_eq!(region.used_slots(), 1);
852 
853             drop(inst);
854             assert_eq!(region.capacity(), 1);
855             assert_eq!(region.free_slots(), 1);
856             assert_eq!(region.used_slots(), 0);
857         }
858 
859         /// This test exercises the AllocStrategy::CustomRandom. In this scenario,
860         /// the Region has 10 slots which are randomly allocated up to capacity
861         /// and then dropped. The test is executed 100 times to exercise the
862         /// random nature of the allocation strategy.
863         #[test]
864         fn slot_counts_work_with_custom_random_alloc() {
865             let mut master_rng = thread_rng();
866             let seed: u64 = master_rng.gen();
867             eprintln!(
868                 "Seeding slot_counts_work_with_custom_random_alloc() with {}",
869                 seed
870             );
871 
872             let rng: StdRng = SeedableRng::seed_from_u64(seed);
873             let shared_rng = Arc::new(Mutex::new(rng));
874 
875             for _ in 0..100 {
876                 let mut inst_vec = Vec::new();
877                 let module = MockModuleBuilder::new()
878                     .with_heap_spec(ONE_PAGE_HEAP)
879                     .build();
880                 let total_slots = 10;
881                 let region = <TestRegion as RegionCreate>::create(total_slots, &LIMITS)
882                     .expect("region created");
883                 assert_eq!(region.capacity(), total_slots);
884                 assert_eq!(region.free_slots(), 10);
885                 assert_eq!(region.used_slots(), 0);
886 
887                 // Randomly allocate all of the slots in the region.
888                 for i in 1..=total_slots {
889                     let inst = region
890                         .new_instance_builder(module.clone())
891                         .with_alloc_strategy(AllocStrategy::CustomRandom(shared_rng.clone()))
892                         .build()
893                         .expect("new_instance succeeds");
894 
895                     assert_eq!(region.capacity(), total_slots);
896                     assert_eq!(region.free_slots(), total_slots - i);
897                     assert_eq!(region.used_slots(), i);
898                     inst_vec.push(inst);
899                 }
900 
901                 // It's not possible to allocate just one more.  Try
902                 // it and affirm that the error is handled gracefully.
903                 let wont_inst = region
904                     .new_instance_builder(module.clone())
905                     .with_alloc_strategy(AllocStrategy::CustomRandom(shared_rng.clone()))
906                     .build();
907                 assert!(wont_inst.is_err());
908 
909                 // Drop all of the slots in the region.
910                 for i in 1..=total_slots {
911                     drop(inst_vec.pop());
912                     assert_eq!(region.capacity(), total_slots);
913                     assert_eq!(region.free_slots(), total_slots - (total_slots - i));
914                     assert_eq!(region.used_slots(), total_slots - i);
915                 }
916 
917                 // Allocate just one more to make sure the drops took place
918                 // and the Region has capacity again.
919                 region
920                     .new_instance_builder(module.clone())
921                     .with_alloc_strategy(AllocStrategy::CustomRandom(shared_rng.clone()))
922                     .build()
923                     .expect("new_instance succeeds");
924             }
925         }
926 
927         /// This test exercises a mixed AllocStrategy. In this scenario,
928         /// the Region has 10 slots which are randomly and linearly allocated
929         /// up to capacity and then dropped. The test is executed 100 times to
930         /// exercise the random nature of the allocation strategy.
931         #[test]
932         fn slot_counts_work_with_mixed_alloc() {
933             let mut master_rng = thread_rng();
934             let seed: u64 = master_rng.gen();
935             eprintln!("Seeding slot_counts_work_with_mixed_alloc() with {}", seed);
936 
937             let rng: StdRng = SeedableRng::seed_from_u64(seed);
938             let shared_rng = Arc::new(Mutex::new(rng));
939 
940             for _ in 0..100 {
941                 let mut inst_vec = Vec::new();
942                 let module = MockModuleBuilder::new()
943                     .with_heap_spec(ONE_PAGE_HEAP)
944                     .build();
945                 let total_slots = 10;
946                 let region = <TestRegion as RegionCreate>::create(total_slots, &LIMITS)
947                     .expect("region created");
948                 assert_eq!(region.capacity(), total_slots);
949                 assert_eq!(region.free_slots(), 10);
950                 assert_eq!(region.used_slots(), 0);
951 
952                 // Allocate all of the slots in the region.
953                 for i in 1..=total_slots {
954                     let inst;
955                     if i % 2 == 0 {
956                         inst = region
957                             .new_instance_builder(module.clone())
958                             .with_alloc_strategy(AllocStrategy::CustomRandom(shared_rng.clone()))
959                             .build()
960                             .expect("new_instance succeeds");
961                     } else {
962                         inst = region
963                             .new_instance_builder(module.clone())
964                             .with_alloc_strategy(AllocStrategy::Linear)
965                             .build()
966                             .expect("new_instance succeeds");
967                     }
968 
969                     assert_eq!(region.capacity(), total_slots);
970                     assert_eq!(region.free_slots(), total_slots - i);
971                     assert_eq!(region.used_slots(), i);
972                     inst_vec.push(inst);
973                 }
974 
975                 // It's not possible to allocate just one more.  Try
976                 // it and affirm that the error is handled gracefully.
977                 let wont_inst = region
978                     .new_instance_builder(module.clone())
979                     .with_alloc_strategy(AllocStrategy::CustomRandom(shared_rng.clone()))
980                     .build();
981                 assert!(wont_inst.is_err());
982 
983                 // Drop all of the slots in the region.
984                 for i in 1..=total_slots {
985                     drop(inst_vec.pop());
986                     assert_eq!(region.capacity(), total_slots);
987                     assert_eq!(region.free_slots(), total_slots - (total_slots - i));
988                     assert_eq!(region.used_slots(), total_slots - i);
989                 }
990 
991                 // Allocate just one more to make sure the drops took place
992                 // and the Region has capacity again.
993                 region
994                     .new_instance_builder(module.clone())
995                     .with_alloc_strategy(AllocStrategy::Linear)
996                     .build()
997                     .expect("new_instance succeeds");
998             }
999         }
1000 
1001         fn do_nothing_module() -> Arc<dyn Module> {
1002             extern "C" fn do_nothing(_vmctx: *const lucet_vmctx) -> () {}
1003 
1004             MockModuleBuilder::new()
1005                 .with_export_func(MockExportBuilder::new(
1006                     "do_nothing",
1007                     FunctionPointer::from_usize(do_nothing as usize),
1008                 ))
1009                 .build()
1010         }
1011 
1012         #[test]
1013         fn reject_sigstack_smaller_than_min() {
1014             if MINSIGSTKSZ == 0 {
1015                 // can't trigger the error on this platform
1016                 return;
1017             }
1018             let limits = Limits {
1019                 // keep it page-aligned but make it too small
1020                 signal_stack_size: (MINSIGSTKSZ.checked_sub(1).unwrap() / host_page_size())
1021                     * host_page_size(),
1022                 ..Limits::default()
1023             };
1024             let region = <TestRegion as RegionCreate>::create(1, &limits).expect("region created");
1025             let mut inst = region
1026                 .new_instance(do_nothing_module())
1027                 .expect("new_instance succeeds");
1028 
1029             // run the bad one a bunch of times, in case there's some bad state left over following
1030             // a failure
1031             for _ in 0..5 {
1032                 match inst.run("do_nothing", &[]) {
1033                     Err(Error::InvalidArgument(
1034                         "signal stack size must be at least MINSIGSTKSZ (defined in <signal.h>)",
1035                     )) => (),
1036                     Err(e) => panic!("unexpected error: {}", e),
1037                     Ok(_) => panic!("unexpected success"),
1038                 }
1039                 assert!(inst.is_ready());
1040             }
1041 
1042             // now make sure that we can run an instance with reasonable limits on this same thread,
1043             // to make sure the `CURRENT_INSTANCE` thread-local isn't left in a bad state
1044             let region = <TestRegion as RegionCreate>::create(1, &Limits::default())
1045                 .expect("region created");
1046             let mut inst = region
1047                 .new_instance(do_nothing_module())
1048                 .expect("new_instance succeeds");
1049             inst.run("do_nothing", &[]).expect("run succeeds");
1050         }
1051 
1052         /// This test ensures that a signal stack smaller than 12KiB is rejected when Lucet is
1053         /// compiled in debug mode.
1054         #[test]
1055         #[cfg(debug_assertions)]
1056         fn reject_debug_sigstack_smaller_than_12kib() {
1057             if 8192 < MINSIGSTKSZ {
1058                 // can't trigger the error on this platform, as the MINSIGSTKSZ check runs first
1059                 return;
1060             }
1061             let limits = Limits {
1062                 signal_stack_size: 8192,
1063                 ..Limits::default()
1064             };
1065             let region = <TestRegion as RegionCreate>::create(1, &limits).expect("region created");
1066             let mut inst = region
1067                 .new_instance(do_nothing_module())
1068                 .expect("new_instance succeeds");
1069             match inst.run("do_nothing", &[]) {
1070                 Err(Error::InvalidArgument(
1071                     "signal stack size must be at least 12KiB for debug builds",
1072                 )) => (),
1073                 Err(e) => panic!("unexpected error: {}", e),
1074                 Ok(_) => panic!("unexpected success"),
1075             }
1076             assert!(inst.is_ready());
1077 
1078             // now make sure that we can run an instance with reasonable limits on this same thread,
1079             // to make sure the `CURRENT_INSTANCE` thread-local isn't left in a bad state
1080             let region = <TestRegion as RegionCreate>::create(1, &Limits::default())
1081                 .expect("region created");
1082             let mut inst = region
1083                 .new_instance(do_nothing_module())
1084                 .expect("new_instance succeeds");
1085             inst.run("do_nothing", &[]).expect("run succeeds");
1086         }
1087 
1088         #[test]
1089         fn reject_unaligned_sigstack() {
1090             let limits = Limits {
1091                 signal_stack_size: std::cmp::max(libc::SIGSTKSZ, 12 * 1024)
1092                     .checked_add(1)
1093                     .unwrap(),
1094                 ..Limits::default()
1095             };
1096             let res = <TestRegion as RegionCreate>::create(1, &limits);
1097             match res {
1098                 Err(Error::InvalidArgument(
1099                     "signal stack size must be a multiple of host page size",
1100                 )) => (),
1101                 Err(e) => panic!("unexpected error: {}", e),
1102                 Ok(_) => panic!("unexpected success"),
1103             }
1104         }
1105 
1106         /// This test exercises custom limits on the heap_memory_size.
1107         /// In this scenario, the Region has a limit on the heap
1108         /// memory size, but the instance has a larger limit.  An
1109         /// instance's custom limit must not exceed the Region's.
1110         #[test]
1111         fn reject_heap_memory_size_exceeds_region_limits() {
1112             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
1113             let module = MockModuleBuilder::new()
1114                 .with_heap_spec(THREE_PAGE_MAX_HEAP)
1115                 .build();
1116             let res = region
1117                 .new_instance_builder(module.clone())
1118                 .with_heap_size_limit(&LIMITS.heap_memory_size * 2)
1119                 .build();
1120 
1121             match res {
1122                 Err(Error::InvalidArgument(
1123                     "heap memory size requested for instance is larger than slot allows",
1124                 )) => (),
1125                 Err(e) => panic!("unexpected error: {}", e),
1126                 Ok(_) => panic!("unexpected success"),
1127             }
1128         }
1129 
1130         /// This test exercises custom limits on the heap_memory_size.
1131         /// In this scenario, successfully create a custom-sized
1132         /// instance, drop it, then create a default-sized instance to
1133         /// affirm that a custom size doesn't somehow overwrite the
1134         /// default size.
1135         #[test]
1136         fn custom_size_does_not_break_default() {
1137             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
1138 
1139             // Build an instance that is has custom limits that are big
1140             // enough to accommodate the HeapSpec.
1141             let custom_inst = region
1142                 .new_instance_builder(
1143                     MockModuleBuilder::new()
1144                         .with_heap_spec(THREE_PAGE_MAX_HEAP)
1145                         .build(),
1146                 )
1147                 .with_heap_size_limit((THREE_PAGE_MAX_HEAP.initial_size * 2) as usize)
1148                 .build()
1149                 .expect("new instance succeeds");
1150 
1151             // Affirm that its heap is the expected size, the size
1152             // specified in the HeapSpec.
1153             let heap_len = custom_inst.alloc().heap_len();
1154             assert_eq!(heap_len, THREE_PAGE_MAX_HEAP.initial_size as usize);
1155             drop(custom_inst);
1156 
1157             // Build a default heap-limited instance, to make sure the
1158             // custom limits didn't break the defaults.
1159             let default_inst = region
1160                 .new_instance(
1161                     MockModuleBuilder::new()
1162                         .with_heap_spec(SMALL_GUARD_HEAP)
1163                         .build(),
1164                 )
1165                 .expect("new_instance succeeds");
1166 
1167             // Affirm that its heap is the expected size.
1168             let heap_len = default_inst.alloc().heap_len();
1169             assert_eq!(heap_len, SMALL_GUARD_HEAP.initial_size as usize);
1170         }
1171 
1172         /// This test exercises custom limits on the heap_memory_size.
1173         /// In this scenario, the HeapSpec has an expectation for the
1174         /// initial heap memory size, but the instance's limit is too small.
1175         #[test]
1176         fn reject_heap_memory_size_exeeds_instance_limits() {
1177             let region = <TestRegion as RegionCreate>::create(1, &LIMITS).expect("region created");
1178             let res = region
1179                 .new_instance_builder(
1180                     MockModuleBuilder::new()
1181                         .with_heap_spec(THREE_PAGE_MAX_HEAP)
1182                         .build(),
1183                 )
1184                 .with_heap_size_limit((THREE_PAGE_MAX_HEAP.initial_size / 2) as usize)
1185                 .build();
1186 
1187             assert!(res.is_err(), "new_instance fails");
1188         }
1189     };
1190 }
1191 
1192 #[cfg(test)]
1193 mod mmap {
1194     alloc_tests!(crate::region::mmap::MmapRegion);
1195 }
1196 
1197 #[cfg(all(test, target_os = "linux", feature = "uffd"))]
1198 mod uffd {
1199     alloc_tests!(crate::region::uffd::UffdRegion);
1200 }
1201