1use std::panic::{catch_unwind, AssertUnwindSafe};
2
3use calimero_node_primitives::client::NodeClient;
4use calimero_primitives::context::ContextId;
5use calimero_primitives::identity::PublicKey;
6use tracing::{debug, error, info};
7use wasmer::{DeserializeError, Instance, SerializeError, Store};
8
9#[cfg(feature = "profiling")]
11use wasmer::sys::{CompilerConfig, Cranelift};
12
13mod constants;
14mod constraint;
15pub mod errors;
16pub mod logic;
17mod memory;
18mod panic_payload;
19pub mod store;
20
21pub use constraint::Constraint;
22use errors::{FunctionCallError, HostError, Location, PanicContext, VMRuntimeError};
23use logic::{ContextHost, Outcome, VMContext, VMLimits, VMLogic, VMLogicError};
24use memory::WasmerTunables;
25use store::Storage;
26
27pub type RuntimeResult<T, E = VMRuntimeError> = Result<T, E>;
28
29fn validate_method_name(method: &str, max_length: u64) -> Result<(), FunctionCallError> {
46 if method.is_empty() {
48 return Err(FunctionCallError::MethodResolutionError(
49 errors::MethodResolutionError::EmptyMethodName,
50 ));
51 }
52
53 let method_len = method.len();
55 if method_len as u64 > max_length {
56 return Err(FunctionCallError::MethodResolutionError(
57 errors::MethodResolutionError::MethodNameTooLong {
58 name: method.to_owned(),
59 length: method_len,
60 max: max_length,
61 },
62 ));
63 }
64
65 for (position, character) in method.chars().enumerate() {
68 if !character.is_ascii_alphanumeric() && character != '_' {
69 return Err(FunctionCallError::MethodResolutionError(
70 errors::MethodResolutionError::InvalidMethodNameCharacter {
71 name: method.to_owned(),
72 character,
73 position,
74 },
75 ));
76 }
77 }
78
79 Ok(())
80}
81
82#[derive(Clone, Debug)]
83pub struct Engine {
84 limits: VMLimits,
85 engine: wasmer::Engine,
86}
87
88impl Default for Engine {
89 fn default() -> Self {
90 let limits = VMLimits::default();
91
92 let engine = Self::create_engine();
93
94 Self::new(engine, limits)
95 }
96}
97
98impl Engine {
99 #[must_use]
100 pub fn new(mut engine: wasmer::Engine, limits: VMLimits) -> Self {
101 if engine.is_sys() {
103 use wasmer::sys::NativeEngineExt;
104 engine.set_tunables(WasmerTunables::new(&limits));
105 }
106
107 Self { limits, engine }
108 }
109
110 fn create_engine() -> wasmer::Engine {
112 #[cfg(feature = "profiling")]
113 {
114 if std::env::var("ENABLE_WASMER_PROFILING")
115 .map(|v| v == "true")
116 .unwrap_or(false)
117 {
118 info!("Enabling Wasmer PerfMap profiling for WASM stack traces");
119 let mut config = Cranelift::default();
121 config.enable_perfmap();
122 return wasmer::Engine::from(config);
123 }
124 }
125
126 wasmer::Engine::default()
128 }
129
130 #[must_use]
131 pub fn headless() -> Self {
132 let limits = VMLimits::default();
133
134 #[cfg(feature = "profiling")]
137 {
138 if std::env::var("ENABLE_WASMER_PROFILING")
139 .map(|v| v == "true")
140 .unwrap_or(false)
141 {
142 debug!("Using profiling-enabled engine for precompiled module (required for perf.map generation)");
143 let engine = Self::create_engine();
144 return Self::new(engine, limits);
145 }
146 }
147
148 use wasmer::sys::NativeEngineExt;
149 let engine = wasmer::Engine::headless();
150
151 Self::new(engine, limits)
152 }
153
154 pub fn compile(&self, bytes: &[u8]) -> Result<Module, FunctionCallError> {
155 let size = bytes.len() as u64;
158 if size > self.limits.max_module_size {
159 tracing::warn!(
160 size,
161 max = self.limits.max_module_size,
162 "WASM module size limit exceeded"
163 );
164 return Err(FunctionCallError::ModuleSizeLimitExceeded {
165 size,
166 max: self.limits.max_module_size,
167 });
168 }
169
170 let module = wasmer::Module::new(&self.engine, bytes)?;
181
182 Ok(Module {
183 limits: self.limits,
184 engine: self.engine.clone(),
185 module,
186 })
187 }
188
189 pub unsafe fn from_precompiled(&self, bytes: &[u8]) -> Result<Module, DeserializeError> {
207 let module = wasmer::Module::deserialize(&self.engine, bytes)?;
208
209 Ok(Module {
210 limits: self.limits,
211 engine: self.engine.clone(),
212 module,
213 })
214 }
215}
216
217#[derive(Debug)]
218pub struct Module {
219 limits: VMLimits,
220 engine: wasmer::Engine,
221 module: wasmer::Module,
222}
223
224impl Module {
225 pub fn to_bytes(&self) -> Result<Box<[u8]>, SerializeError> {
226 let bytes = self.module.serialize()?;
227
228 Ok(Vec::into_boxed_slice(bytes.into()))
229 }
230
231 pub fn run<'a>(
232 &'a self,
233 context: ContextId,
234 executor: PublicKey,
235 method: &str,
236 input: &'a [u8],
237 storage: &'a mut dyn Storage,
238 private_storage: Option<&'a mut dyn Storage>,
239 node_client: Option<NodeClient>,
240 context_host: Option<Box<dyn ContextHost>>,
241 ) -> RuntimeResult<Outcome> {
242 let context_id = context;
243 info!(%context_id, method, "Running WASM method");
244 debug!(%context_id, method, input_len = input.len(), "WASM execution input");
245
246 let context = VMContext::new(input.into(), *context_id, *executor);
247
248 let mut logic = VMLogic::new(
249 storage,
250 private_storage,
251 context,
252 &self.limits,
253 node_client,
254 context_host,
255 );
256
257 let mut store = Store::new(self.engine.clone());
258
259 let imports = logic.imports(&mut store);
260
261 let execution_result = catch_unwind(AssertUnwindSafe(|| {
265 Self::execute_wasm(
266 &mut store,
267 &self.module,
268 &imports,
269 &mut logic,
270 method,
271 &context_id,
272 self.limits.max_method_name_length,
273 )
274 }));
275
276 let err = match execution_result {
278 Ok(Ok(err)) => err,
279 Ok(Err(e)) => return Err(e),
280 Err(panic_payload) => {
281 let message = panic_payload::panic_payload_to_string(
283 panic_payload.as_ref(),
284 "<unknown panic>",
285 );
286 error!(
287 %context_id,
288 method,
289 panic_message = %message,
290 "WASM execution panicked"
291 );
292 Some(FunctionCallError::HostError(HostError::Panic {
293 context: PanicContext::Guest,
294 message,
295 location: Location::Unknown,
296 }))
297 }
298 };
299
300 let outcome = logic.finish(err);
301 if outcome.returns.is_ok() {
302 info!(%context_id, method, "WASM method execution completed");
303 debug!(
304 %context_id,
305 method,
306 has_return = outcome.returns.as_ref().is_ok_and(Option::is_some),
307 logs_count = outcome.logs.len(),
308 events_count = outcome.events.len(),
309 "WASM execution outcome"
310 );
311 for (i, log) in outcome.logs.iter().enumerate() {
313 info!(%context_id, method, log_index = i, log_content = %log, "WASM_LOG");
314 }
315 }
316
317 Ok(outcome)
318 }
319
320 fn execute_wasm(
325 store: &mut Store,
326 module: &wasmer::Module,
327 imports: &wasmer::Imports,
328 logic: &mut VMLogic<'_>,
329 method: &str,
330 context_id: &ContextId,
331 max_method_name_length: u64,
332 ) -> RuntimeResult<Option<FunctionCallError>> {
333 if let Err(err) = validate_method_name(method, max_method_name_length) {
335 error!(%context_id, method, error=?err, "Invalid method name");
336 return Ok(Some(err));
337 }
338 let instance = match Instance::new(store, module, imports) {
339 Ok(instance) => instance,
340 Err(err) => {
341 error!(%context_id, method, error=?err, "Failed to instantiate WASM module");
342 return Ok(Some(err.into()));
343 }
344 };
345
346 let memory = match instance.exports.get_memory("memory") {
350 Ok(memory) => memory.clone(),
351 Err(err) => {
353 error!(%context_id, method, error=?err, "Failed to get WASM memory");
354 return Ok(Some(err.into()));
355 }
356 };
357
358 let _ = logic.with_memory(memory);
360
361 if let Ok(register_fn) = instance
365 .exports
366 .get_typed_function::<(), ()>(store, "__calimero_register_merge")
367 {
368 match register_fn.call(store) {
369 Ok(()) => {
370 debug!(%context_id, "Successfully registered CRDT merge function");
371 }
372 Err(err) => {
373 debug!(
376 %context_id,
377 error=?err,
378 "Failed to call merge registration hook (non-fatal, continuing)"
379 );
380 }
381 }
382 }
383
384 let function = match instance.exports.get_function(method) {
385 Ok(function) => function,
386 Err(err) => {
387 error!(%context_id, method, error=?err, "Method not found in WASM module");
388 return Ok(Some(err.into()));
389 }
390 };
391
392 let signature = function.ty(store);
393
394 if !(signature.params().is_empty() && signature.results().is_empty()) {
395 error!(%context_id, method, "Invalid method signature");
396 return Ok(Some(FunctionCallError::MethodResolutionError(
397 errors::MethodResolutionError::InvalidSignature {
398 name: method.to_owned(),
399 },
400 )));
401 }
402
403 if let Err(err) = function.call(store, &[]) {
404 let traces = err
405 .trace()
406 .iter()
407 .map(|frame| {
408 let module = frame.module_name();
409 let func = frame.function_name().unwrap_or("<unknown-func>");
410 let offset = frame.func_offset();
411 let offset = if offset == 0 {
412 String::new()
413 } else {
414 format!("@0x{offset:x}")
415 };
416 format!("{module}::{func}{offset}")
417 })
418 .collect::<Vec<_>>();
419 let trace_joined = if traces.is_empty() {
420 None
421 } else {
422 Some(traces.join(" -> "))
423 };
424
425 let message = err.message();
426 let message_str = if message.is_empty() {
427 "<no error message>"
428 } else {
429 message.as_str()
430 };
431
432 error!(
433 %context_id,
434 method,
435 error_debug = ?err,
436 error_message = %message_str,
437 wasm_trace = trace_joined.as_deref(),
438 "WASM method execution failed"
439 );
440
441 return match err.downcast::<VMLogicError>() {
442 Ok(err) => Ok(Some(err.try_into()?)),
443 Err(err) => Ok(Some(err.into())),
444 };
445 }
446
447 Ok(None)
448 }
449}
450
451#[cfg(test)]
452mod integration_tests_package_usage {
453 use {eyre as _, owo_colors as _, rand as _, wat as _};
454}
455
456#[cfg(test)]
458mod wasm_integration_tests {
459 use super::*;
460 use crate::store::InMemoryStorage;
461
462 #[test]
464 fn test_wasm_execution_success() {
465 let wat = r#"
467 (module
468 (memory (export "memory") 1)
469 (func (export "test_func"))
470 )
471 "#;
472 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
473
474 let engine = Engine::default();
475 let module = engine.compile(&wasm).expect("Failed to compile module");
476
477 let mut storage = InMemoryStorage::default();
478 let outcome = module
479 .run(
480 [0; 32].into(),
481 [0; 32].into(),
482 "test_func",
483 &[],
484 &mut storage,
485 None, None,
487 None,
488 )
489 .expect("Failed to run module");
490
491 assert!(
492 outcome.returns.is_ok(),
493 "Expected successful execution, got: {:?}",
494 outcome.returns
495 );
496 }
497
498 #[test]
500 fn test_wasm_method_not_found() {
501 let wat = r#"
502 (module
503 (memory (export "memory") 1)
504 (func (export "existing_func"))
505 )
506 "#;
507 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
508
509 let engine = Engine::default();
510 let module = engine.compile(&wasm).expect("Failed to compile module");
511
512 let mut storage = InMemoryStorage::default();
513 let outcome = module
514 .run(
515 [0; 32].into(),
516 [0; 32].into(),
517 "non_existent_func",
518 &[],
519 &mut storage,
520 None, None,
522 None,
523 )
524 .expect("Failed to run module");
525
526 match &outcome.returns {
527 Err(FunctionCallError::MethodResolutionError(
528 errors::MethodResolutionError::MethodNotFound { name },
529 )) => {
530 assert_eq!(name, "non_existent_func");
531 }
532 other => panic!("Expected MethodNotFound error, got: {other:?}"),
533 }
534 }
535
536 #[test]
538 fn test_wasm_empty_method_name() {
539 let wat = r#"
540 (module
541 (memory (export "memory") 1)
542 (func (export "test_func"))
543 )
544 "#;
545 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
546
547 let engine = Engine::default();
548 let module = engine.compile(&wasm).expect("Failed to compile module");
549
550 let mut storage = InMemoryStorage::default();
551 let outcome = module
552 .run(
553 [0; 32].into(),
554 [0; 32].into(),
555 "",
556 &[],
557 &mut storage,
558 None, None,
560 None,
561 )
562 .expect("Failed to run module");
563
564 match &outcome.returns {
565 Err(FunctionCallError::MethodResolutionError(
566 errors::MethodResolutionError::EmptyMethodName,
567 )) => {
568 }
570 other => panic!("Expected EmptyMethodName error, got: {other:?}"),
571 }
572 }
573
574 #[test]
576 fn test_wasm_method_name_too_long() {
577 let wat = r#"
578 (module
579 (memory (export "memory") 1)
580 (func (export "test_func"))
581 )
582 "#;
583 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
584
585 let engine = Engine::default();
586 let module = engine.compile(&wasm).expect("Failed to compile module");
587
588 let long_method_name = "a".repeat(300);
590
591 let mut storage = InMemoryStorage::default();
592 let outcome = module
593 .run(
594 [0; 32].into(),
595 [0; 32].into(),
596 &long_method_name,
597 &[],
598 &mut storage,
599 None, None,
601 None,
602 )
603 .expect("Failed to run module");
604
605 match &outcome.returns {
606 Err(FunctionCallError::MethodResolutionError(
607 errors::MethodResolutionError::MethodNameTooLong { length, max, .. },
608 )) => {
609 assert_eq!(*length, 300);
610 assert_eq!(*max, 256);
611 }
612 other => panic!("Expected MethodNameTooLong error, got: {other:?}"),
613 }
614 }
615
616 #[test]
618 fn test_wasm_method_name_invalid_characters() {
619 let wat = r#"
620 (module
621 (memory (export "memory") 1)
622 (func (export "test_func"))
623 )
624 "#;
625 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
626
627 let engine = Engine::default();
628 let module = engine.compile(&wasm).expect("Failed to compile module");
629
630 let invalid_names = [
632 ("test func", ' ', 4), ("test\nfunc", '\n', 4), ("test-func", '-', 4), ("test.func", '.', 4), ("test/func", '/', 4), ];
638
639 for (method_name, expected_char, expected_pos) in invalid_names {
640 let mut storage = InMemoryStorage::default();
641 let outcome = module
642 .run(
643 [0; 32].into(),
644 [0; 32].into(),
645 method_name,
646 &[],
647 &mut storage,
648 None, None,
650 None,
651 )
652 .expect("Failed to run module");
653
654 match &outcome.returns {
655 Err(FunctionCallError::MethodResolutionError(
656 errors::MethodResolutionError::InvalidMethodNameCharacter {
657 character,
658 position,
659 ..
660 },
661 )) => {
662 assert_eq!(
663 *character, expected_char,
664 "Wrong invalid character for method name: {method_name}"
665 );
666 assert_eq!(
667 *position, expected_pos,
668 "Wrong position for method name: {method_name}"
669 );
670 }
671 other => panic!(
672 "Expected InvalidMethodNameCharacter error for '{method_name}', got: {other:?}"
673 ),
674 }
675 }
676 }
677
678 #[test]
680 fn test_wasm_method_name_valid_characters() {
681 let wat = r#"
685 (module
686 (memory (export "memory") 1)
687 (func (export "test_func"))
688 )
689 "#;
690 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
691
692 let engine = Engine::default();
693 let module = engine.compile(&wasm).expect("Failed to compile module");
694
695 let valid_names = [
697 "simple",
698 "with_underscore",
699 "__double_underscore",
700 "_leading_underscore",
701 "trailing_underscore_",
702 "CamelCase",
703 "mixedCase123",
704 "numbers123",
705 "ALLCAPS",
706 "a", ];
708
709 for method_name in valid_names {
710 let mut storage = InMemoryStorage::default();
711 let outcome = module
712 .run(
713 [0; 32].into(),
714 [0; 32].into(),
715 method_name,
716 &[],
717 &mut storage,
718 None, None,
720 None,
721 )
722 .expect("Failed to run module");
723
724 match &outcome.returns {
726 Err(FunctionCallError::MethodResolutionError(
727 errors::MethodResolutionError::MethodNotFound { name },
728 )) => {
729 assert_eq!(
730 name, method_name,
731 "Method name should pass validation: {method_name}"
732 );
733 }
734 Ok(_) if method_name == "test_func" => {}
736 other => panic!(
737 "Expected MethodNotFound error for valid name '{method_name}', got: {other:?}"
738 ),
739 }
740 }
741 }
742
743 #[test]
745 fn test_wasm_valid_method_name_execution() {
746 let wat = r#"
747 (module
748 (memory (export "memory") 1)
749 (func (export "valid_method_name"))
750 )
751 "#;
752 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
753
754 let engine = Engine::default();
755 let module = engine.compile(&wasm).expect("Failed to compile module");
756
757 let mut storage = InMemoryStorage::default();
758 let outcome = module
759 .run(
760 [0; 32].into(),
761 [0; 32].into(),
762 "valid_method_name",
763 &[],
764 &mut storage,
765 None, None,
767 None,
768 )
769 .expect("Failed to run module");
770
771 assert!(
772 outcome.returns.is_ok(),
773 "Expected successful execution with valid method name, got: {:?}",
774 outcome.returns
775 );
776 }
777
778 #[test]
780 fn test_wasm_unreachable_trap() {
781 let wat = r#"
783 (module
784 (memory (export "memory") 1)
785 (func (export "trap_func")
786 unreachable
787 )
788 )
789 "#;
790 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
791
792 let engine = Engine::default();
793 let module = engine.compile(&wasm).expect("Failed to compile module");
794
795 let mut storage = InMemoryStorage::default();
796 let outcome = module
797 .run(
798 [0; 32].into(),
799 [0; 32].into(),
800 "trap_func",
801 &[],
802 &mut storage,
803 None, None,
805 None,
806 )
807 .expect("Failed to run module");
808
809 match &outcome.returns {
811 Err(FunctionCallError::WasmTrap(errors::WasmTrap::Unreachable)) => {
812 }
814 other => panic!("Expected WasmTrap::Unreachable error, got: {other:?}"),
815 }
816 }
817
818 #[test]
820 fn test_wasm_panic_host_function() {
821 let wat = r#"
824 (module
825 (import "env" "panic" (func $panic (param i64)))
826 (memory (export "memory") 1)
827 (data (i32.const 0) "test.rs")
828 (func (export "panic_func")
829 ;; Store the location struct at offset 100:
830 ;; - ptr to filename (8 bytes): points to 0
831 ;; - len of filename (8 bytes): 7
832 ;; - line (4 bytes): 42
833 ;; - column (4 bytes): 10
834
835 ;; ptr to filename = 0
836 (i64.store (i32.const 100) (i64.const 0))
837 ;; len of filename = 7
838 (i64.store (i32.const 108) (i64.const 7))
839 ;; line = 42
840 (i32.store (i32.const 116) (i32.const 42))
841 ;; column = 10
842 (i32.store (i32.const 120) (i32.const 10))
843
844 ;; Call panic with pointer to location struct
845 (call $panic (i64.const 100))
846 )
847 )
848 "#;
849 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
850
851 let engine = Engine::default();
852 let module = engine.compile(&wasm).expect("Failed to compile module");
853
854 let mut storage = InMemoryStorage::default();
855 let outcome = module
856 .run(
857 [0; 32].into(),
858 [0; 32].into(),
859 "panic_func",
860 &[],
861 &mut storage,
862 None, None,
864 None,
865 )
866 .expect("Failed to run module");
867
868 match &outcome.returns {
870 Err(FunctionCallError::HostError(HostError::Panic {
871 context, location, ..
872 })) => {
873 assert!(
874 matches!(context, PanicContext::Guest),
875 "Expected Guest panic context"
876 );
877 match location {
879 Location::At { file, line, column } => {
880 assert_eq!(file, "test.rs");
881 assert_eq!(*line, 42);
882 assert_eq!(*column, 10);
883 }
884 Location::Unknown => panic!("Expected location to be known"),
885 }
886 }
887 other => panic!("Expected HostError::Panic, got: {other:?}"),
888 }
889 }
890
891 #[test]
893 fn test_wasm_memory_out_of_bounds() {
894 let wat = r#"
896 (module
897 (memory (export "memory") 1)
898 (func (export "oob_func")
899 ;; Try to load from way beyond the memory limit (1 page = 65536 bytes)
900 (drop (i32.load (i32.const 1000000)))
901 )
902 )
903 "#;
904 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
905
906 let engine = Engine::default();
907 let module = engine.compile(&wasm).expect("Failed to compile module");
908
909 let mut storage = InMemoryStorage::default();
910 let outcome = module
911 .run(
912 [0; 32].into(),
913 [0; 32].into(),
914 "oob_func",
915 &[],
916 &mut storage,
917 None, None,
919 None,
920 )
921 .expect("Failed to run module");
922
923 match &outcome.returns {
925 Err(FunctionCallError::WasmTrap(errors::WasmTrap::MemoryOutOfBounds)) => {
926 }
928 other => panic!("Expected WasmTrap::MemoryOutOfBounds error, got: {other:?}"),
929 }
930 }
931
932 #[test]
934 fn test_wasm_stack_overflow() {
935 let wat = r#"
937 (module
938 (memory (export "memory") 1)
939 (func $recurse
940 (call $recurse)
941 )
942 (func (export "overflow_func")
943 (call $recurse)
944 )
945 )
946 "#;
947 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
948
949 let engine = Engine::default();
950 let module = engine.compile(&wasm).expect("Failed to compile module");
951
952 let mut storage = InMemoryStorage::default();
953 let outcome = module
954 .run(
955 [0; 32].into(),
956 [0; 32].into(),
957 "overflow_func",
958 &[],
959 &mut storage,
960 None, None,
962 None,
963 )
964 .expect("Failed to run module");
965
966 match &outcome.returns {
968 Err(FunctionCallError::WasmTrap(errors::WasmTrap::StackOverflow)) => {
969 }
971 other => panic!("Expected WasmTrap::StackOverflow error, got: {other:?}"),
972 }
973 }
974
975 #[test]
977 fn test_wasm_module_size_limit() {
978 use crate::logic::VMLimits;
979
980 let wat = r#"
982 (module
983 (memory (export "memory") 1)
984 (func (export "test_func"))
985 )
986 "#;
987 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
988
989 let mut limits = VMLimits::default();
991 limits.max_module_size = 10; let engine = Engine::new(wasmer::Engine::default(), limits);
994
995 let result = engine.compile(&wasm);
997
998 match result {
999 Err(FunctionCallError::ModuleSizeLimitExceeded { size, max }) => {
1000 assert_eq!(max, 10);
1001 assert!(size > 10, "Module size should be greater than the limit");
1002 }
1003 Ok(_) => panic!("Expected ModuleSizeLimitExceeded error, but compilation succeeded"),
1004 Err(other) => panic!("Expected ModuleSizeLimitExceeded error, got: {other:?}"),
1005 }
1006 }
1007
1008 #[test]
1010 fn test_wasm_module_within_size_limit() {
1011 use crate::logic::VMLimits;
1012
1013 let wat = r#"
1015 (module
1016 (memory (export "memory") 1)
1017 (func (export "test_func"))
1018 )
1019 "#;
1020 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1021
1022 let mut limits = VMLimits::default();
1024 limits.max_module_size = 1024 * 1024; let engine = Engine::new(wasmer::Engine::default(), limits);
1027
1028 let result = engine.compile(&wasm);
1030 assert!(
1031 result.is_ok(),
1032 "Expected successful compilation, got: {result:?}"
1033 );
1034 }
1035
1036 #[test]
1038 fn test_wasm_module_at_exact_size_limit() {
1039 use crate::logic::VMLimits;
1040
1041 let wat = r#"
1043 (module
1044 (memory (export "memory") 1)
1045 (func (export "test_func"))
1046 )
1047 "#;
1048 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1049
1050 let mut limits = VMLimits::default();
1052 limits.max_module_size = wasm.len() as u64; let engine = Engine::new(wasmer::Engine::default(), limits);
1055
1056 let result = engine.compile(&wasm);
1058 assert!(
1059 result.is_ok(),
1060 "Expected successful compilation at exact size limit, got: {result:?}"
1061 );
1062 }
1063
1064 #[test]
1066 fn test_wasm_compilation_error_propagation() {
1067 let invalid_wasm = b"not valid wasm bytes at all - this should fail compilation";
1070
1071 let engine = Engine::default();
1072
1073 let result = engine.compile(invalid_wasm);
1075
1076 match result {
1077 Err(FunctionCallError::CompilationError { .. }) => {
1078 }
1080 Err(FunctionCallError::ModuleSizeLimitExceeded { .. }) => {
1081 panic!("Should not hit size limit for small invalid module")
1082 }
1083 Ok(_) => panic!("Expected compilation error for invalid WASM"),
1084 Err(other) => panic!("Expected CompilationError, got: {other:?}"),
1085 }
1086 }
1087
1088 #[test]
1090 fn test_wasm_module_size_limit_zero() {
1091 use crate::logic::VMLimits;
1092
1093 let wat = r#"
1095 (module
1096 (memory (export "memory") 1)
1097 (func (export "test_func"))
1098 )
1099 "#;
1100 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1101
1102 let mut limits = VMLimits::default();
1104 limits.max_module_size = 0;
1105
1106 let engine = Engine::new(wasmer::Engine::default(), limits);
1107
1108 let result = engine.compile(&wasm);
1110
1111 match result {
1112 Err(FunctionCallError::ModuleSizeLimitExceeded { size, max }) => {
1113 assert_eq!(max, 0);
1114 assert!(size > 0, "Module size should be greater than 0");
1115 }
1116 Ok(_) => panic!("Expected ModuleSizeLimitExceeded error with max_module_size=0"),
1117 Err(other) => panic!("Expected ModuleSizeLimitExceeded error, got: {other:?}"),
1118 }
1119
1120 let empty_bytes: &[u8] = &[];
1122 let empty_result = engine.compile(empty_bytes);
1123
1124 match empty_result {
1125 Err(FunctionCallError::CompilationError { .. }) => {
1126 }
1128 Err(FunctionCallError::ModuleSizeLimitExceeded { .. }) => {
1129 panic!("Empty module should pass size check (0 is not > 0)")
1130 }
1131 Ok(_) => panic!("Empty bytes should not compile successfully"),
1132 Err(other) => panic!("Expected CompilationError for empty bytes, got: {other:?}"),
1133 }
1134 }
1135}