1use crate::{
22 buffer::LimitedVec,
23 gas::ChargeError,
24 pages::{GearPage, WasmPage, WasmPagesAmount},
25};
26use alloc::format;
27use byteorder::{ByteOrder, LittleEndian};
28use core::{
29 fmt,
30 fmt::Debug,
31 ops::{Deref, DerefMut},
32};
33use numerated::{
34 interval::{Interval, TryFromRangeError},
35 tree::IntervalsTree,
36};
37use scale_info::{
38 scale::{self, Decode, Encode, EncodeLike, Input, Output},
39 TypeInfo,
40};
41
42#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode)]
44pub struct MemoryInterval {
45 pub offset: u32,
47 pub size: u32,
49}
50
51impl MemoryInterval {
52 #[inline]
56 pub fn to_bytes(&self) -> [u8; 8] {
57 let mut bytes = [0u8; 8];
58 LittleEndian::write_u32(&mut bytes[0..4], self.offset);
59 LittleEndian::write_u32(&mut bytes[4..8], self.size);
60 bytes
61 }
62
63 #[inline]
67 pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
68 if bytes.len() != 8 {
69 return Err("bytes size != 8");
70 }
71 let offset = LittleEndian::read_u32(&bytes[0..4]);
72 let size = LittleEndian::read_u32(&bytes[4..8]);
73 Ok(MemoryInterval { offset, size })
74 }
75}
76
77impl From<(u32, u32)> for MemoryInterval {
78 fn from(val: (u32, u32)) -> Self {
79 MemoryInterval {
80 offset: val.0,
81 size: val.1,
82 }
83 }
84}
85
86impl From<MemoryInterval> for (u32, u32) {
87 fn from(val: MemoryInterval) -> Self {
88 (val.offset, val.size)
89 }
90}
91
92impl Debug for MemoryInterval {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 f.debug_struct("MemoryInterval")
95 .field("offset", &format_args!("{:#x}", self.offset))
96 .field("size", &format_args!("{:#x}", self.size))
97 .finish()
98 }
99}
100
101#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, TypeInfo, derive_more::Display)]
103#[display("Trying to make wrong size page buffer, must be {:#x}", GearPage::SIZE)]
104pub struct IntoPageBufError;
105
106pub type PageBufInner = LimitedVec<u8, IntoPageBufError, { GearPage::SIZE as usize }>;
108
109#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, TypeInfo)]
111pub struct PageBuf(PageBufInner);
112
113impl Encode for PageBuf {
120 fn size_hint(&self) -> usize {
121 GearPage::SIZE as usize
122 }
123
124 fn encode_to<W: Output + ?Sized>(&self, dest: &mut W) {
125 dest.write(self.0.inner())
126 }
127}
128
129impl Decode for PageBuf {
130 #[inline]
131 fn decode<I: Input>(input: &mut I) -> Result<Self, scale::Error> {
132 let mut buffer = PageBufInner::new_default();
133 input.read(buffer.inner_mut())?;
134 Ok(Self(buffer))
135 }
136}
137
138impl EncodeLike for PageBuf {}
139
140impl Debug for PageBuf {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(
143 f,
144 "PageBuf({:?}..{:?})",
145 &self.0.inner()[0..10],
146 &self.0.inner()[GearPage::SIZE as usize - 10..GearPage::SIZE as usize]
147 )
148 }
149}
150
151impl Deref for PageBuf {
152 type Target = [u8];
153 fn deref(&self) -> &Self::Target {
154 self.0.inner()
155 }
156}
157
158impl DerefMut for PageBuf {
159 fn deref_mut(&mut self) -> &mut Self::Target {
160 self.0.inner_mut()
161 }
162}
163
164impl PageBuf {
165 pub fn new_zeroed() -> PageBuf {
167 Self(PageBufInner::new_default())
168 }
169
170 pub fn from_inner(mut inner: PageBufInner) -> Self {
177 inner.extend_with(0);
178 Self(inner)
179 }
180}
181
182pub type HostPointer = u64;
185
186const _: () = assert!(size_of::<HostPointer>() >= size_of::<usize>());
187
188#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)]
190pub enum MemoryError {
191 #[display("Trying to access memory outside wasm program memory")]
193 AccessOutOfBounds,
194}
195
196pub trait Memory<Context> {
198 type GrowError: Debug;
200
201 fn grow(&self, ctx: &mut Context, pages: WasmPagesAmount) -> Result<(), Self::GrowError>;
203
204 fn size(&self, ctx: &Context) -> WasmPagesAmount;
206
207 fn write(&self, ctx: &mut Context, offset: u32, buffer: &[u8]) -> Result<(), MemoryError>;
209
210 fn read(&self, ctx: &Context, offset: u32, buffer: &mut [u8]) -> Result<(), MemoryError>;
212
213 fn get_buffer_host_addr(&self, ctx: &Context) -> Option<HostPointer> {
215 if self.size(ctx) == WasmPagesAmount::from(0) {
216 None
217 } else {
218 unsafe { Some(self.get_buffer_host_addr_unsafe(ctx)) }
221 }
222 }
223
224 unsafe fn get_buffer_host_addr_unsafe(&self, ctx: &Context) -> HostPointer;
229}
230
231#[derive(Debug)]
233pub struct AllocationsContext {
234 allocations: IntervalsTree<WasmPage>,
236 allocations_changed: bool,
238 heap: Option<Interval<WasmPage>>,
239 static_pages: WasmPagesAmount,
240}
241
242#[must_use]
244pub trait GrowHandler<Context> {
245 fn before_grow_action(ctx: &mut Context, mem: &mut impl Memory<Context>) -> Self;
247 fn after_grow_action(self, ctx: &mut Context, mem: &mut impl Memory<Context>);
249}
250
251pub struct NoopGrowHandler;
253
254impl<Context> GrowHandler<Context> for NoopGrowHandler {
255 fn before_grow_action(_ctx: &mut Context, _mem: &mut impl Memory<Context>) -> Self {
256 NoopGrowHandler
257 }
258 fn after_grow_action(self, _ctx: &mut Context, _mem: &mut impl Memory<Context>) {}
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
263pub enum MemorySetupError {
264 #[display("Memory size {memory_size:?} must be less than or equal to {max_pages:?}")]
266 MemorySizeExceedsMaxPages {
267 memory_size: WasmPagesAmount,
269 max_pages: WasmPagesAmount,
271 },
272 #[display("Memory size {memory_size:?} must be at least {static_pages:?}")]
274 InsufficientMemorySize {
275 memory_size: WasmPagesAmount,
277 static_pages: WasmPagesAmount,
279 },
280 #[display("Stack end {stack_end:?} is out of static memory 0..{static_pages:?}")]
282 StackEndOutOfStaticMemory {
283 stack_end: WasmPage,
285 static_pages: WasmPagesAmount,
287 },
288 #[display(
290 "Allocated page {page:?} is out of allowed memory interval {static_pages:?}..{memory_size:?}"
291 )]
292 AllocatedPageOutOfAllowedInterval {
293 page: WasmPage,
295 static_pages: WasmPagesAmount,
297 memory_size: WasmPagesAmount,
299 },
300}
301
302#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display, derive_more::From)]
304pub enum AllocError {
305 #[display("Trying to allocate more wasm program memory than allowed")]
308 ProgramAllocOutOfBounds,
309 #[display("{_0:?} cannot be freed by the current program")]
312 InvalidFree(WasmPage),
313 #[display("Invalid range {_0:?}..={_1:?} for free_range")]
315 InvalidFreeRange(WasmPage, WasmPage),
316 GasCharge(ChargeError),
318}
319
320impl AllocationsContext {
321 pub fn try_new(
329 memory_size: WasmPagesAmount,
330 allocations: IntervalsTree<WasmPage>,
331 static_pages: WasmPagesAmount,
332 stack_end: Option<WasmPage>,
333 max_pages: WasmPagesAmount,
334 ) -> Result<Self, MemorySetupError> {
335 Self::validate_memory_params(
336 memory_size,
337 &allocations,
338 static_pages,
339 stack_end,
340 max_pages,
341 )?;
342
343 let heap = match Interval::try_from(static_pages..max_pages) {
344 Ok(interval) => Some(interval),
345 Err(TryFromRangeError::EmptyRange) => None,
346 _ => unreachable!(),
348 };
349
350 Ok(Self {
351 allocations,
352 allocations_changed: false,
353 heap,
354 static_pages,
355 })
356 }
357
358 fn validate_memory_params(
361 memory_size: WasmPagesAmount,
362 allocations: &IntervalsTree<WasmPage>,
363 static_pages: WasmPagesAmount,
364 stack_end: Option<WasmPage>,
365 max_pages: WasmPagesAmount,
366 ) -> Result<(), MemorySetupError> {
367 if memory_size > max_pages {
368 return Err(MemorySetupError::MemorySizeExceedsMaxPages {
369 memory_size,
370 max_pages,
371 });
372 }
373
374 if static_pages > memory_size {
375 return Err(MemorySetupError::InsufficientMemorySize {
376 memory_size,
377 static_pages,
378 });
379 }
380
381 if let Some(stack_end) = stack_end {
382 if stack_end > static_pages {
383 return Err(MemorySetupError::StackEndOutOfStaticMemory {
384 stack_end,
385 static_pages,
386 });
387 }
388 }
389
390 if let Some(page) = allocations.end() {
391 if page >= memory_size {
392 return Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
393 page,
394 static_pages,
395 memory_size,
396 });
397 }
398 }
399 if let Some(page) = allocations.start() {
400 if page < static_pages {
401 return Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
402 page,
403 static_pages,
404 memory_size,
405 });
406 }
407 }
408
409 Ok(())
410 }
411
412 pub fn alloc<Context, G: GrowHandler<Context>>(
415 &mut self,
416 ctx: &mut Context,
417 mem: &mut impl Memory<Context>,
418 pages: WasmPagesAmount,
419 charge_gas_for_grow: impl FnOnce(WasmPagesAmount) -> Result<(), ChargeError>,
420 ) -> Result<WasmPage, AllocError> {
421 let heap = self.heap.ok_or(AllocError::ProgramAllocOutOfBounds)?;
424
425 if pages == WasmPage::from(0) {
427 return Ok(heap.start());
428 }
429
430 let interval = self
431 .allocations
432 .voids(heap)
433 .find_map(|void| {
434 Interval::<WasmPage>::with_len(void.start(), u32::from(pages))
435 .ok()
436 .and_then(|interval| (interval.end() <= void.end()).then_some(interval))
437 })
438 .ok_or(AllocError::ProgramAllocOutOfBounds)?;
439
440 if let Ok(grow) = Interval::<WasmPage>::try_from(mem.size(ctx)..interval.end().inc()) {
441 charge_gas_for_grow(grow.len())?;
442 let grow_handler = G::before_grow_action(ctx, mem);
443 mem.grow(ctx, grow.len()).unwrap_or_else(|err| {
444 let err_msg = format!(
445 "AllocationContext:alloc: Failed to grow memory. \
446 Got error - {err:?}",
447 );
448
449 log::error!("{err_msg}");
450 unreachable!("{err_msg}")
451 });
452 grow_handler.after_grow_action(ctx, mem);
453 }
454
455 self.allocations.insert(interval);
456 self.allocations_changed = true;
457
458 Ok(interval.start())
459 }
460
461 pub fn free(&mut self, page: WasmPage) -> Result<(), AllocError> {
463 if let Some(heap) = self.heap {
464 if page >= heap.start() && page <= heap.end() && self.allocations.remove(page) {
465 self.allocations_changed = true;
466 return Ok(());
467 }
468 }
469 Err(AllocError::InvalidFree(page))
470 }
471
472 pub fn free_range(&mut self, interval: Interval<WasmPage>) -> Result<(), AllocError> {
476 if let Some(heap) = self.heap {
477 if interval.start() >= heap.start() && interval.end() <= heap.end() {
479 if self.allocations.remove(interval) {
480 self.allocations_changed = true;
481 }
482
483 return Ok(());
484 }
485 }
486
487 Err(AllocError::InvalidFreeRange(
488 interval.start(),
489 interval.end(),
490 ))
491 }
492
493 pub fn into_parts(self) -> (WasmPagesAmount, IntervalsTree<WasmPage>, bool) {
495 (
496 self.static_pages,
497 self.allocations,
498 self.allocations_changed,
499 )
500 }
501}
502
503#[cfg(test)]
505mod tests {
506 use super::*;
507 use alloc::vec::Vec;
508 use core::{cell::Cell, iter};
509
510 struct TestMemory(Cell<WasmPagesAmount>);
511
512 impl TestMemory {
513 fn new(amount: WasmPagesAmount) -> Self {
514 Self(Cell::new(amount))
515 }
516 }
517
518 impl Memory<()> for TestMemory {
519 type GrowError = ();
520
521 fn grow(&self, _ctx: &mut (), pages: WasmPagesAmount) -> Result<(), Self::GrowError> {
522 let new_pages_amount = self.0.get().add(pages).ok_or(())?;
523 self.0.set(new_pages_amount);
524 Ok(())
525 }
526
527 fn size(&self, _ctx: &()) -> WasmPagesAmount {
528 self.0.get()
529 }
530
531 fn write(&self, _ctx: &mut (), _offset: u32, _buffer: &[u8]) -> Result<(), MemoryError> {
532 unimplemented!()
533 }
534
535 fn read(&self, _ctx: &(), _offset: u32, _buffer: &mut [u8]) -> Result<(), MemoryError> {
536 unimplemented!()
537 }
538
539 unsafe fn get_buffer_host_addr_unsafe(&self, _ctx: &()) -> HostPointer {
540 unimplemented!()
541 }
542 }
543
544 #[test]
545 fn page_buf() {
546 let _ = tracing_subscriber::fmt::try_init();
547
548 let mut data = PageBufInner::filled_with(199u8);
549 data.inner_mut()[1] = 2;
550 let page_buf = PageBuf::from_inner(data);
551 log::debug!("page buff = {:?}", page_buf);
552 }
553
554 #[test]
555 fn free_fails() {
556 let mut ctx =
557 AllocationsContext::try_new(0.into(), Default::default(), 0.into(), None, 0.into())
558 .unwrap();
559 assert_eq!(ctx.free(1.into()), Err(AllocError::InvalidFree(1.into())));
560
561 let mut ctx = AllocationsContext::try_new(
562 1.into(),
563 [WasmPage::from(0)].into_iter().collect(),
564 0.into(),
565 None,
566 1.into(),
567 )
568 .unwrap();
569 assert_eq!(ctx.free(1.into()), Err(AllocError::InvalidFree(1.into())));
570
571 let mut ctx = AllocationsContext::try_new(
572 4.into(),
573 [WasmPage::from(1), WasmPage::from(3)].into_iter().collect(),
574 1.into(),
575 None,
576 4.into(),
577 )
578 .unwrap();
579 let interval = Interval::<WasmPage>::try_from(1u16..4).unwrap();
580 assert_eq!(ctx.free_range(interval), Ok(()));
581 }
582
583 #[track_caller]
584 fn alloc_ok(ctx: &mut AllocationsContext, mem: &mut TestMemory, pages: u16, expected: u16) {
585 let res = ctx.alloc::<(), NoopGrowHandler>(&mut (), mem, pages.into(), |_| Ok(()));
586 assert_eq!(res, Ok(expected.into()));
587 }
588
589 #[track_caller]
590 fn alloc_err(ctx: &mut AllocationsContext, mem: &mut TestMemory, pages: u16, err: AllocError) {
591 let res = ctx.alloc::<(), NoopGrowHandler>(&mut (), mem, pages.into(), |_| Ok(()));
592 assert_eq!(res, Err(err));
593 }
594
595 #[test]
596 fn alloc() {
597 let _ = tracing_subscriber::fmt::try_init();
598
599 let mut ctx = AllocationsContext::try_new(
600 256.into(),
601 Default::default(),
602 16.into(),
603 None,
604 256.into(),
605 )
606 .unwrap();
607 let mut mem = TestMemory::new(16.into());
608 alloc_ok(&mut ctx, &mut mem, 16, 16);
609 alloc_ok(&mut ctx, &mut mem, 0, 16);
610
611 (2..16).for_each(|i| alloc_ok(&mut ctx, &mut mem, 16, i * 16));
613
614 alloc_err(&mut ctx, &mut mem, 16, AllocError::ProgramAllocOutOfBounds);
616
617 ctx.free(137.into()).unwrap();
619 alloc_ok(&mut ctx, &mut mem, 1, 137);
620
621 ctx.free(117.into()).unwrap();
623 ctx.free(118.into()).unwrap();
624 alloc_ok(&mut ctx, &mut mem, 2, 117);
625
626 let interval = Interval::<WasmPage>::try_from(117..119).unwrap();
628 ctx.free_range(interval).unwrap();
629 alloc_ok(&mut ctx, &mut mem, 2, 117);
630
631 ctx.free(117.into()).unwrap();
633 ctx.free(158.into()).unwrap();
634 alloc_err(&mut ctx, &mut mem, 2, AllocError::ProgramAllocOutOfBounds);
635 }
636
637 #[test]
638 fn memory_params_validation() {
639 assert_eq!(
640 AllocationsContext::validate_memory_params(
641 4.into(),
642 &iter::once(WasmPage::from(2)).collect(),
643 2.into(),
644 Some(2.into()),
645 4.into(),
646 ),
647 Ok(())
648 );
649
650 assert_eq!(
651 AllocationsContext::validate_memory_params(
652 4.into(),
653 &Default::default(),
654 2.into(),
655 Some(2.into()),
656 3.into(),
657 ),
658 Err(MemorySetupError::MemorySizeExceedsMaxPages {
659 memory_size: 4.into(),
660 max_pages: 3.into()
661 })
662 );
663
664 assert_eq!(
665 AllocationsContext::validate_memory_params(
666 1.into(),
667 &Default::default(),
668 2.into(),
669 Some(1.into()),
670 4.into(),
671 ),
672 Err(MemorySetupError::InsufficientMemorySize {
673 memory_size: 1.into(),
674 static_pages: 2.into()
675 })
676 );
677
678 assert_eq!(
679 AllocationsContext::validate_memory_params(
680 4.into(),
681 &Default::default(),
682 2.into(),
683 Some(3.into()),
684 4.into(),
685 ),
686 Err(MemorySetupError::StackEndOutOfStaticMemory {
687 stack_end: 3.into(),
688 static_pages: 2.into()
689 })
690 );
691
692 assert_eq!(
693 AllocationsContext::validate_memory_params(
694 4.into(),
695 &[WasmPage::from(1), WasmPage::from(3)].into_iter().collect(),
696 2.into(),
697 Some(2.into()),
698 4.into(),
699 ),
700 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
701 page: 1.into(),
702 static_pages: 2.into(),
703 memory_size: 4.into()
704 })
705 );
706
707 assert_eq!(
708 AllocationsContext::validate_memory_params(
709 4.into(),
710 &[WasmPage::from(2), WasmPage::from(4)].into_iter().collect(),
711 2.into(),
712 Some(2.into()),
713 4.into(),
714 ),
715 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
716 page: 4.into(),
717 static_pages: 2.into(),
718 memory_size: 4.into()
719 })
720 );
721
722 assert_eq!(
723 AllocationsContext::validate_memory_params(
724 13.into(),
725 &iter::once(WasmPage::from(1)).collect(),
726 10.into(),
727 None,
728 13.into()
729 ),
730 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
731 page: 1.into(),
732 static_pages: 10.into(),
733 memory_size: 13.into()
734 })
735 );
736
737 assert_eq!(
738 AllocationsContext::validate_memory_params(
739 13.into(),
740 &iter::once(WasmPage::from(1)).collect(),
741 WasmPagesAmount::UPPER,
742 None,
743 13.into()
744 ),
745 Err(MemorySetupError::InsufficientMemorySize {
746 memory_size: 13.into(),
747 static_pages: WasmPagesAmount::UPPER
748 })
749 );
750
751 assert_eq!(
752 AllocationsContext::validate_memory_params(
753 WasmPagesAmount::UPPER,
754 &iter::once(WasmPage::from(1)).collect(),
755 10.into(),
756 None,
757 WasmPagesAmount::UPPER,
758 ),
759 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
760 page: 1.into(),
761 static_pages: 10.into(),
762 memory_size: WasmPagesAmount::UPPER
763 })
764 );
765 }
766
767 #[test]
768 fn allocations_changed_correctness() {
769 let new_ctx = |allocations| {
770 AllocationsContext::try_new(16.into(), allocations, 0.into(), None, 16.into()).unwrap()
771 };
772
773 let mut ctx = new_ctx(Default::default());
775 assert!(
776 !ctx.allocations_changed,
777 "Expecting no changes after creation"
778 );
779 let mut mem = TestMemory::new(16.into());
780 alloc_ok(&mut ctx, &mut mem, 16, 0);
781 assert!(ctx.allocations_changed);
782
783 let (_, allocations, allocations_changed) = ctx.into_parts();
784 assert!(allocations_changed);
785
786 let mut ctx = new_ctx(allocations);
788 alloc_err(&mut ctx, &mut mem, 16, AllocError::ProgramAllocOutOfBounds);
789 assert!(
790 !ctx.allocations_changed,
791 "Expecting allocations don't change because of error"
792 );
793
794 assert!(ctx.free(16.into()).is_err());
796 assert!(!ctx.allocations_changed);
797
798 assert!(ctx.free(10.into()).is_ok());
800 assert!(ctx.allocations_changed);
801
802 let (_, allocations, allocations_changed) = ctx.into_parts();
803 assert!(allocations_changed);
804
805 let mut ctx = new_ctx(allocations);
808 let interval = Interval::<WasmPage>::try_from(10u16..12).unwrap();
809 assert!(ctx.free_range(interval).is_ok());
810 assert!(
811 ctx.allocations_changed,
812 "Expected value is `true` because the 11th page was freed from allocations."
813 );
814
815 let (_, allocations, allocations_changed) = ctx.into_parts();
816 assert!(allocations_changed);
817
818 let mut ctx = new_ctx(allocations);
820 let interval = Interval::<WasmPage>::try_from(0u16..17).unwrap();
821 assert!(ctx.free_range(interval).is_err());
822 assert!(!ctx.allocations_changed);
823 assert!(!ctx.into_parts().2);
824 }
825
826 mod property_tests {
827 use super::*;
828 use proptest::{
829 arbitrary::any,
830 collection::size_range,
831 prop_oneof, proptest,
832 strategy::{Just, Strategy},
833 test_runner::Config as ProptestConfig,
834 };
835
836 #[derive(Debug, Clone)]
837 enum Action {
838 Alloc { pages: WasmPagesAmount },
839 Free { page: WasmPage },
840 FreeRange { page: WasmPage, size: u8 },
841 }
842
843 fn actions() -> impl Strategy<Value = Vec<Action>> {
844 let action = prop_oneof![
845 wasm_pages_amount_with_range(0, 32).prop_map(|pages| Action::Alloc { pages }),
847 wasm_page().prop_map(|page| Action::Free { page }),
848 (wasm_page(), any::<u8>())
849 .prop_map(|(page, size)| Action::FreeRange { page, size }),
850 ];
851 proptest::collection::vec(action, 0..1024)
852 }
853
854 fn allocations(start: u16, end: u16) -> impl Strategy<Value = IntervalsTree<WasmPage>> {
855 proptest::collection::btree_set(wasm_page_with_range(start, end), size_range(0..1024))
856 .prop_map(|pages| pages.into_iter().collect::<IntervalsTree<WasmPage>>())
857 }
858
859 fn wasm_page_with_range(start: u16, end: u16) -> impl Strategy<Value = WasmPage> {
860 (start..=end).prop_map(WasmPage::from)
861 }
862
863 fn wasm_page() -> impl Strategy<Value = WasmPage> {
864 wasm_page_with_range(0, u16::MAX)
865 }
866
867 fn wasm_pages_amount_with_range(
868 start: u32,
869 end: u32,
870 ) -> impl Strategy<Value = WasmPagesAmount> {
871 (start..=end).prop_map(|x| {
872 if x == u16::MAX as u32 + 1 {
873 WasmPagesAmount::UPPER
874 } else {
875 WasmPagesAmount::from(x as u16)
876 }
877 })
878 }
879
880 fn wasm_pages_amount() -> impl Strategy<Value = WasmPagesAmount> {
881 wasm_pages_amount_with_range(0, u16::MAX as u32 + 1)
882 }
883
884 #[derive(Debug)]
885 struct MemoryParams {
886 max_pages: WasmPagesAmount,
887 mem_size: WasmPagesAmount,
888 static_pages: WasmPagesAmount,
889 allocations: IntervalsTree<WasmPage>,
890 }
891
892 fn combined_memory_params() -> impl Strategy<Value = MemoryParams> {
894 wasm_pages_amount()
895 .prop_flat_map(|max_pages| {
896 let mem_size = wasm_pages_amount_with_range(0, u32::from(max_pages));
897 (Just(max_pages), mem_size)
898 })
899 .prop_flat_map(|(max_pages, mem_size)| {
900 let static_pages = wasm_pages_amount_with_range(0, u32::from(mem_size));
901 (Just(max_pages), Just(mem_size), static_pages)
902 })
903 .prop_filter(
904 "filter out cases where allocation region has zero size",
905 |(_max_pages, mem_size, static_pages)| static_pages < mem_size,
906 )
907 .prop_flat_map(|(max_pages, mem_size, static_pages)| {
908 let end_exclusive = u32::from(mem_size) - 1;
910 (
911 Just(max_pages),
912 Just(mem_size),
913 Just(static_pages),
914 allocations(u32::from(static_pages) as u16, end_exclusive as u16),
915 )
916 })
917 .prop_map(
918 |(max_pages, mem_size, static_pages, allocations)| MemoryParams {
919 max_pages,
920 mem_size,
921 static_pages,
922 allocations,
923 },
924 )
925 }
926
927 fn proptest_config() -> ProptestConfig {
928 ProptestConfig {
929 cases: 1024,
930 ..Default::default()
931 }
932 }
933
934 #[track_caller]
935 fn assert_free_error(err: AllocError) {
936 match err {
937 AllocError::InvalidFree(_) => {}
938 AllocError::InvalidFreeRange(_, _) => {}
939 err => panic!("{err:?}"),
940 }
941 }
942
943 proptest! {
944 #![proptest_config(proptest_config())]
945 #[test]
946 fn alloc(
947 mem_params in combined_memory_params(),
948 actions in actions(),
949 ) {
950 let _ = tracing_subscriber::fmt::try_init();
951
952 let MemoryParams{max_pages, mem_size, static_pages, allocations} = mem_params;
953 let mut ctx = AllocationsContext::try_new(mem_size, allocations, static_pages, None, max_pages).unwrap();
954
955 let mut mem = TestMemory::new(mem_size);
956
957 for action in actions {
958 match action {
959 Action::Alloc { pages } => {
960 match ctx.alloc::<_, NoopGrowHandler>(&mut (), &mut mem, pages, |_| Ok(())) {
961 Err(AllocError::ProgramAllocOutOfBounds) => {
962 let x = mem.size(&()).add(pages);
963 assert!(x.is_none() || x.unwrap() > max_pages);
964 }
965 Ok(page) => {
966 assert!(pages == WasmPagesAmount::from(0) || (page >= static_pages && page < max_pages));
967 assert!(mem.size(&()) <= max_pages);
968 assert!(WasmPagesAmount::from(page).add(pages).unwrap() <= mem.size(&()));
969 }
970 Err(err) => panic!("{err:?}"),
971 }
972 }
973 Action::Free { page } => {
974 if let Err(err) = ctx.free(page) {
975 assert_free_error(err);
976 }
977 }
978 Action::FreeRange { page, size } => {
979 if let Ok(interval) = Interval::<WasmPage>::with_len(page, size as u32) {
980 let _ = ctx.free_range(interval).map_err(assert_free_error);
981 }
982 }
983 }
984 }
985 }
986 }
987 }
988}