1use crate::{
22 gas::ChargeError,
23 pages::{GearPage, WasmPage, WasmPagesAmount},
24};
25use alloc::{boxed::Box, format};
26use byteorder::{ByteOrder, LittleEndian};
27use core::{
28 fmt::{self, Debug},
29 ops::{Deref, DerefMut},
30};
31use numerated::{
32 interval::{Interval, TryFromRangeError},
33 tree::IntervalsTree,
34};
35use scale_decode::DecodeAsType;
36use scale_encode::EncodeAsType;
37use scale_info::{
38 TypeInfo,
39 scale::{Decode, Encode},
40};
41
42#[derive(Clone, Copy, Eq, PartialEq, Encode, EncodeAsType, Decode, DecodeAsType)]
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
106const _: () = {
110 assert!((GearPage::SIZE as usize).is_multiple_of(size_of::<u64>()));
111 assert!(GearPage::SIZE as usize / size_of::<u64>() <= 2048);
112};
113
114pub type PageBufInner = Box<[u64; GearPage::SIZE as usize / size_of::<u64>()]>;
116
117#[derive(
119 Clone,
120 PartialEq,
121 Eq,
122 PartialOrd,
123 Ord,
124 Hash,
125 Encode,
126 EncodeAsType,
127 Decode,
128 DecodeAsType,
129 TypeInfo,
130)]
131pub struct PageBuf(PageBufInner);
132
133impl Debug for PageBuf {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(
136 f,
137 "PageBuf({:?}..{:?})",
138 &self.0[0..10],
139 &self.0[GearPage::SIZE as usize - 10..GearPage::SIZE as usize]
140 )
141 }
142}
143
144impl Deref for PageBuf {
145 type Target = [u8];
146 fn deref(&self) -> &Self::Target {
147 bytemuck::must_cast_slice(&*self.0)
148 }
149}
150
151impl DerefMut for PageBuf {
152 fn deref_mut(&mut self) -> &mut Self::Target {
153 bytemuck::must_cast_slice_mut(&mut *self.0)
154 }
155}
156
157impl AsRef<[u8; GearPage::SIZE as usize]> for PageBuf {
158 fn as_ref(&self) -> &[u8; GearPage::SIZE as usize] {
159 bytemuck::must_cast_ref(&*self.0)
160 }
161}
162
163impl AsMut<[u8; GearPage::SIZE as usize]> for PageBuf {
164 fn as_mut(&mut self) -> &mut [u8; GearPage::SIZE as usize] {
165 bytemuck::must_cast_mut(&mut *self.0)
166 }
167}
168
169impl PageBuf {
170 pub fn new_zeroed() -> PageBuf {
172 Self::filled_with(0)
173 }
174
175 pub fn filled_with(byte: u8) -> PageBuf {
177 let chunk = u64::from_ne_bytes([byte; 8]);
178 Self([chunk; GearPage::SIZE as usize / size_of::<u64>()].into())
179 }
180
181 pub fn from_inner(inner: PageBufInner) -> Self {
188 Self(inner)
189 }
190}
191
192pub type HostPointer = u64;
195
196const _: () = assert!(size_of::<HostPointer>() >= size_of::<usize>());
197
198#[derive(Debug, Copy, Clone, Eq, PartialEq, derive_more::Display)]
200pub enum MemoryError {
201 #[display("Trying to access memory outside wasm program memory")]
203 AccessOutOfBounds,
204}
205
206pub trait Memory<Context> {
208 type GrowError: Debug;
210
211 fn grow(&self, ctx: &mut Context, pages: WasmPagesAmount) -> Result<(), Self::GrowError>;
213
214 fn size(&self, ctx: &Context) -> WasmPagesAmount;
216
217 fn write(&self, ctx: &mut Context, offset: u32, buffer: &[u8]) -> Result<(), MemoryError>;
219
220 fn read(&self, ctx: &Context, offset: u32, buffer: &mut [u8]) -> Result<(), MemoryError>;
222
223 fn get_buffer_host_addr(&self, ctx: &Context) -> Option<HostPointer> {
225 if self.size(ctx) == WasmPagesAmount::from(0) {
226 None
227 } else {
228 unsafe { Some(self.get_buffer_host_addr_unsafe(ctx)) }
231 }
232 }
233
234 unsafe fn get_buffer_host_addr_unsafe(&self, ctx: &Context) -> HostPointer;
239}
240
241#[derive(Debug)]
243pub struct AllocationsContext {
244 allocations: IntervalsTree<WasmPage>,
246 allocations_changed: bool,
248 heap: Option<Interval<WasmPage>>,
249 static_pages: WasmPagesAmount,
250}
251
252#[must_use]
254pub trait GrowHandler<Context> {
255 fn before_grow_action(ctx: &mut Context, mem: &mut impl Memory<Context>) -> Self;
257 fn after_grow_action(self, ctx: &mut Context, mem: &mut impl Memory<Context>);
259}
260
261pub struct NoopGrowHandler;
263
264impl<Context> GrowHandler<Context> for NoopGrowHandler {
265 fn before_grow_action(_ctx: &mut Context, _mem: &mut impl Memory<Context>) -> Self {
266 NoopGrowHandler
267 }
268 fn after_grow_action(self, _ctx: &mut Context, _mem: &mut impl Memory<Context>) {}
269}
270
271#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
273pub enum MemorySetupError {
274 #[display("Memory size {memory_size:?} must be less than or equal to {max_pages:?}")]
276 MemorySizeExceedsMaxPages {
277 memory_size: WasmPagesAmount,
279 max_pages: WasmPagesAmount,
281 },
282 #[display("Memory size {memory_size:?} must be at least {static_pages:?}")]
284 InsufficientMemorySize {
285 memory_size: WasmPagesAmount,
287 static_pages: WasmPagesAmount,
289 },
290 #[display("Stack end {stack_end:?} is out of static memory 0..{static_pages:?}")]
292 StackEndOutOfStaticMemory {
293 stack_end: WasmPage,
295 static_pages: WasmPagesAmount,
297 },
298 #[display(
300 "Allocated page {page:?} is out of allowed memory interval {static_pages:?}..{memory_size:?}"
301 )]
302 AllocatedPageOutOfAllowedInterval {
303 page: WasmPage,
305 static_pages: WasmPagesAmount,
307 memory_size: WasmPagesAmount,
309 },
310}
311
312#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display, derive_more::From)]
314pub enum AllocError {
315 #[display("Trying to allocate more wasm program memory than allowed")]
318 ProgramAllocOutOfBounds,
319 #[display("{_0:?} cannot be freed by the current program")]
322 InvalidFree(WasmPage),
323 #[display("Invalid range {_0:?}..={_1:?} for free_range")]
325 InvalidFreeRange(WasmPage, WasmPage),
326 GasCharge(ChargeError),
328}
329
330impl AllocationsContext {
331 pub fn try_new(
339 memory_size: WasmPagesAmount,
340 allocations: IntervalsTree<WasmPage>,
341 static_pages: WasmPagesAmount,
342 stack_end: Option<WasmPage>,
343 max_pages: WasmPagesAmount,
344 ) -> Result<Self, MemorySetupError> {
345 Self::validate_memory_params(
346 memory_size,
347 &allocations,
348 static_pages,
349 stack_end,
350 max_pages,
351 )?;
352
353 let heap = match Interval::try_from(static_pages..max_pages) {
354 Ok(interval) => Some(interval),
355 Err(TryFromRangeError::EmptyRange) => None,
356 _ => unreachable!(),
358 };
359
360 Ok(Self {
361 allocations,
362 allocations_changed: false,
363 heap,
364 static_pages,
365 })
366 }
367
368 fn validate_memory_params(
371 memory_size: WasmPagesAmount,
372 allocations: &IntervalsTree<WasmPage>,
373 static_pages: WasmPagesAmount,
374 stack_end: Option<WasmPage>,
375 max_pages: WasmPagesAmount,
376 ) -> Result<(), MemorySetupError> {
377 if memory_size > max_pages {
378 return Err(MemorySetupError::MemorySizeExceedsMaxPages {
379 memory_size,
380 max_pages,
381 });
382 }
383
384 if static_pages > memory_size {
385 return Err(MemorySetupError::InsufficientMemorySize {
386 memory_size,
387 static_pages,
388 });
389 }
390
391 if let Some(stack_end) = stack_end
392 && stack_end > static_pages
393 {
394 return Err(MemorySetupError::StackEndOutOfStaticMemory {
395 stack_end,
396 static_pages,
397 });
398 }
399
400 if let Some(page) = allocations.end()
401 && page >= memory_size
402 {
403 return Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
404 page,
405 static_pages,
406 memory_size,
407 });
408 }
409 if let Some(page) = allocations.start()
410 && page < static_pages
411 {
412 return Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
413 page,
414 static_pages,
415 memory_size,
416 });
417 }
418
419 Ok(())
420 }
421
422 pub fn alloc<Context, G: GrowHandler<Context>>(
425 &mut self,
426 ctx: &mut Context,
427 mem: &mut impl Memory<Context>,
428 pages: WasmPagesAmount,
429 charge_gas_for_grow: impl FnOnce(WasmPagesAmount) -> Result<(), ChargeError>,
430 ) -> Result<WasmPage, AllocError> {
431 let heap = self.heap.ok_or(AllocError::ProgramAllocOutOfBounds)?;
434
435 if pages == WasmPage::from(0) {
437 return Ok(heap.start());
438 }
439
440 let interval = self
441 .allocations
442 .voids(heap)
443 .find_map(|void| {
444 Interval::<WasmPage>::with_len(void.start(), u32::from(pages))
445 .ok()
446 .and_then(|interval| (interval.end() <= void.end()).then_some(interval))
447 })
448 .ok_or(AllocError::ProgramAllocOutOfBounds)?;
449
450 if let Ok(grow) = Interval::<WasmPage>::try_from(mem.size(ctx)..interval.end().inc()) {
451 charge_gas_for_grow(grow.len())?;
452 let grow_handler = G::before_grow_action(ctx, mem);
453 mem.grow(ctx, grow.len()).unwrap_or_else(|err| {
454 let err_msg = format!(
455 "AllocationContext:alloc: Failed to grow memory. \
456 Got error - {err:?}",
457 );
458
459 log::error!("{err_msg}");
460 unreachable!("{err_msg}")
461 });
462 grow_handler.after_grow_action(ctx, mem);
463 }
464
465 self.allocations.insert(interval);
466 self.allocations_changed = true;
467
468 Ok(interval.start())
469 }
470
471 pub fn free(&mut self, page: WasmPage) -> Result<(), AllocError> {
473 if let Some(heap) = self.heap
474 && page >= heap.start()
475 && page <= heap.end()
476 && self.allocations.remove(page)
477 {
478 self.allocations_changed = true;
479 return Ok(());
480 }
481
482 Err(AllocError::InvalidFree(page))
483 }
484
485 pub fn free_range(&mut self, interval: Interval<WasmPage>) -> Result<(), AllocError> {
489 if let Some(heap) = self.heap {
490 if interval.start() >= heap.start() && interval.end() <= heap.end() {
492 if self.allocations.remove(interval) {
493 self.allocations_changed = true;
494 }
495
496 return Ok(());
497 }
498 }
499
500 Err(AllocError::InvalidFreeRange(
501 interval.start(),
502 interval.end(),
503 ))
504 }
505
506 pub fn into_parts(self) -> (WasmPagesAmount, IntervalsTree<WasmPage>, bool) {
508 (
509 self.static_pages,
510 self.allocations,
511 self.allocations_changed,
512 )
513 }
514}
515
516#[cfg(test)]
518mod tests {
519 use super::*;
520 use alloc::{vec, vec::Vec};
521 use core::{cell::Cell, iter};
522
523 struct TestMemory(Cell<WasmPagesAmount>);
524
525 impl TestMemory {
526 fn new(amount: WasmPagesAmount) -> Self {
527 Self(Cell::new(amount))
528 }
529 }
530
531 impl Memory<()> for TestMemory {
532 type GrowError = ();
533
534 fn grow(&self, _ctx: &mut (), pages: WasmPagesAmount) -> Result<(), Self::GrowError> {
535 let new_pages_amount = self.0.get().add(pages).ok_or(())?;
536 self.0.set(new_pages_amount);
537 Ok(())
538 }
539
540 fn size(&self, _ctx: &()) -> WasmPagesAmount {
541 self.0.get()
542 }
543
544 fn write(&self, _ctx: &mut (), _offset: u32, _buffer: &[u8]) -> Result<(), MemoryError> {
545 unimplemented!()
546 }
547
548 fn read(&self, _ctx: &(), _offset: u32, _buffer: &mut [u8]) -> Result<(), MemoryError> {
549 unimplemented!()
550 }
551
552 unsafe fn get_buffer_host_addr_unsafe(&self, _ctx: &()) -> HostPointer {
553 unimplemented!()
554 }
555 }
556
557 #[test]
558 fn page_buf() {
559 let _ = tracing_subscriber::fmt::try_init();
560
561 let mut page_buf = PageBuf::filled_with(199);
562 page_buf[1] = 2;
563 log::debug!("page buff = {page_buf:?}");
564 }
565
566 #[test]
567 fn page_buf_encode() {
568 let page_buf = PageBuf::filled_with(199);
569
570 assert_eq!(page_buf.encode(), vec![199u8; GearPage::SIZE as usize])
571 }
572
573 #[test]
574 fn free_fails() {
575 let mut ctx =
576 AllocationsContext::try_new(0.into(), Default::default(), 0.into(), None, 0.into())
577 .unwrap();
578 assert_eq!(ctx.free(1.into()), Err(AllocError::InvalidFree(1.into())));
579
580 let mut ctx = AllocationsContext::try_new(
581 1.into(),
582 [WasmPage::from(0)].into_iter().collect(),
583 0.into(),
584 None,
585 1.into(),
586 )
587 .unwrap();
588 assert_eq!(ctx.free(1.into()), Err(AllocError::InvalidFree(1.into())));
589
590 let mut ctx = AllocationsContext::try_new(
591 4.into(),
592 [WasmPage::from(1), WasmPage::from(3)].into_iter().collect(),
593 1.into(),
594 None,
595 4.into(),
596 )
597 .unwrap();
598 let interval = Interval::<WasmPage>::try_from(1u16..4).unwrap();
599 assert_eq!(ctx.free_range(interval), Ok(()));
600 }
601
602 #[track_caller]
603 fn alloc_ok(ctx: &mut AllocationsContext, mem: &mut TestMemory, pages: u16, expected: u16) {
604 let res = ctx.alloc::<(), NoopGrowHandler>(&mut (), mem, pages.into(), |_| Ok(()));
605 assert_eq!(res, Ok(expected.into()));
606 }
607
608 #[track_caller]
609 fn alloc_err(ctx: &mut AllocationsContext, mem: &mut TestMemory, pages: u16, err: AllocError) {
610 let res = ctx.alloc::<(), NoopGrowHandler>(&mut (), mem, pages.into(), |_| Ok(()));
611 assert_eq!(res, Err(err));
612 }
613
614 #[test]
615 fn alloc() {
616 let _ = tracing_subscriber::fmt::try_init();
617
618 let mut ctx = AllocationsContext::try_new(
619 256.into(),
620 Default::default(),
621 16.into(),
622 None,
623 256.into(),
624 )
625 .unwrap();
626 let mut mem = TestMemory::new(16.into());
627 alloc_ok(&mut ctx, &mut mem, 16, 16);
628 alloc_ok(&mut ctx, &mut mem, 0, 16);
629
630 (2..16).for_each(|i| alloc_ok(&mut ctx, &mut mem, 16, i * 16));
632
633 alloc_err(&mut ctx, &mut mem, 16, AllocError::ProgramAllocOutOfBounds);
635
636 ctx.free(137.into()).unwrap();
638 alloc_ok(&mut ctx, &mut mem, 1, 137);
639
640 ctx.free(117.into()).unwrap();
642 ctx.free(118.into()).unwrap();
643 alloc_ok(&mut ctx, &mut mem, 2, 117);
644
645 let interval = Interval::<WasmPage>::try_from(117..119).unwrap();
647 ctx.free_range(interval).unwrap();
648 alloc_ok(&mut ctx, &mut mem, 2, 117);
649
650 ctx.free(117.into()).unwrap();
652 ctx.free(158.into()).unwrap();
653 alloc_err(&mut ctx, &mut mem, 2, AllocError::ProgramAllocOutOfBounds);
654 }
655
656 #[test]
657 fn memory_params_validation() {
658 assert_eq!(
659 AllocationsContext::validate_memory_params(
660 4.into(),
661 &iter::once(WasmPage::from(2)).collect(),
662 2.into(),
663 Some(2.into()),
664 4.into(),
665 ),
666 Ok(())
667 );
668
669 assert_eq!(
670 AllocationsContext::validate_memory_params(
671 4.into(),
672 &Default::default(),
673 2.into(),
674 Some(2.into()),
675 3.into(),
676 ),
677 Err(MemorySetupError::MemorySizeExceedsMaxPages {
678 memory_size: 4.into(),
679 max_pages: 3.into()
680 })
681 );
682
683 assert_eq!(
684 AllocationsContext::validate_memory_params(
685 1.into(),
686 &Default::default(),
687 2.into(),
688 Some(1.into()),
689 4.into(),
690 ),
691 Err(MemorySetupError::InsufficientMemorySize {
692 memory_size: 1.into(),
693 static_pages: 2.into()
694 })
695 );
696
697 assert_eq!(
698 AllocationsContext::validate_memory_params(
699 4.into(),
700 &Default::default(),
701 2.into(),
702 Some(3.into()),
703 4.into(),
704 ),
705 Err(MemorySetupError::StackEndOutOfStaticMemory {
706 stack_end: 3.into(),
707 static_pages: 2.into()
708 })
709 );
710
711 assert_eq!(
712 AllocationsContext::validate_memory_params(
713 4.into(),
714 &[WasmPage::from(1), WasmPage::from(3)].into_iter().collect(),
715 2.into(),
716 Some(2.into()),
717 4.into(),
718 ),
719 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
720 page: 1.into(),
721 static_pages: 2.into(),
722 memory_size: 4.into()
723 })
724 );
725
726 assert_eq!(
727 AllocationsContext::validate_memory_params(
728 4.into(),
729 &[WasmPage::from(2), WasmPage::from(4)].into_iter().collect(),
730 2.into(),
731 Some(2.into()),
732 4.into(),
733 ),
734 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
735 page: 4.into(),
736 static_pages: 2.into(),
737 memory_size: 4.into()
738 })
739 );
740
741 assert_eq!(
742 AllocationsContext::validate_memory_params(
743 13.into(),
744 &iter::once(WasmPage::from(1)).collect(),
745 10.into(),
746 None,
747 13.into()
748 ),
749 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
750 page: 1.into(),
751 static_pages: 10.into(),
752 memory_size: 13.into()
753 })
754 );
755
756 assert_eq!(
757 AllocationsContext::validate_memory_params(
758 13.into(),
759 &iter::once(WasmPage::from(1)).collect(),
760 WasmPagesAmount::UPPER,
761 None,
762 13.into()
763 ),
764 Err(MemorySetupError::InsufficientMemorySize {
765 memory_size: 13.into(),
766 static_pages: WasmPagesAmount::UPPER
767 })
768 );
769
770 assert_eq!(
771 AllocationsContext::validate_memory_params(
772 WasmPagesAmount::UPPER,
773 &iter::once(WasmPage::from(1)).collect(),
774 10.into(),
775 None,
776 WasmPagesAmount::UPPER,
777 ),
778 Err(MemorySetupError::AllocatedPageOutOfAllowedInterval {
779 page: 1.into(),
780 static_pages: 10.into(),
781 memory_size: WasmPagesAmount::UPPER
782 })
783 );
784 }
785
786 #[test]
787 fn allocations_changed_correctness() {
788 let new_ctx = |allocations| {
789 AllocationsContext::try_new(16.into(), allocations, 0.into(), None, 16.into()).unwrap()
790 };
791
792 let mut ctx = new_ctx(Default::default());
794 assert!(
795 !ctx.allocations_changed,
796 "Expecting no changes after creation"
797 );
798 let mut mem = TestMemory::new(16.into());
799 alloc_ok(&mut ctx, &mut mem, 16, 0);
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);
807 alloc_err(&mut ctx, &mut mem, 16, AllocError::ProgramAllocOutOfBounds);
808 assert!(
809 !ctx.allocations_changed,
810 "Expecting allocations don't change because of error"
811 );
812
813 assert!(ctx.free(16.into()).is_err());
815 assert!(!ctx.allocations_changed);
816
817 assert!(ctx.free(10.into()).is_ok());
819 assert!(ctx.allocations_changed);
820
821 let (_, allocations, allocations_changed) = ctx.into_parts();
822 assert!(allocations_changed);
823
824 let mut ctx = new_ctx(allocations);
827 let interval = Interval::<WasmPage>::try_from(10u16..12).unwrap();
828 assert!(ctx.free_range(interval).is_ok());
829 assert!(
830 ctx.allocations_changed,
831 "Expected value is `true` because the 11th page was freed from allocations."
832 );
833
834 let (_, allocations, allocations_changed) = ctx.into_parts();
835 assert!(allocations_changed);
836
837 let mut ctx = new_ctx(allocations);
839 let interval = Interval::<WasmPage>::try_from(0u16..17).unwrap();
840 assert!(ctx.free_range(interval).is_err());
841 assert!(!ctx.allocations_changed);
842 assert!(!ctx.into_parts().2);
843 }
844
845 mod property_tests {
846 use super::*;
847 use proptest::{
848 arbitrary::any,
849 collection::size_range,
850 prop_oneof, proptest,
851 strategy::{Just, Strategy},
852 test_runner::Config as ProptestConfig,
853 };
854
855 #[derive(Debug, Clone)]
856 enum Action {
857 Alloc { pages: WasmPagesAmount },
858 Free { page: WasmPage },
859 FreeRange { page: WasmPage, size: u8 },
860 }
861
862 fn actions() -> impl Strategy<Value = Vec<Action>> {
863 let action = prop_oneof![
864 wasm_pages_amount_with_range(0, 32).prop_map(|pages| Action::Alloc { pages }),
866 wasm_page().prop_map(|page| Action::Free { page }),
867 (wasm_page(), any::<u8>())
868 .prop_map(|(page, size)| Action::FreeRange { page, size }),
869 ];
870 proptest::collection::vec(action, 0..1024)
871 }
872
873 fn allocations(start: u16, end: u16) -> impl Strategy<Value = IntervalsTree<WasmPage>> {
874 proptest::collection::btree_set(wasm_page_with_range(start, end), size_range(0..1024))
875 .prop_map(|pages| pages.into_iter().collect::<IntervalsTree<WasmPage>>())
876 }
877
878 fn wasm_page_with_range(start: u16, end: u16) -> impl Strategy<Value = WasmPage> {
879 (start..=end).prop_map(WasmPage::from)
880 }
881
882 fn wasm_page() -> impl Strategy<Value = WasmPage> {
883 wasm_page_with_range(0, u16::MAX)
884 }
885
886 fn wasm_pages_amount_with_range(
887 start: u32,
888 end: u32,
889 ) -> impl Strategy<Value = WasmPagesAmount> {
890 (start..=end).prop_map(|x| {
891 if x == u16::MAX as u32 + 1 {
892 WasmPagesAmount::UPPER
893 } else {
894 WasmPagesAmount::from(x as u16)
895 }
896 })
897 }
898
899 fn wasm_pages_amount() -> impl Strategy<Value = WasmPagesAmount> {
900 wasm_pages_amount_with_range(0, u16::MAX as u32 + 1)
901 }
902
903 #[derive(Debug)]
904 struct MemoryParams {
905 max_pages: WasmPagesAmount,
906 mem_size: WasmPagesAmount,
907 static_pages: WasmPagesAmount,
908 allocations: IntervalsTree<WasmPage>,
909 }
910
911 fn combined_memory_params() -> impl Strategy<Value = MemoryParams> {
913 wasm_pages_amount()
914 .prop_flat_map(|max_pages| {
915 let mem_size = wasm_pages_amount_with_range(0, u32::from(max_pages));
916 (Just(max_pages), mem_size)
917 })
918 .prop_flat_map(|(max_pages, mem_size)| {
919 let static_pages = wasm_pages_amount_with_range(0, u32::from(mem_size));
920 (Just(max_pages), Just(mem_size), static_pages)
921 })
922 .prop_filter(
923 "filter out cases where allocation region has zero size",
924 |(_max_pages, mem_size, static_pages)| static_pages < mem_size,
925 )
926 .prop_flat_map(|(max_pages, mem_size, static_pages)| {
927 let end_exclusive = u32::from(mem_size) - 1;
929 (
930 Just(max_pages),
931 Just(mem_size),
932 Just(static_pages),
933 allocations(u32::from(static_pages) as u16, end_exclusive as u16),
934 )
935 })
936 .prop_map(
937 |(max_pages, mem_size, static_pages, allocations)| MemoryParams {
938 max_pages,
939 mem_size,
940 static_pages,
941 allocations,
942 },
943 )
944 }
945
946 fn proptest_config() -> ProptestConfig {
947 ProptestConfig {
948 cases: 1024,
949 ..Default::default()
950 }
951 }
952
953 #[track_caller]
954 fn assert_free_error(err: AllocError) {
955 match err {
956 AllocError::InvalidFree(_) => {}
957 AllocError::InvalidFreeRange(_, _) => {}
958 err => panic!("{err:?}"),
959 }
960 }
961
962 proptest! {
963 #![proptest_config(proptest_config())]
964 #[test]
965 fn alloc(
966 mem_params in combined_memory_params(),
967 actions in actions(),
968 ) {
969 let _ = tracing_subscriber::fmt::try_init();
970
971 let MemoryParams{max_pages, mem_size, static_pages, allocations} = mem_params;
972 let mut ctx = AllocationsContext::try_new(mem_size, allocations, static_pages, None, max_pages).unwrap();
973
974 let mut mem = TestMemory::new(mem_size);
975
976 for action in actions {
977 match action {
978 Action::Alloc { pages } => {
979 match ctx.alloc::<_, NoopGrowHandler>(&mut (), &mut mem, pages, |_| Ok(())) {
980 Err(AllocError::ProgramAllocOutOfBounds) => {
981 let x = mem.size(&()).add(pages);
982 assert!(x.is_none() || x.unwrap() > max_pages);
983 }
984 Ok(page) => {
985 assert!(pages == WasmPagesAmount::from(0) || (page >= static_pages && page < max_pages));
986 assert!(mem.size(&()) <= max_pages);
987 assert!(WasmPagesAmount::from(page).add(pages).unwrap() <= mem.size(&()));
988 }
989 Err(err) => panic!("{err:?}"),
990 }
991 }
992 Action::Free { page } => {
993 if let Err(err) = ctx.free(page) {
994 assert_free_error(err);
995 }
996 }
997 Action::FreeRange { page, size } => {
998 if let Ok(interval) = Interval::<WasmPage>::with_len(page, size as u32) {
999 let _ = ctx.free_range(interval).map_err(assert_free_error);
1000 }
1001 }
1002 }
1003 }
1004 }
1005 }
1006 }
1007}