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 TypeInfo,
39 scale::{self, Decode, Encode, EncodeLike, Input, Output},
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, Hash, 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, Copy, 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 && stack_end > static_pages
383 {
384 return Err(MemorySetupError::StackEndOutOfStaticMemory {
385 stack_end,
386 static_pages,
387 });
388 }
389
390 if let Some(page) = allocations.end()
391 && page >= memory_size
392 {
393 return Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
394 page,
395 static_pages,
396 memory_size,
397 });
398 }
399 if let Some(page) = allocations.start()
400 && page < static_pages
401 {
402 return Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
403 page,
404 static_pages,
405 memory_size,
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 && page >= heap.start()
465 && page <= heap.end()
466 && self.allocations.remove(page)
467 {
468 self.allocations_changed = true;
469 return Ok(());
470 }
471
472 Err(AllocError::InvalidFree(page))
473 }
474
475 pub fn free_range(&mut self, interval: Interval<WasmPage>) -> Result<(), AllocError> {
479 if let Some(heap) = self.heap {
480 if interval.start() >= heap.start() && interval.end() <= heap.end() {
482 if self.allocations.remove(interval) {
483 self.allocations_changed = true;
484 }
485
486 return Ok(());
487 }
488 }
489
490 Err(AllocError::InvalidFreeRange(
491 interval.start(),
492 interval.end(),
493 ))
494 }
495
496 pub fn into_parts(self) -> (WasmPagesAmount, IntervalsTree<WasmPage>, bool) {
498 (
499 self.static_pages,
500 self.allocations,
501 self.allocations_changed,
502 )
503 }
504}
505
506#[cfg(test)]
508mod tests {
509 use super::*;
510 use alloc::vec::Vec;
511 use core::{cell::Cell, iter};
512
513 struct TestMemory(Cell<WasmPagesAmount>);
514
515 impl TestMemory {
516 fn new(amount: WasmPagesAmount) -> Self {
517 Self(Cell::new(amount))
518 }
519 }
520
521 impl Memory<()> for TestMemory {
522 type GrowError = ();
523
524 fn grow(&self, _ctx: &mut (), pages: WasmPagesAmount) -> Result<(), Self::GrowError> {
525 let new_pages_amount = self.0.get().add(pages).ok_or(())?;
526 self.0.set(new_pages_amount);
527 Ok(())
528 }
529
530 fn size(&self, _ctx: &()) -> WasmPagesAmount {
531 self.0.get()
532 }
533
534 fn write(&self, _ctx: &mut (), _offset: u32, _buffer: &[u8]) -> Result<(), MemoryError> {
535 unimplemented!()
536 }
537
538 fn read(&self, _ctx: &(), _offset: u32, _buffer: &mut [u8]) -> Result<(), MemoryError> {
539 unimplemented!()
540 }
541
542 unsafe fn get_buffer_host_addr_unsafe(&self, _ctx: &()) -> HostPointer {
543 unimplemented!()
544 }
545 }
546
547 #[test]
548 fn page_buf() {
549 let _ = tracing_subscriber::fmt::try_init();
550
551 let mut data = PageBufInner::filled_with(199u8);
552 data.inner_mut()[1] = 2;
553 let page_buf = PageBuf::from_inner(data);
554 log::debug!("page buff = {page_buf:?}");
555 }
556
557 #[test]
558 fn free_fails() {
559 let mut ctx =
560 AllocationsContext::try_new(0.into(), Default::default(), 0.into(), None, 0.into())
561 .unwrap();
562 assert_eq!(ctx.free(1.into()), Err(AllocError::InvalidFree(1.into())));
563
564 let mut ctx = AllocationsContext::try_new(
565 1.into(),
566 [WasmPage::from(0)].into_iter().collect(),
567 0.into(),
568 None,
569 1.into(),
570 )
571 .unwrap();
572 assert_eq!(ctx.free(1.into()), Err(AllocError::InvalidFree(1.into())));
573
574 let mut ctx = AllocationsContext::try_new(
575 4.into(),
576 [WasmPage::from(1), WasmPage::from(3)].into_iter().collect(),
577 1.into(),
578 None,
579 4.into(),
580 )
581 .unwrap();
582 let interval = Interval::<WasmPage>::try_from(1u16..4).unwrap();
583 assert_eq!(ctx.free_range(interval), Ok(()));
584 }
585
586 #[track_caller]
587 fn alloc_ok(ctx: &mut AllocationsContext, mem: &mut TestMemory, pages: u16, expected: u16) {
588 let res = ctx.alloc::<(), NoopGrowHandler>(&mut (), mem, pages.into(), |_| Ok(()));
589 assert_eq!(res, Ok(expected.into()));
590 }
591
592 #[track_caller]
593 fn alloc_err(ctx: &mut AllocationsContext, mem: &mut TestMemory, pages: u16, err: AllocError) {
594 let res = ctx.alloc::<(), NoopGrowHandler>(&mut (), mem, pages.into(), |_| Ok(()));
595 assert_eq!(res, Err(err));
596 }
597
598 #[test]
599 fn alloc() {
600 let _ = tracing_subscriber::fmt::try_init();
601
602 let mut ctx = AllocationsContext::try_new(
603 256.into(),
604 Default::default(),
605 16.into(),
606 None,
607 256.into(),
608 )
609 .unwrap();
610 let mut mem = TestMemory::new(16.into());
611 alloc_ok(&mut ctx, &mut mem, 16, 16);
612 alloc_ok(&mut ctx, &mut mem, 0, 16);
613
614 (2..16).for_each(|i| alloc_ok(&mut ctx, &mut mem, 16, i * 16));
616
617 alloc_err(&mut ctx, &mut mem, 16, AllocError::ProgramAllocOutOfBounds);
619
620 ctx.free(137.into()).unwrap();
622 alloc_ok(&mut ctx, &mut mem, 1, 137);
623
624 ctx.free(117.into()).unwrap();
626 ctx.free(118.into()).unwrap();
627 alloc_ok(&mut ctx, &mut mem, 2, 117);
628
629 let interval = Interval::<WasmPage>::try_from(117..119).unwrap();
631 ctx.free_range(interval).unwrap();
632 alloc_ok(&mut ctx, &mut mem, 2, 117);
633
634 ctx.free(117.into()).unwrap();
636 ctx.free(158.into()).unwrap();
637 alloc_err(&mut ctx, &mut mem, 2, AllocError::ProgramAllocOutOfBounds);
638 }
639
640 #[test]
641 fn memory_params_validation() {
642 assert_eq!(
643 AllocationsContext::validate_memory_params(
644 4.into(),
645 &iter::once(WasmPage::from(2)).collect(),
646 2.into(),
647 Some(2.into()),
648 4.into(),
649 ),
650 Ok(())
651 );
652
653 assert_eq!(
654 AllocationsContext::validate_memory_params(
655 4.into(),
656 &Default::default(),
657 2.into(),
658 Some(2.into()),
659 3.into(),
660 ),
661 Err(MemorySetupError::MemorySizeExceedsMaxPages {
662 memory_size: 4.into(),
663 max_pages: 3.into()
664 })
665 );
666
667 assert_eq!(
668 AllocationsContext::validate_memory_params(
669 1.into(),
670 &Default::default(),
671 2.into(),
672 Some(1.into()),
673 4.into(),
674 ),
675 Err(MemorySetupError::InsufficientMemorySize {
676 memory_size: 1.into(),
677 static_pages: 2.into()
678 })
679 );
680
681 assert_eq!(
682 AllocationsContext::validate_memory_params(
683 4.into(),
684 &Default::default(),
685 2.into(),
686 Some(3.into()),
687 4.into(),
688 ),
689 Err(MemorySetupError::StackEndOutOfStaticMemory {
690 stack_end: 3.into(),
691 static_pages: 2.into()
692 })
693 );
694
695 assert_eq!(
696 AllocationsContext::validate_memory_params(
697 4.into(),
698 &[WasmPage::from(1), WasmPage::from(3)].into_iter().collect(),
699 2.into(),
700 Some(2.into()),
701 4.into(),
702 ),
703 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
704 page: 1.into(),
705 static_pages: 2.into(),
706 memory_size: 4.into()
707 })
708 );
709
710 assert_eq!(
711 AllocationsContext::validate_memory_params(
712 4.into(),
713 &[WasmPage::from(2), WasmPage::from(4)].into_iter().collect(),
714 2.into(),
715 Some(2.into()),
716 4.into(),
717 ),
718 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
719 page: 4.into(),
720 static_pages: 2.into(),
721 memory_size: 4.into()
722 })
723 );
724
725 assert_eq!(
726 AllocationsContext::validate_memory_params(
727 13.into(),
728 &iter::once(WasmPage::from(1)).collect(),
729 10.into(),
730 None,
731 13.into()
732 ),
733 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
734 page: 1.into(),
735 static_pages: 10.into(),
736 memory_size: 13.into()
737 })
738 );
739
740 assert_eq!(
741 AllocationsContext::validate_memory_params(
742 13.into(),
743 &iter::once(WasmPage::from(1)).collect(),
744 WasmPagesAmount::UPPER,
745 None,
746 13.into()
747 ),
748 Err(MemorySetupError::InsufficientMemorySize {
749 memory_size: 13.into(),
750 static_pages: WasmPagesAmount::UPPER
751 })
752 );
753
754 assert_eq!(
755 AllocationsContext::validate_memory_params(
756 WasmPagesAmount::UPPER,
757 &iter::once(WasmPage::from(1)).collect(),
758 10.into(),
759 None,
760 WasmPagesAmount::UPPER,
761 ),
762 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
763 page: 1.into(),
764 static_pages: 10.into(),
765 memory_size: WasmPagesAmount::UPPER
766 })
767 );
768 }
769
770 #[test]
771 fn allocations_changed_correctness() {
772 let new_ctx = |allocations| {
773 AllocationsContext::try_new(16.into(), allocations, 0.into(), None, 16.into()).unwrap()
774 };
775
776 let mut ctx = new_ctx(Default::default());
778 assert!(
779 !ctx.allocations_changed,
780 "Expecting no changes after creation"
781 );
782 let mut mem = TestMemory::new(16.into());
783 alloc_ok(&mut ctx, &mut mem, 16, 0);
784 assert!(ctx.allocations_changed);
785
786 let (_, allocations, allocations_changed) = ctx.into_parts();
787 assert!(allocations_changed);
788
789 let mut ctx = new_ctx(allocations);
791 alloc_err(&mut ctx, &mut mem, 16, AllocError::ProgramAllocOutOfBounds);
792 assert!(
793 !ctx.allocations_changed,
794 "Expecting allocations don't change because of error"
795 );
796
797 assert!(ctx.free(16.into()).is_err());
799 assert!(!ctx.allocations_changed);
800
801 assert!(ctx.free(10.into()).is_ok());
803 assert!(ctx.allocations_changed);
804
805 let (_, allocations, allocations_changed) = ctx.into_parts();
806 assert!(allocations_changed);
807
808 let mut ctx = new_ctx(allocations);
811 let interval = Interval::<WasmPage>::try_from(10u16..12).unwrap();
812 assert!(ctx.free_range(interval).is_ok());
813 assert!(
814 ctx.allocations_changed,
815 "Expected value is `true` because the 11th page was freed from allocations."
816 );
817
818 let (_, allocations, allocations_changed) = ctx.into_parts();
819 assert!(allocations_changed);
820
821 let mut ctx = new_ctx(allocations);
823 let interval = Interval::<WasmPage>::try_from(0u16..17).unwrap();
824 assert!(ctx.free_range(interval).is_err());
825 assert!(!ctx.allocations_changed);
826 assert!(!ctx.into_parts().2);
827 }
828
829 mod property_tests {
830 use super::*;
831 use proptest::{
832 arbitrary::any,
833 collection::size_range,
834 prop_oneof, proptest,
835 strategy::{Just, Strategy},
836 test_runner::Config as ProptestConfig,
837 };
838
839 #[derive(Debug, Clone)]
840 enum Action {
841 Alloc { pages: WasmPagesAmount },
842 Free { page: WasmPage },
843 FreeRange { page: WasmPage, size: u8 },
844 }
845
846 fn actions() -> impl Strategy<Value = Vec<Action>> {
847 let action = prop_oneof![
848 wasm_pages_amount_with_range(0, 32).prop_map(|pages| Action::Alloc { pages }),
850 wasm_page().prop_map(|page| Action::Free { page }),
851 (wasm_page(), any::<u8>())
852 .prop_map(|(page, size)| Action::FreeRange { page, size }),
853 ];
854 proptest::collection::vec(action, 0..1024)
855 }
856
857 fn allocations(start: u16, end: u16) -> impl Strategy<Value = IntervalsTree<WasmPage>> {
858 proptest::collection::btree_set(wasm_page_with_range(start, end), size_range(0..1024))
859 .prop_map(|pages| pages.into_iter().collect::<IntervalsTree<WasmPage>>())
860 }
861
862 fn wasm_page_with_range(start: u16, end: u16) -> impl Strategy<Value = WasmPage> {
863 (start..=end).prop_map(WasmPage::from)
864 }
865
866 fn wasm_page() -> impl Strategy<Value = WasmPage> {
867 wasm_page_with_range(0, u16::MAX)
868 }
869
870 fn wasm_pages_amount_with_range(
871 start: u32,
872 end: u32,
873 ) -> impl Strategy<Value = WasmPagesAmount> {
874 (start..=end).prop_map(|x| {
875 if x == u16::MAX as u32 + 1 {
876 WasmPagesAmount::UPPER
877 } else {
878 WasmPagesAmount::from(x as u16)
879 }
880 })
881 }
882
883 fn wasm_pages_amount() -> impl Strategy<Value = WasmPagesAmount> {
884 wasm_pages_amount_with_range(0, u16::MAX as u32 + 1)
885 }
886
887 #[derive(Debug)]
888 struct MemoryParams {
889 max_pages: WasmPagesAmount,
890 mem_size: WasmPagesAmount,
891 static_pages: WasmPagesAmount,
892 allocations: IntervalsTree<WasmPage>,
893 }
894
895 fn combined_memory_params() -> impl Strategy<Value = MemoryParams> {
897 wasm_pages_amount()
898 .prop_flat_map(|max_pages| {
899 let mem_size = wasm_pages_amount_with_range(0, u32::from(max_pages));
900 (Just(max_pages), mem_size)
901 })
902 .prop_flat_map(|(max_pages, mem_size)| {
903 let static_pages = wasm_pages_amount_with_range(0, u32::from(mem_size));
904 (Just(max_pages), Just(mem_size), static_pages)
905 })
906 .prop_filter(
907 "filter out cases where allocation region has zero size",
908 |(_max_pages, mem_size, static_pages)| static_pages < mem_size,
909 )
910 .prop_flat_map(|(max_pages, mem_size, static_pages)| {
911 let end_exclusive = u32::from(mem_size) - 1;
913 (
914 Just(max_pages),
915 Just(mem_size),
916 Just(static_pages),
917 allocations(u32::from(static_pages) as u16, end_exclusive as u16),
918 )
919 })
920 .prop_map(
921 |(max_pages, mem_size, static_pages, allocations)| MemoryParams {
922 max_pages,
923 mem_size,
924 static_pages,
925 allocations,
926 },
927 )
928 }
929
930 fn proptest_config() -> ProptestConfig {
931 ProptestConfig {
932 cases: 1024,
933 ..Default::default()
934 }
935 }
936
937 #[track_caller]
938 fn assert_free_error(err: AllocError) {
939 match err {
940 AllocError::InvalidFree(_) => {}
941 AllocError::InvalidFreeRange(_, _) => {}
942 err => panic!("{err:?}"),
943 }
944 }
945
946 proptest! {
947 #![proptest_config(proptest_config())]
948 #[test]
949 fn alloc(
950 mem_params in combined_memory_params(),
951 actions in actions(),
952 ) {
953 let _ = tracing_subscriber::fmt::try_init();
954
955 let MemoryParams{max_pages, mem_size, static_pages, allocations} = mem_params;
956 let mut ctx = AllocationsContext::try_new(mem_size, allocations, static_pages, None, max_pages).unwrap();
957
958 let mut mem = TestMemory::new(mem_size);
959
960 for action in actions {
961 match action {
962 Action::Alloc { pages } => {
963 match ctx.alloc::<_, NoopGrowHandler>(&mut (), &mut mem, pages, |_| Ok(())) {
964 Err(AllocError::ProgramAllocOutOfBounds) => {
965 let x = mem.size(&()).add(pages);
966 assert!(x.is_none() || x.unwrap() > max_pages);
967 }
968 Ok(page) => {
969 assert!(pages == WasmPagesAmount::from(0) || (page >= static_pages && page < max_pages));
970 assert!(mem.size(&()) <= max_pages);
971 assert!(WasmPagesAmount::from(page).add(pages).unwrap() <= mem.size(&()));
972 }
973 Err(err) => panic!("{err:?}"),
974 }
975 }
976 Action::Free { page } => {
977 if let Err(err) = ctx.free(page) {
978 assert_free_error(err);
979 }
980 }
981 Action::FreeRange { page, size } => {
982 if let Ok(interval) = Interval::<WasmPage>::with_len(page, size as u32) {
983 let _ = ctx.free_range(interval).map_err(assert_free_error);
984 }
985 }
986 }
987 }
988 }
989 }
990 }
991}