1use wasmparser::{
33 ComponentAlias, ComponentExternalKind, ComponentInstance, ComponentOuterAliasKind,
34 ComponentTypeRef, Encoding, FuncValidatorAllocations, Instance, Parser, Payload, TypeRef,
35 ValidPayload, Validator, WasmFeatures,
36};
37
38use crate::{CodecFailure, Error, Result};
39
40pub const NOTE_CODEC_WASM_FEATURES: WasmFeatures = WasmFeatures::COMPONENT_MODEL
51 .union(WasmFeatures::FLOATS)
52 .union(WasmFeatures::MUTABLE_GLOBAL)
53 .union(WasmFeatures::SATURATING_FLOAT_TO_INT)
54 .union(WasmFeatures::SIGN_EXTENSION)
55 .union(WasmFeatures::REFERENCE_TYPES)
56 .union(WasmFeatures::MULTI_VALUE)
57 .union(WasmFeatures::BULK_MEMORY);
58
59const MAX_MODULE_FUNCTIONS: usize = 10_000;
61
62const MAX_MODULE_GLOBALS: usize = 1_000;
64
65const MAX_MODULE_TYPES: usize = 1_000;
67
68const MAX_MODULE_TABLES: usize = 100;
73
74const MAX_MODULE_MEMORIES: usize = 1;
76
77const MAX_MODULE_ELEMENT_SEGMENTS: usize = 1_000;
79
80const MAX_MODULE_DATA_SEGMENTS: usize = 1_000;
82
83const MAX_MODULE_IMPORTS: usize = 1_024;
85
86const MAX_MODULE_EXPORTS: usize = 1_024;
88
89const MAX_FUNCTION_PARAMS: usize = 32;
91
92const MAX_FUNCTION_RESULTS: usize = 32;
94
95const MIN_METERED_CODE_SECTION_BYTES: u32 = 1_000;
99
100const MIN_AVERAGE_FUNCTION_BYTES: u32 = 40;
105
106const MAX_CORE_MODULES: usize = 16;
108
109const MAX_COMPONENT_DEPTH: usize = 4;
111
112const MAX_COMPONENT_SECTION_ITEMS: usize = 256;
117
118pub(crate) const MAX_CORE_INSTANCES: usize = 32;
122
123const MAX_COMPONENT_INSTANCES: usize = 8;
129
130pub(crate) const MAX_INSTANTIATED_TABLES: usize = 32;
134
135pub(crate) const MAX_INSTANTIATED_MEMORIES: usize = 1;
139
140pub fn validate_note_codec_component(component: &[u8], max_bytes: usize) -> Result<()> {
147 ensure_component_byte_limit(component.len(), max_bytes)?;
148 validate_note_codec_structure(component)
149}
150
151pub fn validate_note_codec_structure(component: &[u8]) -> Result<()> {
156 let mut validator = Validator::new_with_features(NOTE_CODEC_WASM_FEATURES);
157 let mut allocations = FuncValidatorAllocations::default();
159 let mut walk = StructureWalk::default();
160 for payload in Parser::new(0).parse_all(component) {
161 let payload = payload.map_err(malformed)?;
162 if matches!(payload, Payload::ComponentStartSection { .. }) {
166 return Err(policy_rejection(
167 "note codec component declares a start function; the policy rejects a component \
168 that runs code when it is instantiated",
169 ));
170 }
171 if let ValidPayload::Func(function, body) =
172 validator.payload(&payload).map_err(rejected_feature)?
173 {
174 let mut function = function.into_validator(allocations);
175 function.validate(&body).map_err(rejected_feature)?;
176 allocations = function.into_allocations();
177 }
178 walk.visit(payload)?;
179 }
180 Ok(())
181}
182
183fn ensure_component_byte_limit(byte_len: usize, limit: usize) -> Result<()> {
185 if byte_len <= limit {
186 return Ok(());
187 }
188 Err(policy_rejection(format!(
189 "note codec component is {byte_len} bytes; the pre-compilation limit is {limit}"
190 )))
191}
192
193#[derive(Default)]
195struct StructureWalk {
196 frames: Vec<Frame>,
198 core_modules: usize,
200 component_items: ComponentItemCounts,
202}
203
204#[derive(Default)]
206struct ComponentItemCounts {
207 core_types: usize,
208 types: usize,
209 aliases: usize,
210 canonical_functions: usize,
211 imports: usize,
212 exports: usize,
213}
214
215enum Frame {
217 Module(ModuleCounts),
219 Component(ComponentFrame),
221}
222
223#[derive(Default)]
228struct ComponentFrame {
229 core_modules: Vec<Option<ModuleRuntimeCounts>>,
231 components: Vec<Option<CreatedCounts>>,
233 created: CreatedCounts,
235}
236
237#[derive(Clone, Copy, Default)]
239struct ModuleRuntimeCounts {
240 memories: usize,
241 tables: usize,
242}
243
244#[derive(Clone, Copy, Default)]
246struct CreatedCounts {
247 core_instances: usize,
248 component_instances: usize,
249 memories: usize,
250 tables: usize,
251}
252
253impl CreatedCounts {
254 fn add(&mut self, other: Self) {
256 self.core_instances = self.core_instances.saturating_add(other.core_instances);
257 self.component_instances =
258 self.component_instances.saturating_add(other.component_instances);
259 self.memories = self.memories.saturating_add(other.memories);
260 self.tables = self.tables.saturating_add(other.tables);
261 }
262
263 fn check(&self) -> Result<()> {
265 ensure_created_cap("core instances", self.core_instances, MAX_CORE_INSTANCES)?;
266 ensure_created_cap(
267 "component instances",
268 self.component_instances,
269 MAX_COMPONENT_INSTANCES,
270 )?;
271 ensure_created_cap("linear memories", self.memories, MAX_INSTANTIATED_MEMORIES)?;
272 ensure_created_cap("tables", self.tables, MAX_INSTANTIATED_TABLES)
273 }
274}
275
276#[derive(Default)]
278struct ModuleCounts {
279 types: usize,
280 functions: usize,
281 globals: usize,
282 tables: usize,
283 memories: usize,
284 element_segments: usize,
285 data_segments: usize,
286 imports: usize,
287 exports: usize,
288 runtime: ModuleRuntimeCounts,
290}
291
292impl ModuleCounts {
293 fn check(&self) -> Result<()> {
295 ensure_module_cap("types", self.types, MAX_MODULE_TYPES)?;
296 ensure_module_cap("functions", self.functions, MAX_MODULE_FUNCTIONS)?;
297 ensure_module_cap("globals", self.globals, MAX_MODULE_GLOBALS)?;
298 ensure_module_cap("tables", self.tables, MAX_MODULE_TABLES)?;
299 ensure_module_cap("memories", self.memories, MAX_MODULE_MEMORIES)?;
300 ensure_module_cap("element segments", self.element_segments, MAX_MODULE_ELEMENT_SEGMENTS)?;
301 ensure_module_cap("data segments", self.data_segments, MAX_MODULE_DATA_SEGMENTS)?;
302 ensure_module_cap("imports", self.imports, MAX_MODULE_IMPORTS)?;
303 ensure_module_cap("exports", self.exports, MAX_MODULE_EXPORTS)
304 }
305}
306
307impl StructureWalk {
308 fn visit(&mut self, payload: Payload<'_>) -> Result<()> {
310 match payload {
311 Payload::Version { encoding, .. } => self.enter(encoding)?,
312 Payload::End(_) => self.leave()?,
313 Payload::TypeSection(reader) => {
314 self.module_counts()?.types += reader.count() as usize;
315 for ty in reader.into_iter_err_on_gc_types() {
318 let ty = ty.map_err(rejected_feature)?;
319 ensure_signature_cap("parameters", ty.params().len(), MAX_FUNCTION_PARAMS)?;
320 ensure_signature_cap("results", ty.results().len(), MAX_FUNCTION_RESULTS)?;
321 }
322 }
323 Payload::ImportSection(reader) => {
324 for import in reader.into_imports() {
325 let import = import.map_err(malformed)?;
326 let counts = self.module_counts()?;
327 counts.imports += 1;
328 match import.ty {
329 TypeRef::Func(_) | TypeRef::FuncExact(_) => counts.functions += 1,
330 TypeRef::Global(_) => counts.globals += 1,
331 TypeRef::Table(_) => counts.tables += 1,
332 TypeRef::Memory(_) => counts.memories += 1,
333 TypeRef::Tag(_) => {}
334 }
335 }
336 }
337 Payload::FunctionSection(reader) => {
338 self.module_counts()?.functions += reader.count() as usize;
339 }
340 Payload::GlobalSection(reader) => {
341 self.module_counts()?.globals += reader.count() as usize;
342 }
343 Payload::TableSection(reader) => {
344 let count = reader.count() as usize;
345 let counts = self.module_counts()?;
346 counts.tables += count;
347 counts.runtime.tables += count;
348 }
349 Payload::MemorySection(reader) => {
350 let count = reader.count() as usize;
351 let counts = self.module_counts()?;
352 counts.memories += count;
353 counts.runtime.memories += count;
354 }
355 Payload::ElementSection(reader) => {
356 self.module_counts()?.element_segments += reader.count() as usize;
357 }
358 Payload::DataSection(reader) => {
359 self.module_counts()?.data_segments += reader.count() as usize;
360 }
361 Payload::ExportSection(reader) => {
362 self.module_counts()?.exports += reader.count() as usize;
363 }
364 Payload::CodeSectionStart { count, size, .. } => {
365 ensure_average_function_size(count, size)?;
366 }
367 Payload::InstanceSection(reader) => {
368 for instance in reader {
369 self.visit_core_instance(instance.map_err(malformed)?)?;
370 }
371 }
372 Payload::ComponentInstanceSection(reader) => {
373 for instance in reader {
374 self.visit_component_instance(instance.map_err(malformed)?)?;
375 }
376 }
377 Payload::CoreTypeSection(reader) => {
378 self.component_items.core_types += reader.count() as usize;
379 ensure_component_item_cap("core types", self.component_items.core_types)?;
380 }
381 Payload::ComponentTypeSection(reader) => {
382 self.component_items.types += reader.count() as usize;
383 ensure_component_item_cap("types", self.component_items.types)?;
384 }
385 Payload::ComponentAliasSection(reader) => {
386 for alias in reader {
387 let alias = alias.map_err(malformed)?;
388 self.component_items.aliases += 1;
389 ensure_component_item_cap("aliases", self.component_items.aliases)?;
390 self.declare_aliased_item(&alias)?;
391 }
392 }
393 Payload::ComponentCanonicalSection(reader) => {
394 self.component_items.canonical_functions += reader.count() as usize;
395 ensure_component_item_cap(
396 "canonical functions",
397 self.component_items.canonical_functions,
398 )?;
399 }
400 Payload::ComponentImportSection(reader) => {
401 for import in reader {
402 let import = import.map_err(malformed)?;
403 self.component_items.imports += 1;
404 ensure_component_item_cap("imports", self.component_items.imports)?;
405 match import.ty {
406 ComponentTypeRef::Module(_) => self.declare_core_module(None)?,
407 ComponentTypeRef::Component(_) => self.declare_component(None)?,
408 _ => {}
409 }
410 }
411 }
412 Payload::ComponentExportSection(reader) => {
413 self.component_items.exports += reader.count() as usize;
414 ensure_component_item_cap("exports", self.component_items.exports)?;
415 }
416 _ => {}
417 }
418 Ok(())
419 }
420
421 fn enter(&mut self, encoding: Encoding) -> Result<()> {
423 match encoding {
424 Encoding::Module => {
425 self.core_modules += 1;
426 if self.core_modules > MAX_CORE_MODULES {
427 return Err(policy_rejection(format!(
428 "note codec component has {} core modules; the limit is {MAX_CORE_MODULES}",
429 self.core_modules
430 )));
431 }
432 self.frames.push(Frame::Module(ModuleCounts::default()));
433 }
434 Encoding::Component => {
435 self.frames.push(Frame::Component(ComponentFrame::default()));
436 let depth =
437 self.frames.iter().filter(|frame| matches!(frame, Frame::Component(_))).count();
438 if depth > MAX_COMPONENT_DEPTH {
439 return Err(policy_rejection(format!(
440 "note codec component nests components {depth} deep; the limit is \
441 {MAX_COMPONENT_DEPTH}"
442 )));
443 }
444 }
445 }
446 Ok(())
447 }
448
449 fn leave(&mut self) -> Result<()> {
451 match self.frames.pop() {
452 Some(Frame::Module(counts)) => {
453 counts.check()?;
454 self.declare_core_module(Some(counts.runtime))
457 }
458 Some(Frame::Component(frame)) => self.declare_component(Some(frame.created)),
459 None => Ok(()),
462 }
463 }
464
465 fn visit_core_instance(&mut self, instance: Instance<'_>) -> Result<()> {
467 let created = match instance {
469 Instance::Instantiate { module_index, .. } => {
470 let module = self.instantiated_core_module(module_index)?;
471 CreatedCounts {
472 core_instances: 1,
473 component_instances: 0,
474 memories: module.memories,
475 tables: module.tables,
476 }
477 }
478 Instance::FromExports(_) => CreatedCounts {
479 core_instances: 1,
480 ..CreatedCounts::default()
481 },
482 };
483 self.record_created(created)
484 }
485
486 fn visit_component_instance(&mut self, instance: ComponentInstance<'_>) -> Result<()> {
488 let ComponentInstance::Instantiate {
490 component_index, ..
491 } = instance
492 else {
493 return Ok(());
494 };
495 let mut created = self.instantiated_component(component_index)?;
496 created.component_instances = created.component_instances.saturating_add(1);
497 self.record_created(created)
498 }
499
500 fn record_created(&mut self, created: CreatedCounts) -> Result<()> {
502 let frame = self.component_frame()?;
503 frame.created.add(created);
504 frame.created.check()
505 }
506
507 fn instantiated_core_module(&mut self, module_index: u32) -> Result<ModuleRuntimeCounts> {
509 let frame = self.component_frame()?;
510 frame.core_modules.get(module_index as usize).copied().flatten().ok_or_else(|| {
511 policy_rejection(format!(
512 "note codec component instantiates core module {module_index}, which the policy \
513 cannot read; a codec instantiates only the core modules it defines"
514 ))
515 })
516 }
517
518 fn instantiated_component(&mut self, component_index: u32) -> Result<CreatedCounts> {
520 let frame = self.component_frame()?;
521 frame
522 .components
523 .get(component_index as usize)
524 .copied()
525 .flatten()
526 .ok_or_else(|| {
527 policy_rejection(format!(
528 "note codec component instantiates component {component_index}, which the \
529 policy cannot read; a codec instantiates only the components it defines"
530 ))
531 })
532 }
533
534 fn declare_core_module(&mut self, created: Option<ModuleRuntimeCounts>) -> Result<()> {
538 if let Some(Frame::Component(frame)) = self.frames.last_mut() {
539 frame.core_modules.push(created);
540 }
541 Ok(())
542 }
543
544 fn declare_component(&mut self, created: Option<CreatedCounts>) -> Result<()> {
548 if let Some(Frame::Component(frame)) = self.frames.last_mut() {
549 frame.components.push(created);
550 }
551 Ok(())
552 }
553
554 fn declare_aliased_item(&mut self, alias: &ComponentAlias<'_>) -> Result<()> {
556 match aliased_item_kind(alias) {
557 Some(AliasedItem::CoreModule) => self.declare_core_module(None),
558 Some(AliasedItem::Component) => self.declare_component(None),
559 None => Ok(()),
560 }
561 }
562
563 fn component_frame(&mut self) -> Result<&mut ComponentFrame> {
568 match self.frames.last_mut() {
569 Some(Frame::Component(frame)) => Ok(frame),
570 _ => Err(policy_rejection(
571 "note codec component is malformed: a component section appears outside a \
572 component",
573 )),
574 }
575 }
576
577 fn module_counts(&mut self) -> Result<&mut ModuleCounts> {
582 match self.frames.last_mut() {
583 Some(Frame::Module(counts)) => Ok(counts),
584 _ => Err(policy_rejection(
585 "note codec component is malformed: a core section appears outside a core module",
586 )),
587 }
588 }
589}
590
591enum AliasedItem {
593 CoreModule,
594 Component,
595}
596
597fn aliased_item_kind(alias: &ComponentAlias<'_>) -> Option<AliasedItem> {
599 match alias {
600 ComponentAlias::InstanceExport {
601 kind: ComponentExternalKind::Module,
602 ..
603 }
604 | ComponentAlias::Outer {
605 kind: ComponentOuterAliasKind::CoreModule,
606 ..
607 } => Some(AliasedItem::CoreModule),
608 ComponentAlias::InstanceExport {
609 kind: ComponentExternalKind::Component,
610 ..
611 }
612 | ComponentAlias::Outer {
613 kind: ComponentOuterAliasKind::Component,
614 ..
615 } => Some(AliasedItem::Component),
616 _ => None,
617 }
618}
619
620fn ensure_module_cap(kind: &str, observed: usize, limit: usize) -> Result<()> {
622 if observed > limit {
623 return Err(policy_rejection(format!(
624 "note codec component has a core module with {observed} {kind}; the limit is {limit}"
625 )));
626 }
627 Ok(())
628}
629
630fn ensure_created_cap(kind: &str, observed: usize, limit: usize) -> Result<()> {
632 if observed > limit {
633 return Err(policy_rejection(format!(
634 "note codec component creates {observed} {kind} when it is instantiated; the limit is \
635 {limit}"
636 )));
637 }
638 Ok(())
639}
640
641fn ensure_signature_cap(kind: &str, observed: usize, limit: usize) -> Result<()> {
643 if observed > limit {
644 return Err(policy_rejection(format!(
645 "note codec component has a function type with {observed} {kind}; the limit is {limit}"
646 )));
647 }
648 Ok(())
649}
650
651fn ensure_component_item_cap(kind: &str, observed: usize) -> Result<()> {
653 if observed > MAX_COMPONENT_SECTION_ITEMS {
654 return Err(policy_rejection(format!(
655 "note codec component has {observed} component {kind}; the limit is \
656 {MAX_COMPONENT_SECTION_ITEMS}"
657 )));
658 }
659 Ok(())
660}
661
662fn ensure_average_function_size(count: u32, size: u32) -> Result<()> {
664 if size < MIN_METERED_CODE_SECTION_BYTES || count == 0 {
665 return Ok(());
666 }
667 let average = size / count;
668 if average < MIN_AVERAGE_FUNCTION_BYTES {
669 return Err(policy_rejection(format!(
670 "note codec component has a core module with {count} functions in {size} bytes of \
671 code, an average of {average} bytes; the limit is {MIN_AVERAGE_FUNCTION_BYTES} bytes \
672 per function"
673 )));
674 }
675 Ok(())
676}
677
678fn malformed(error: wasmparser::BinaryReaderError) -> Error {
680 policy_rejection(format!("note codec component is malformed: {error}"))
681}
682
683fn rejected_feature(error: wasmparser::BinaryReaderError) -> Error {
685 policy_rejection(format!(
686 "note codec component uses a Wasm feature the policy rejects: {error}"
687 ))
688}
689
690fn policy_rejection(message: impl Into<String>) -> Error {
695 Error::codec(CodecFailure::LimitExceeded, message)
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 fn component(core_module: &str) -> Vec<u8> {
704 wat::parse_str(format!("(component (core module {core_module}))")).unwrap()
705 }
706
707 fn validate(component: &[u8]) -> Result<()> {
709 validate_note_codec_component(component, usize::MAX)
710 }
711
712 #[test]
713 fn minimal_component_passes() {
714 validate(&component("(func)")).unwrap();
715 }
716
717 #[test]
718 fn the_wasm_feature_policy_is_exact() {
719 let wasmparser::WasmFeaturesInflated {
722 mutable_global,
723 saturating_float_to_int,
724 sign_extension,
725 reference_types,
726 multi_value,
727 bulk_memory,
728 simd,
729 relaxed_simd,
730 threads,
731 shared_everything_threads,
732 tail_call,
733 floats,
734 multi_memory,
735 exceptions,
736 memory64,
737 extended_const,
738 component_model,
739 function_references,
740 memory_control,
741 gc,
742 custom_page_sizes,
743 legacy_exceptions,
744 gc_types,
745 stack_switching,
746 wide_arithmetic,
747 cm_values,
748 cm_nested_names,
749 cm_async,
750 cm_async_stackful,
751 cm_more_async_builtins,
752 cm_threading,
753 cm_error_context,
754 cm_fixed_length_lists,
755 cm_gc,
756 call_indirect_overlong,
757 bulk_memory_opt,
758 custom_descriptors,
759 compact_imports,
760 cm_map,
761 cm64,
762 } = NOTE_CODEC_WASM_FEATURES.inflate();
763
764 for (name, required) in [
765 ("component model", component_model),
766 ("floats", floats),
767 ("mutable globals", mutable_global),
768 ("saturating float to int", saturating_float_to_int),
769 ("sign extension", sign_extension),
770 ("reference types", reference_types),
771 ("call-indirect overlong", call_indirect_overlong),
772 ("multi value", multi_value),
773 ("bulk memory", bulk_memory),
774 ("bulk memory opt", bulk_memory_opt),
775 ] {
776 assert!(required, "the policy must accept {name}");
777 }
778
779 for (name, rejected) in [
780 ("simd", simd),
781 ("relaxed simd", relaxed_simd),
782 ("threads", threads),
783 ("shared everything threads", shared_everything_threads),
784 ("tail call", tail_call),
785 ("multi memory", multi_memory),
786 ("exceptions", exceptions),
787 ("legacy exceptions", legacy_exceptions),
788 ("memory64", memory64),
789 ("extended const", extended_const),
790 ("function references", function_references),
791 ("memory control", memory_control),
792 ("gc", gc),
793 ("gc types", gc_types),
794 ("custom page sizes", custom_page_sizes),
795 ("stack switching", stack_switching),
796 ("wide arithmetic", wide_arithmetic),
797 ("component model values", cm_values),
798 ("component model nested names", cm_nested_names),
799 ("component model async", cm_async),
800 ("component model stackful async", cm_async_stackful),
801 ("component model async builtins", cm_more_async_builtins),
802 ("component model threading", cm_threading),
803 ("component model error context", cm_error_context),
804 ("component model fixed length lists", cm_fixed_length_lists),
805 ("component model gc", cm_gc),
806 ("component model maps", cm_map),
807 ("component model 64-bit contexts", cm64),
808 ("custom descriptors", custom_descriptors),
809 ("compact imports", compact_imports),
810 ] {
811 assert!(!rejected, "the policy must reject {name}");
812 }
813 }
814
815 #[test]
816 fn oversized_components_are_rejected_before_parsing() {
817 let error = validate_note_codec_component(&[0; 8], 4).unwrap_err().to_string();
818
819 assert!(error.contains("is 8 bytes"), "unexpected error: {error}");
820 assert!(error.contains("the pre-compilation limit is 4"), "unexpected error: {error}");
821 }
822
823 #[test]
824 fn too_many_globals_are_rejected() {
825 let globals = "(global i32 (i32.const 0))".repeat(MAX_MODULE_GLOBALS + 1);
826 let error = validate(&component(&globals)).unwrap_err().to_string();
827
828 assert!(error.contains("1001 globals"), "unexpected error: {error}");
829 assert!(error.contains("the limit is 1000"), "unexpected error: {error}");
830 }
831
832 #[test]
833 fn oversized_function_signatures_are_rejected() {
834 let params = "i32 ".repeat(MAX_FUNCTION_PARAMS + 1);
835 let module = format!("(type (func (param {params})))");
836 let error = validate(&component(&module)).unwrap_err().to_string();
837
838 assert!(error.contains("33 parameters"), "unexpected error: {error}");
839 assert!(error.contains("the limit is 32"), "unexpected error: {error}");
840 }
841
842 #[test]
843 fn many_tiny_functions_are_rejected() {
844 let error = validate(&component(&"(func)".repeat(600))).unwrap_err().to_string();
845
846 assert!(error.contains("600 functions"), "unexpected error: {error}");
847 assert!(error.contains("bytes per function"), "unexpected error: {error}");
848 }
849
850 #[test]
851 fn malformed_bytes_are_rejected() {
852 let error = validate(b"not a component").unwrap_err().to_string();
853
854 assert!(error.contains("note codec component is malformed"), "unexpected error: {error}");
855 }
856
857 #[test]
858 fn what_a_nested_component_creates_is_counted_once_per_instantiation() {
859 let text = "(component
863 (component $inner
864 (core module $m (memory 1))
865 (core instance (instantiate $m)))
866 (instance (instantiate $inner))
867 (instance (instantiate $inner)))";
868 let error = validate(&wat::parse_str(text).unwrap()).unwrap_err().to_string();
869
870 assert!(
871 error.contains("creates 2 linear memories when it is instantiated"),
872 "unexpected error: {error}"
873 );
874 assert!(
875 error.contains(&format!("the limit is {MAX_INSTANTIATED_MEMORIES}")),
876 "unexpected error: {error}"
877 );
878 }
879
880 #[test]
881 fn a_component_that_is_never_instantiated_creates_nothing() {
882 let text = "(component
883 (component $unused
884 (core module $m (memory 1))
885 (core instance (instantiate $m)))
886 (core module $m (memory 1))
887 (core instance (instantiate $m)))";
888
889 validate(&wat::parse_str(text).unwrap()).unwrap();
890 }
891
892 #[test]
893 fn too_many_component_instances_are_rejected() {
894 let instantiate = "(instance (instantiate $inner))".repeat(MAX_COMPONENT_INSTANCES + 1);
895 let text = format!("(component (component $inner) {instantiate})");
896 let error = validate(&wat::parse_str(text).unwrap()).unwrap_err().to_string();
897
898 assert!(
899 error.contains(&format!("creates {} component instances", MAX_COMPONENT_INSTANCES + 1)),
900 "unexpected error: {error}"
901 );
902 }
903
904 #[test]
905 fn instantiated_memories_are_counted_across_core_modules() {
906 let text = "(component
907 (core module $a (memory 1))
908 (core module $b (memory 1))
909 (core instance (instantiate $a))
910 (core instance (instantiate $b)))";
911 let error = validate(&wat::parse_str(text).unwrap()).unwrap_err().to_string();
912
913 assert!(
914 error.contains("creates 2 linear memories when it is instantiated"),
915 "unexpected error: {error}"
916 );
917 assert!(
918 error.contains(&format!("the limit is {MAX_INSTANTIATED_MEMORIES}")),
919 "unexpected error: {error}"
920 );
921 }
922
923 #[test]
924 fn one_module_instantiated_twice_is_counted_twice() {
925 let text = r#"(component
928 (core module $m (memory 1) (func (export "f")))
929 (core instance $a (instantiate $m))
930 (core instance $b (instantiate $m)))"#;
931 let error = validate(&wat::parse_str(text).unwrap()).unwrap_err().to_string();
932
933 assert!(
934 error.contains("creates 2 linear memories when it is instantiated"),
935 "unexpected error: {error}"
936 );
937 }
938
939 #[test]
940 fn a_core_module_the_walk_cannot_read_is_not_instantiable() {
941 let text = r#"(component
942 (import "m" (core module $m))
943 (core instance (instantiate $m)))"#;
944 let error = validate(&wat::parse_str(text).unwrap()).unwrap_err().to_string();
945
946 assert!(error.contains("which the policy cannot read"), "unexpected error: {error}");
947 }
948
949 #[test]
950 fn component_items_of_one_kind_are_capped_across_sections() {
951 let types = |count: usize| "(type u8)".repeat(count);
954 let text = format!(
955 "(component {first} (core module) {second})",
956 first = types(MAX_COMPONENT_SECTION_ITEMS),
957 second = types(1),
958 );
959 let error = validate(&wat::parse_str(text).unwrap()).unwrap_err().to_string();
960
961 assert!(
962 error.contains(&format!("{} component types", MAX_COMPONENT_SECTION_ITEMS + 1)),
963 "unexpected error: {error}"
964 );
965 assert!(
966 error.contains(&format!("the limit is {MAX_COMPONENT_SECTION_ITEMS}")),
967 "unexpected error: {error}"
968 );
969 }
970
971 #[test]
972 fn too_many_module_types_are_rejected() {
973 let types = "(type (func (param i32)))".repeat(MAX_MODULE_TYPES + 1);
974 let error = validate(&component(&types)).unwrap_err().to_string();
975
976 assert!(
977 error.contains(&format!("{} types", MAX_MODULE_TYPES + 1)),
978 "unexpected error: {error}"
979 );
980 }
981
982 #[test]
983 fn every_structural_rejection_carries_a_class() {
984 let error = validate(b"not a component").unwrap_err();
985
986 assert_eq!(error.codec_failure(), Some(crate::CodecFailure::LimitExceeded));
987 }
988
989 #[test]
990 fn component_start_functions_are_rejected() {
991 let text = r#"(component
992 (core module $m (func (export "f")))
993 (core instance $i (instantiate $m))
994 (func $f (canon lift (core func $i "f")))
995 (start $f))"#;
996 let error = validate(&wat::parse_str(text).unwrap()).unwrap_err().to_string();
997
998 assert!(error.contains("declares a start function"), "unexpected error: {error}");
999 }
1000
1001 #[cfg(feature = "codec-component")]
1002 #[test]
1003 fn fixture_component_passes() {
1004 if !midenc_integration_test_support::wasm_target_is_installed() {
1005 eprintln!("skipping the structural limit fixture test: wasm32-wasip2 is not installed");
1006 return;
1007 }
1008
1009 validate(&crate::codec_component::tests::build_fixture_component()).unwrap();
1010 }
1011}