Skip to main content

gear_core/code/
mod.rs

1// Copyright (C) Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4//! Module for checked code.
5
6use crate::{
7    gas_metering::{CustomConstantCostRules, Rules},
8    ids::{CodeId, prelude::*},
9};
10use alloc::vec::Vec;
11use gear_wasm_instrument::{GEAR_SUPPORTED_FEATURES, InstrumentationBuilder, Module};
12
13mod errors;
14mod instrumented;
15mod metadata;
16mod utils;
17
18pub use errors::*;
19pub use gear_wasm_instrument::SyscallKind;
20pub use instrumented::*;
21pub use metadata::*;
22pub use utils::{
23    ALLOWED_EXPORTS, MAX_WASM_PAGES_AMOUNT, REQUIRED_EXPORTS, get_custom_section_data,
24};
25
26use utils::CodeTypeSectionSizes;
27
28/// Generic OS page size. Approximated to 4KB as a most common value.
29const GENERIC_OS_PAGE_SIZE: u32 = 4096;
30
31/// Configuration for `Code::try_new_mock_`.
32/// By default all checks enabled.
33pub struct TryNewCodeConfig {
34    /// Instrumentation version
35    pub version: u32,
36    /// Stack height limit
37    pub stack_height: Option<u32>,
38    /// Limit of data section amount
39    pub data_segments_amount_limit: Option<u32>,
40    /// Limit on the number of tables.
41    pub table_amount_limit: Option<u32>,
42    /// Limit on the length of the type section.
43    pub type_section_len_limit: Option<u32>,
44    /// Limit on the number of parameters per type in type section.
45    pub type_section_params_per_type_limit: Option<u32>,
46    /// Export `STACK_HEIGHT_EXPORT_NAME` global
47    pub export_stack_height: bool,
48    /// Check exports (wasm contains init or handle exports)
49    pub check_exports: bool,
50    /// Check imports (check that all imports are valid syscalls with correct signature)
51    pub check_imports: bool,
52    /// Check and canonize stack end
53    pub check_and_canonize_stack_end: bool,
54    /// Check mutable global exports
55    pub check_mut_global_exports: bool,
56    /// Check start section (not allowed for programs)
57    pub check_start_section: bool,
58    /// Check data section
59    pub check_data_section: bool,
60    /// Check table section
61    pub check_table_section: bool,
62    /// Make wasmparser validation
63    pub make_validation: bool,
64    /// Syscall kind for imports check
65    pub syscall_kind: SyscallKind,
66}
67
68impl TryNewCodeConfig {
69    /// New default config without exports checks.
70    pub fn new_no_exports_check() -> Self {
71        Self {
72            check_exports: false,
73            ..Default::default()
74        }
75    }
76}
77
78impl Default for TryNewCodeConfig {
79    fn default() -> Self {
80        Self {
81            version: 1,
82            stack_height: None,
83            data_segments_amount_limit: None,
84            table_amount_limit: None,
85            type_section_len_limit: None,
86            type_section_params_per_type_limit: None,
87            export_stack_height: false,
88            check_exports: true,
89            check_imports: true,
90            check_and_canonize_stack_end: true,
91            check_mut_global_exports: true,
92            check_start_section: true,
93            check_data_section: true,
94            check_table_section: true,
95            make_validation: true,
96            syscall_kind: SyscallKind::default(),
97        }
98    }
99}
100
101/// Contains original and instrumented binary code of a program.
102#[derive(Clone, Debug, PartialEq, Eq)]
103pub struct Code {
104    original: Vec<u8>,
105    instrumented: InstrumentedCode,
106    metadata: CodeMetadata,
107}
108
109impl Code {
110    /// Create the code by checking and instrumenting `original_code`.
111    fn try_new_internal<R, GetRulesFn>(
112        original_code: Vec<u8>,
113        get_gas_rules: Option<GetRulesFn>,
114        config: TryNewCodeConfig,
115    ) -> Result<Self, CodeError>
116    where
117        R: Rules,
118        GetRulesFn: FnMut(&Module) -> R,
119    {
120        if config.make_validation {
121            wasmparser::Validator::new_with_features(GEAR_SUPPORTED_FEATURES)
122                .validate_all(&original_code)
123                .map_err(CodeError::Validation)?;
124        }
125
126        let mut module = Module::new(&original_code)?;
127
128        let static_pages = utils::get_static_pages(&module)?;
129
130        // Canonize stack end before any changes in module
131        let stack_end = match config.check_and_canonize_stack_end {
132            true => utils::check_and_canonize_gear_stack_end(&mut module, static_pages)?,
133            false => None,
134        };
135
136        // Not changing steps
137        if config.check_data_section {
138            utils::check_data_section(
139                &module,
140                static_pages,
141                stack_end,
142                config.data_segments_amount_limit,
143            )?;
144        }
145        if config.check_mut_global_exports {
146            utils::check_mut_global_exports(&module)?;
147        }
148        if config.check_start_section {
149            utils::check_start_section(&module)?;
150        }
151        if config.check_exports {
152            utils::check_exports(&module)?;
153        }
154        if config.check_imports {
155            utils::check_imports(&module, config.syscall_kind)?;
156        }
157        if let Some(limit) = config.type_section_params_per_type_limit {
158            utils::check_type_section(&module, limit)?;
159        }
160
161        // Get exports set before instrumentations.
162        let exports = utils::get_exports(&module);
163
164        let mut instrumentation_builder = InstrumentationBuilder::new("env");
165        if let Some(stack_limit) = config.stack_height {
166            instrumentation_builder.with_stack_limiter(stack_limit, config.export_stack_height);
167        }
168        if let Some(get_gas_rules) = get_gas_rules {
169            instrumentation_builder.with_gas_limiter(get_gas_rules);
170        }
171
172        module = instrumentation_builder.instrument(module)?;
173
174        // Use instrumented module to get section sizes.
175        let data_section_size = utils::get_data_section_size(&module)?;
176        let global_section_size = utils::get_instantiated_global_section_size(&module)?;
177        let table_section_size = utils::get_instantiated_table_section_size(&module);
178        let element_section_size = utils::get_instantiated_element_section_size(&module)?;
179
180        module.strip_custom_sections();
181
182        let code = module.serialize()?;
183
184        // Use instrumented code to get section sizes.
185        let CodeTypeSectionSizes {
186            code_section,
187            type_section,
188        } = utils::get_code_type_sections_sizes(&code, config.type_section_len_limit)?;
189
190        let instantiated_section_sizes = InstantiatedSectionSizes::new(
191            code_section,
192            data_section_size,
193            global_section_size,
194            table_section_size,
195            element_section_size,
196            type_section,
197        );
198
199        let instrumented_code = InstrumentedCode::new(code, instantiated_section_sizes);
200
201        let metadata = CodeMetadata::new(
202            original_code.len() as u32,
203            exports.clone(),
204            static_pages,
205            stack_end,
206            InstrumentationStatus::Instrumented {
207                version: config.version,
208                code_len: instrumented_code.bytes().len() as u32,
209            },
210        );
211
212        Ok(Self {
213            original: original_code,
214            instrumented: instrumented_code,
215            metadata,
216        })
217    }
218
219    /// Create the code by checking and instrumenting `original_code`.
220    /// Main logic of instrumentation can be represented by this example:
221    /// Let's take a code:
222    /// ```wasm
223    /// (module
224    ///    (import "env" "memory" (memory 1))
225    ///    (export "init" (func $init))
226    ///    (func $f1
227    ///       <-- f1 code -->
228    ///    )
229    ///    (func $f2
230    ///       if (i32.eqz (i32.const 0))
231    ///          <-- some code -->
232    ///       else
233    ///          <-- some code -->
234    ///       end
235    ///    )
236    ///    (func $f3
237    ///       <-- f3 code -->
238    ///    )
239    ///    (func $init
240    ///       call $f1
241    ///       call $f2
242    ///       call $f3
243    ///       <-- some code -->
244    ///    )
245    /// )
246    /// ```
247    ///
248    /// After instrumentation code will be like:
249    /// ```wasm
250    /// (module
251    ///   (import "env" "memory" (memory 1))
252    ///   (export "init" (func $init_export))
253    ///   (func $gas_charge
254    ///      <-- gas charge impl --> ;; see protocol/wasm-instrument/src/lib.rs
255    ///   )
256    ///   (func $f1
257    ///      i32.const 123
258    ///      call $gas_charge
259    ///      <-- f1 code -->
260    ///   )
261    ///   (func $f2
262    ///      i32.const 123
263    ///      call $gas_charge
264    ///      if (i32.eqz (i32.const 0))
265    ///         i32.const 1
266    ///         call $gas_charge
267    ///         <-- some code -->
268    ///      else
269    ///         i32.const 2
270    ///         call $gas_charge
271    ///         <-- some code -->
272    ///      end
273    ///   )
274    ///   (func $init
275    ///      i32.const 123
276    ///      call $gas_charge
277    ///      ;; stack limit check impl see in wasm_instrument::inject_stack_limiter
278    ///      <-- stack limit check and increase -->
279    ///      call $f1
280    ///      <-- stack limit decrease -->
281    ///      <-- stack limit check and increase -->
282    ///      call $f2
283    ///      <-- stack limit decrease -->
284    ///      <-- some code -->
285    ///   )
286    ///   (func $init_export
287    ///      i32.const 123
288    ///      call $gas_charge
289    ///      <-- stack limit check and increase -->
290    ///      call $init
291    ///      <-- stack limit decrease -->
292    ///   )
293    /// )
294    /// ```
295    #[allow(clippy::too_many_arguments)]
296    pub fn try_new<R, GetRulesFn>(
297        original_code: Vec<u8>,
298        version: u32,
299        get_gas_rules: GetRulesFn,
300        stack_height: Option<u32>,
301        data_segments_amount_limit: Option<u32>,
302        type_section_len_limit: Option<u32>,
303        type_section_params_per_type_limit: Option<u32>,
304        syscall_kind: SyscallKind,
305    ) -> Result<Self, CodeError>
306    where
307        R: Rules,
308        GetRulesFn: FnMut(&Module) -> R,
309    {
310        Self::try_new_internal(
311            original_code,
312            Some(get_gas_rules),
313            TryNewCodeConfig {
314                version,
315                stack_height,
316                data_segments_amount_limit,
317                type_section_len_limit,
318                type_section_params_per_type_limit,
319                syscall_kind,
320                ..Default::default()
321            },
322        )
323    }
324
325    /// Create new code for mock goals with const or no instrumentation rules.
326    pub fn try_new_mock_const_or_no_rules(
327        original_code: Vec<u8>,
328        const_rules: bool,
329        config: TryNewCodeConfig,
330    ) -> Result<Self, CodeError> {
331        let get_gas_rules =
332            const_rules.then_some(|_module: &Module| CustomConstantCostRules::default());
333        Self::try_new_internal(original_code, get_gas_rules, config)
334    }
335
336    /// Create new code for mock goals with custom instrumentation rules.
337    pub fn try_new_mock_with_rules<R, GetRulesFn>(
338        original_code: Vec<u8>,
339        get_gas_rules: GetRulesFn,
340        config: TryNewCodeConfig,
341    ) -> Result<Self, CodeError>
342    where
343        R: Rules,
344        GetRulesFn: FnMut(&Module) -> R,
345    {
346        Self::try_new_internal(original_code, Some(get_gas_rules), config)
347    }
348
349    /// Returns the original code.
350    pub fn original_code(&self) -> &[u8] {
351        &self.original
352    }
353
354    /// Returns the instrumented code.
355    pub fn instrumented_code(&self) -> &InstrumentedCode {
356        &self.instrumented
357    }
358
359    /// Returns the code metadata.
360    pub fn metadata(&self) -> &CodeMetadata {
361        &self.metadata
362    }
363
364    /// Consumes this instance and returns the instrumented and raw binary codes.
365    pub fn into_parts(self) -> (Vec<u8>, InstrumentedCode, CodeMetadata) {
366        (self.original, self.instrumented, self.metadata)
367    }
368
369    /// Consumes this instance and returns the instrumented code and metadata struct.
370    pub fn into_instrumented_code_and_metadata(self) -> InstrumentedCodeAndMetadata {
371        InstrumentedCodeAndMetadata {
372            instrumented_code: self.instrumented,
373            metadata: self.metadata,
374        }
375    }
376}
377
378/// The newtype contains the Code instance and the corresponding id (hash).
379#[derive(Clone, Debug)]
380pub struct CodeAndId {
381    code: Code,
382    code_id: CodeId,
383}
384
385impl CodeAndId {
386    /// Calculates the id (hash) of the raw binary code and creates new instance.
387    pub fn new(code: Code) -> Self {
388        let code_id = CodeId::generate(code.original_code());
389        Self { code, code_id }
390    }
391
392    /// Creates the instance from the precalculated hash without checks.
393    pub fn from_parts_unchecked(code: Code, code_id: CodeId) -> Self {
394        debug_assert_eq!(code_id, CodeId::generate(code.original_code()));
395        Self { code, code_id }
396    }
397
398    /// Creates the instance from the hash and incompatible with that hash code.
399    ///
400    /// # Safety
401    /// USE FOR TEST PURPOSES ONLY.
402    pub unsafe fn from_incompatible_parts(code: Code, code_id: CodeId) -> Self {
403        Self { code, code_id }
404    }
405
406    /// Returns corresponding id (hash) for the code.
407    pub fn code_id(&self) -> CodeId {
408        self.code_id
409    }
410
411    /// Returns reference to Code.
412    pub fn code(&self) -> &Code {
413        &self.code
414    }
415
416    /// Decomposes this instance.
417    pub fn into_parts(self) -> (Code, CodeId) {
418        (self.code, self.code_id)
419    }
420}
421
422/// The newtype contains the InstrumentedCode instance and the corresponding metadata.
423#[derive(Clone, Debug)]
424pub struct InstrumentedCodeAndMetadata {
425    /// Instrumented code.
426    pub instrumented_code: InstrumentedCode,
427    /// Code metadata.
428    pub metadata: CodeMetadata,
429}
430
431impl InstrumentedCodeAndMetadata {
432    /// Decomposes this instance into parts.
433    pub fn into_parts(self) -> (InstrumentedCode, CodeMetadata) {
434        (self.instrumented_code, self.metadata)
435    }
436}
437
438impl From<Code> for InstrumentedCodeAndMetadata {
439    fn from(code: Code) -> Self {
440        let (_, instrumented_code, metadata) = code.into_parts();
441        Self {
442            instrumented_code,
443            metadata,
444        }
445    }
446}
447
448#[cfg(test)]
449mod tests {
450    use crate::{
451        code::{
452            Code, CodeError, DataSectionError, ExportError, GENERIC_OS_PAGE_SIZE, ImportError,
453            StackEndError, SyscallKind, TryNewCodeConfig, TypeSectionError, utils::REF_TYPE_SIZE,
454        },
455        gas_metering::CustomConstantCostRules,
456    };
457    use alloc::{format, vec::Vec};
458    use gear_wasm_instrument::{InstrumentationError, ModuleError, STACK_END_EXPORT_NAME};
459
460    fn wat2wasm_with_validate(s: &str, validate: bool) -> Vec<u8> {
461        let code = wat::parse_str(s).unwrap();
462        if validate {
463            wasmparser::validate(&code).unwrap();
464        }
465        code
466    }
467
468    fn wat2wasm(s: &str) -> Vec<u8> {
469        wat2wasm_with_validate(s, true)
470    }
471
472    macro_rules! assert_code_err {
473        ($res:expr, $expected:pat) => {
474            let err = $res.expect_err("Code::try_new must return an error");
475            let expected_err = stringify!($expected);
476            assert!(
477                matches!(err, $expected),
478                "Must receive {:?}, got {:?}",
479                expected_err,
480                err
481            );
482        };
483    }
484
485    fn try_new_code_from_wat_with_params(
486        wat: &str,
487        stack_height: Option<u32>,
488        data_segments_amount_limit: Option<u32>,
489        make_validation: bool,
490    ) -> Result<Code, CodeError> {
491        Code::try_new_mock_const_or_no_rules(
492            wat2wasm(wat),
493            true,
494            TryNewCodeConfig {
495                stack_height,
496                data_segments_amount_limit,
497                make_validation,
498                ..Default::default()
499            },
500        )
501    }
502
503    fn try_new_code_from_wat(wat: &str, stack_height: Option<u32>) -> Result<Code, CodeError> {
504        try_new_code_from_wat_with_params(wat, stack_height, None, true)
505    }
506
507    #[test]
508    fn reject_unknown_exports() {
509        let wat = r#"
510            (module
511                (import "env" "memory" (memory 1))
512                (export "this_import_is_unknown" (func $test))
513                (func $test)
514            )
515        "#;
516
517        assert_code_err!(
518            try_new_code_from_wat(wat, None),
519            CodeError::Export(ExportError::ExcessExport(0))
520        );
521    }
522
523    #[test]
524    fn required_fn_not_found() {
525        let wat = r#"
526            (module
527                (import "env" "memory" (memory 1))
528                (export "handle_signal" (func $handle_signal))
529                (func $handle_signal)
530            )
531        "#;
532
533        assert_code_err!(
534            try_new_code_from_wat(wat, None),
535            CodeError::Export(ExportError::RequiredExportNotFound)
536        );
537    }
538
539    #[test]
540    fn stack_limit_injection_works() {
541        let wat = r#"
542            (module
543                (import "env" "memory" (memory 1))
544                (export "init" (func $init))
545                (func $init)
546            )
547        "#;
548
549        let _ = try_new_code_from_wat(wat, Some(16 * 1024)).unwrap();
550    }
551
552    #[test]
553    fn data_segment_out_of_static_memory() {
554        // Data segment end address is out of static memory.
555        let wat = r#"
556            (module
557                (import "env" "memory" (memory 1))
558                (export "init" (func $init))
559                (func $init)
560                (data (;0;) (i32.const 0x10000) "gear")
561            )
562        "#;
563
564        assert_code_err!(
565            try_new_code_from_wat(wat, None),
566            CodeError::DataSection(DataSectionError::EndAddressOutOfStaticMemory(
567                0x10000, 0x10003, 0x10000
568            ))
569        );
570
571        // Data segment last byte is next byte after static memory
572        let wat = r#"
573            (module
574                (import "env" "memory" (memory 1))
575                (export "init" (func $init))
576                (func $init)
577                (data (;0;) (i32.const 0xfffd) "gear")
578            )
579        "#;
580
581        assert_code_err!(
582            try_new_code_from_wat(wat, None),
583            CodeError::DataSection(DataSectionError::EndAddressOutOfStaticMemory(
584                0xfffd, 0x10000, 0x10000
585            ))
586        );
587    }
588
589    #[test]
590    fn data_segment_out_of_u32() {
591        // Data segment end address is out of possible 32 bits address space.
592        let wat = r#"
593            (module
594                (import "env" "memory" (memory 1))
595                (export "init" (func $init))
596                (func $init)
597                (data (;0;) (i32.const 0xffffffff) "gear")
598            )
599        "#;
600
601        assert_code_err!(
602            try_new_code_from_wat(wat, None),
603            CodeError::DataSection(DataSectionError::EndAddressOverflow(0xffffffff))
604        );
605    }
606
607    #[test]
608    fn data_segment_stack_overlaps() {
609        // Data segment overlaps gear stack.
610        let wat = format!(
611            r#"
612            (module
613                (import "env" "memory" (memory 3))
614                (export "init" (func $init))
615                (func $init)
616                (data (;0;) (i32.const 0x10000) "gear")
617                (export "{STACK_END_EXPORT_NAME}" (global 0))
618                (global (mut i32) (i32.const 0x20000))
619            )"#
620        );
621
622        assert_code_err!(
623            try_new_code_from_wat(wat.as_str(), None),
624            CodeError::DataSection(DataSectionError::GearStackOverlaps(0x10000, 0x20000))
625        );
626    }
627
628    #[test]
629    fn data_section() {
630        let wat = format!(
631            r#"
632            (module
633                (import "env" "memory" (memory 3))
634                (export "init" (func $init))
635                (func $init)
636                (data (i32.const 0x20000) "gear")
637                (data (i32.const 0x10000) "")     ;; empty data segment
638                (data (i32.const 0x1ffff) "gear") ;; overlapping other segments, also ok
639                (data (i32.const 0x2ffff) "g")    ;; one byte before the end of memory
640                (export "{STACK_END_EXPORT_NAME}" (global 0))
641                (global (mut i32) (i32.const 0x10000))
642            )"#
643        );
644
645        try_new_code_from_wat(wat.as_str(), None).expect("Must be ok");
646    }
647
648    #[test]
649    fn check_mutable_global_exports_restriction() {
650        let wat = r#"
651            (module
652                (import "env" "memory" (memory 0))
653                (func $init)
654                (export "init" (func $init))
655                (export "global" (global 0))
656                (global (;0;) (mut i32) (i32.const 0))
657            )"#;
658
659        assert_code_err!(
660            try_new_code_from_wat(wat, None),
661            CodeError::Export(ExportError::MutableGlobalExport(0, 1))
662        );
663    }
664
665    #[test]
666    fn stack_end_initialization() {
667        let wat = format!(
668            r#"
669            (module
670                (import "env" "memory" (memory 1))
671                (import "env" "unknown" (global i32))
672                (func $init)
673                (export "init" (func $init))
674                (export "{STACK_END_EXPORT_NAME}" (global 1))
675                (global (mut i32) (global.get 0))
676            )"#
677        );
678
679        assert_code_err!(
680            try_new_code_from_wat(wat.as_str(), None),
681            CodeError::StackEnd(StackEndError::Initialization)
682        );
683    }
684
685    #[test]
686    fn stack_end_alignment() {
687        let wat = format!(
688            r#"
689            (module
690                (import "env" "memory" (memory 2))
691                (func $init)
692                (export "init" (func $init))
693                (export "{STACK_END_EXPORT_NAME}" (global 0))
694                (global (;0;) (mut i32) (i32.const 0x10001))
695            )"#
696        );
697
698        assert_code_err!(
699            try_new_code_from_wat(wat.as_str(), None),
700            CodeError::StackEnd(StackEndError::NotAligned(0x10001))
701        );
702    }
703
704    #[test]
705    fn stack_end_out_of_static_memory() {
706        let wat = format!(
707            r#"
708            (module
709                (import "env" "memory" (memory 1))
710                (func $init)
711                (export "init" (func $init))
712                (export "{STACK_END_EXPORT_NAME}" (global 0))
713                (global (;0;) (mut i32) (i32.const 0x20000))
714            )"#
715        );
716
717        assert_code_err!(
718            try_new_code_from_wat(wat.as_str(), None),
719            CodeError::StackEnd(StackEndError::OutOfStatic(0x20000, 0x10000))
720        );
721    }
722
723    #[test]
724    fn stack_end() {
725        let wat = format!(
726            r#"
727            (module
728                (import "env" "memory" (memory 1))
729                (func $init)
730                (export "init" (func $init))
731                (export "{STACK_END_EXPORT_NAME}" (global 0))
732                (global (;0;) (mut i32) (i32.const 0x10000))
733            )"#
734        );
735
736        let code = try_new_code_from_wat(wat.as_str(), None).expect("Must be ok");
737        assert_eq!(code.metadata().stack_end(), Some(1.into()));
738    }
739
740    #[test]
741    fn export_to_imported_function() {
742        let wat = r#"
743            (module
744                (import "env" "memory" (memory 1))
745                (import "env" "gr_leave" (func $gr_leave))
746                (export "init" (func $gr_leave))
747                (func)
748            )"#;
749
750        assert_code_err!(
751            try_new_code_from_wat(wat, None),
752            CodeError::Export(ExportError::ExportReferencesToImportFunction(0, 0))
753        );
754    }
755
756    #[test]
757    fn export_to_imported_global() {
758        let wat = r#"
759            (module
760                (import "env" "memory" (memory 1))
761                (import "env" "global" (global i32))
762                (export "init" (func 0))
763                (export "global" (global 0))
764                (func)
765            )"#;
766
767        assert_code_err!(
768            try_new_code_from_wat(wat, None),
769            CodeError::Export(ExportError::ExportReferencesToImportGlobal(1, 0))
770        );
771    }
772
773    #[test]
774    fn multi_memory_import() {
775        let wat = r#"
776            (module
777                (import "env" "memory" (memory 1))
778                (import "env" "memory2" (memory 2))
779                (export "init" (func $init))
780                (func $init)
781            )
782        "#;
783
784        let res = Code::try_new(
785            wat2wasm_with_validate(wat, false),
786            1,
787            |_| CustomConstantCostRules::default(),
788            None,
789            None,
790            None,
791            None,
792            SyscallKind::Vara,
793        );
794
795        assert_code_err!(res, CodeError::Validation(_));
796    }
797
798    #[test]
799    fn global_import() {
800        let wat = r#"
801            (module
802                (import "env" "memory" (memory 1))
803                (import "env" "unknown" (global $unknown i32))
804                (export "init" (func $init))
805                (func $init)
806            )
807        "#;
808
809        assert_code_err!(
810            try_new_code_from_wat(wat, None),
811            CodeError::Import(ImportError::UnexpectedImportKind {
812                kind: &"Global",
813                index: 1
814            })
815        );
816    }
817
818    #[test]
819    fn table_import() {
820        let wat = r#"
821            (module
822                (import "env" "memory" (memory 1))
823                (import "env" "unknown" (table $unknown 10 20 funcref))
824                (export "init" (func $init))
825                (func $init)
826            )
827        "#;
828
829        assert_code_err!(
830            try_new_code_from_wat(wat, None),
831            CodeError::Import(ImportError::UnexpectedImportKind {
832                kind: &"Table",
833                index: 1
834            })
835        );
836    }
837
838    #[test]
839    fn data_segments_amount_limit() {
840        const DATA_SEGMENTS_AMOUNT_LIMIT: u32 = 1024;
841
842        let segment = r#"(data (i32.const 0x0) "gear")"#;
843
844        let wat = format!(
845            r#"
846            (module
847                (import "env" "memory" (memory 1))
848                (func $init)
849                (export "init" (func $init))
850                {}
851            )
852        "#,
853            segment.repeat(1025)
854        );
855
856        assert_code_err!(
857            try_new_code_from_wat_with_params(
858                wat.as_str(),
859                None,
860                DATA_SEGMENTS_AMOUNT_LIMIT.into(),
861                true,
862            ),
863            CodeError::DataSection(DataSectionError::DataSegmentsAmountLimit {
864                limit: DATA_SEGMENTS_AMOUNT_LIMIT,
865                actual: 1025
866            })
867        );
868    }
869
870    #[test]
871    fn type_section_limits() {
872        const TYPE_SECTION_LEN_LIMIT: u32 = 16;
873        const PARAMS_PER_TYPE_LIMIT: u32 = 10;
874
875        fn try_new_with_type_limits(
876            wat: &str,
877            type_section_len_limit: Option<u32>,
878            type_section_params_per_type_limit: Option<u32>,
879        ) -> Result<Code, CodeError> {
880            Code::try_new_mock_const_or_no_rules(
881                wat2wasm(wat),
882                true,
883                TryNewCodeConfig {
884                    type_section_len_limit,
885                    type_section_params_per_type_limit,
886                    stack_height: None,
887                    make_validation: true,
888                    ..Default::default()
889                },
890            )
891        }
892
893        let wat = r#"
894            (module
895                (import "env" "memory" (memory 1))
896                (func $init)
897                (export "init" (func $init))
898                (type (func (param i64 i64 i32 i32 i64 i32 i64 i64 i64 i32 i32 i64 i32 i64) (result i64)))
899            )"#;
900
901        assert_code_err!(
902            try_new_with_type_limits(wat, TYPE_SECTION_LEN_LIMIT.into(), None,),
903            CodeError::TypeSection(TypeSectionError::LengthLimitExceeded {
904                limit: TYPE_SECTION_LEN_LIMIT,
905                actual: 26,
906            })
907        );
908
909        assert_code_err!(
910            try_new_with_type_limits(wat, None, PARAMS_PER_TYPE_LIMIT.into(),),
911            CodeError::TypeSection(TypeSectionError::ParametersPerTypeLimitExceeded {
912                limit: PARAMS_PER_TYPE_LIMIT,
913                actual: 14,
914            })
915        );
916    }
917
918    #[test]
919    fn data_section_bytes() {
920        // Smoke
921        let wat = r#"
922            (module
923                (import "env" "memory" (memory 3))
924                (func $init)
925                (export "init" (func $init))
926                (data (i32.const 0x20000) "gear")
927            )
928        "#;
929
930        assert_eq!(
931            try_new_code_from_wat(wat, Some(1024))
932                .unwrap()
933                .instrumented_code()
934                .instantiated_section_sizes()
935                .data_section(),
936            GENERIC_OS_PAGE_SIZE,
937        );
938
939        // 2 adjacent
940        let wat = r#"
941            (module
942                (import "env" "memory" (memory 3))
943                (func $init)
944                (export "init" (func $init))
945                (data (i32.const 0x0000) "gear")
946                (data (i32.const 0x1000) "gear")
947            )
948        "#;
949
950        assert_eq!(
951            try_new_code_from_wat(wat, Some(1024))
952                .unwrap()
953                .instrumented_code()
954                .instantiated_section_sizes()
955                .data_section(),
956            GENERIC_OS_PAGE_SIZE * 2,
957        );
958
959        // 2 not adjacent
960        let wat = r#"
961            (module
962                (import "env" "memory" (memory 3))
963                (func $init)
964                (export "init" (func $init))
965                (data (i32.const  0x0000) "gear")
966                (data (i32.const 0x10000) "gear")
967            )
968        "#;
969
970        assert_eq!(
971            try_new_code_from_wat(wat, Some(1024))
972                .unwrap()
973                .instrumented_code()
974                .instantiated_section_sizes()
975                .data_section(),
976            GENERIC_OS_PAGE_SIZE * 2,
977        );
978
979        // 2 zero sized
980        let wat = r#"
981            (module
982                (import "env" "memory" (memory 3))
983                (func $init)
984                (export "init" (func $init))
985                (data (i32.const 0x0) "")
986                (data (i32.const 0x0) "")
987            )
988        "#;
989
990        assert_eq!(
991            try_new_code_from_wat(wat, Some(1024))
992                .unwrap()
993                .instrumented_code()
994                .instantiated_section_sizes()
995                .data_section(),
996            0,
997        );
998
999        // Overlap
1000        let wat = r#"
1001            (module
1002                (import "env" "memory" (memory 3))
1003                (func $init)
1004                (export "init" (func $init))
1005                (data (i32.const 0x20000) "gear")
1006                (data (i32.const 0x20001) "gear")
1007            )
1008        "#;
1009
1010        assert_eq!(
1011            try_new_code_from_wat(wat, Some(1024))
1012                .unwrap()
1013                .instrumented_code()
1014                .instantiated_section_sizes()
1015                .data_section(),
1016            GENERIC_OS_PAGE_SIZE,
1017        );
1018
1019        // Big segment
1020        let wat = format!(
1021            r#"
1022            (module
1023                (import "env" "memory" (memory 3))
1024                (func $init)
1025                (export "init" (func $init))
1026                (data (i32.const 0x20000) "{}")
1027            )
1028        "#,
1029            "a".repeat((GENERIC_OS_PAGE_SIZE + 1) as usize)
1030        );
1031
1032        assert_eq!(
1033            try_new_code_from_wat(&wat, Some(1024))
1034                .unwrap()
1035                .instrumented_code()
1036                .instantiated_section_sizes()
1037                .data_section(),
1038            GENERIC_OS_PAGE_SIZE * 2,
1039        );
1040
1041        // 2 big segments
1042        let wat = format!(
1043            r#"
1044            (module
1045                (import "env" "memory" (memory 3))
1046                (func $init)
1047                (export "init" (func $init))
1048                (data (i32.const 0x20000) "{0}")
1049                (data (i32.const 0x23000) "{1}")
1050            )
1051            "#,
1052            "a".repeat((GENERIC_OS_PAGE_SIZE * 3) as usize),
1053            "b".repeat((GENERIC_OS_PAGE_SIZE) as usize)
1054        );
1055
1056        assert_eq!(
1057            try_new_code_from_wat(&wat, Some(1024))
1058                .unwrap()
1059                .instrumented_code()
1060                .instantiated_section_sizes()
1061                .data_section(),
1062            GENERIC_OS_PAGE_SIZE * 4,
1063        );
1064
1065        // 2 big segments overlap
1066        let wat = format!(
1067            r#"
1068            (module
1069                (import "env" "memory" (memory 3))
1070                (func $init)
1071                (export "init" (func $init))
1072                (data (i32.const 0x20000) "{0}")
1073                (data (i32.const 0x21000) "{1}")
1074            )
1075            "#,
1076            "a".repeat((GENERIC_OS_PAGE_SIZE * 2 + 1) as usize),
1077            "b".repeat((GENERIC_OS_PAGE_SIZE * 2 + 1) as usize)
1078        );
1079
1080        assert_eq!(
1081            try_new_code_from_wat(&wat, Some(1024))
1082                .unwrap()
1083                .instrumented_code()
1084                .instantiated_section_sizes()
1085                .data_section(),
1086            GENERIC_OS_PAGE_SIZE * 4,
1087        );
1088    }
1089
1090    #[test]
1091    fn code_section_bytes() {
1092        const INSTRUMENTATION_CODE_SIZE: u32 = 74;
1093
1094        let wat = r#"
1095            (module
1096                (import "env" "memory" (memory 3))
1097                (func $init)
1098                (export "init" (func $init))
1099                (func $sum (param i32 i32) (result i32)
1100                    local.get 0
1101                    local.get 1
1102                    i32.add
1103                )
1104            )
1105        "#;
1106
1107        assert_eq!(
1108            try_new_code_from_wat(wat, Some(1024))
1109                .unwrap()
1110                .instrumented_code()
1111                .instantiated_section_sizes()
1112                .code_section(),
1113            INSTRUMENTATION_CODE_SIZE + 11,
1114        );
1115    }
1116
1117    #[test]
1118    fn global_section_bytes() {
1119        const INSTRUMENTATION_GLOBALS_SIZE: usize = size_of::<i32>() + size_of::<i64>();
1120
1121        let wat = r#"
1122            (module
1123                (import "env" "memory" (memory 3))
1124                (func $init)
1125                (export "init" (func $init))
1126                (global (mut i32) (i32.const 0))
1127                (global (mut i32) (i32.const 0))
1128                (global (mut i64) (i64.const 0))
1129            )
1130        "#;
1131
1132        assert_eq!(
1133            try_new_code_from_wat(wat, Some(1024))
1134                .unwrap()
1135                .instrumented_code()
1136                .instantiated_section_sizes()
1137                .global_section(),
1138            (INSTRUMENTATION_GLOBALS_SIZE + size_of::<i32>() * 2 + size_of::<i64>()) as u32,
1139        );
1140    }
1141
1142    #[test]
1143    fn element_section_bytes() {
1144        let wat = r#"
1145            (module
1146                (import "env" "memory" (memory 3))
1147                (func $init)
1148                (export "init" (func $init))
1149                (table 10 10 funcref)
1150                (elem (i32.const 1) 0 0 0 0)
1151            )
1152        "#;
1153
1154        assert_eq!(
1155            try_new_code_from_wat(wat, Some(1024))
1156                .unwrap()
1157                .instrumented_code()
1158                .instantiated_section_sizes()
1159                .table_section(),
1160            10 * REF_TYPE_SIZE,
1161        );
1162
1163        assert_eq!(
1164            try_new_code_from_wat(wat, Some(1024))
1165                .unwrap()
1166                .instrumented_code()
1167                .instantiated_section_sizes()
1168                .element_section(),
1169            REF_TYPE_SIZE * 4,
1170        );
1171    }
1172
1173    #[test]
1174    fn type_section_bytes() {
1175        let wat = r#"
1176            (module
1177                (import "env" "memory" (memory 3))
1178                (type (;35;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
1179                (type (;36;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
1180                (func $init)
1181                (export "init" (func $init))
1182            )
1183        "#;
1184
1185        assert_eq!(
1186            try_new_code_from_wat(wat, Some(1024))
1187                .unwrap()
1188                .instrumented_code()
1189                .instantiated_section_sizes()
1190                .type_section(),
1191            50,
1192        );
1193    }
1194
1195    #[test]
1196    fn unsupported_instruction() {
1197        // floats
1198        let res = try_new_code_from_wat_with_params(
1199            r#"
1200            (module
1201                (import "env" "memory" (memory 0 1))
1202                (func (result f64)
1203                    f64.const 10
1204                    f64.const 3
1205                    f64.div)
1206                (global i32 (i32.const 42))
1207                (func $init)
1208                (export "init" (func $init))
1209            )
1210            "#,
1211            Some(1024),
1212            None,
1213            // check not only `wasmparser` validator denies forbidden instructions
1214            false,
1215        );
1216
1217        assert!(matches!(
1218            res,
1219            Err(CodeError::Module(ModuleError::UnsupportedInstruction(_))),
1220        ));
1221
1222        // memory grow
1223        let res = try_new_code_from_wat(
1224            r#"
1225            (module
1226                (import "env" "memory" (memory 0 1))
1227                (func (result i32)
1228                    global.get 0
1229                    memory.grow
1230                )
1231                (global i32 (i32.const 42))
1232                (func $init)
1233                (export "init" (func $init))
1234        )"#,
1235            Some(1024),
1236        );
1237
1238        assert!(matches!(
1239            res,
1240            Err(CodeError::Instrumentation(
1241                InstrumentationError::GasInjection
1242            ))
1243        ));
1244    }
1245
1246    mod custom_section_tests {
1247        use crate::code::get_custom_section_data;
1248        use alloc::{vec, vec::Vec};
1249        use wasm_encoder::{CustomSection, Module, TypeSection};
1250
1251        fn make_wasm_with_custom_sections(sections: &[(&str, &[u8])]) -> Vec<u8> {
1252            let mut module = Module::new();
1253            let mut types = TypeSection::new();
1254            types.ty().function(vec![], vec![]);
1255            module.section(&types);
1256
1257            for (name, data) in sections {
1258                module.section(&CustomSection {
1259                    name: (*name).into(),
1260                    data: (*data).into(),
1261                });
1262            }
1263
1264            module.finish()
1265        }
1266
1267        #[test]
1268        fn section_found() {
1269            let wasm = make_wasm_with_custom_sections(&[("sails:idl", b"hello idl")]);
1270            let result = get_custom_section_data(&wasm, "sails:idl");
1271
1272            assert_eq!(result.unwrap().unwrap(), b"hello idl");
1273        }
1274
1275        #[test]
1276        fn section_not_found() {
1277            let wasm = make_wasm_with_custom_sections(&[("other", b"data")]);
1278            let result = get_custom_section_data(&wasm, "sails:idl");
1279
1280            assert_eq!(result.unwrap(), None);
1281        }
1282
1283        #[test]
1284        fn first_match_returned() {
1285            let wasm = make_wasm_with_custom_sections(&[
1286                ("sails:idl", b"first"),
1287                ("sails:idl", b"second"),
1288            ]);
1289            let result = get_custom_section_data(&wasm, "sails:idl");
1290
1291            assert_eq!(result.unwrap().unwrap(), b"first");
1292        }
1293
1294        #[test]
1295        fn invalid_wasm() {
1296            let result = get_custom_section_data(b"not wasm at all", "sails:idl");
1297
1298            assert!(result.is_err());
1299        }
1300
1301        #[test]
1302        fn malformed_after_section_errors() {
1303            let mut wasm = make_wasm_with_custom_sections(&[("sails:idl", b"hello idl")]);
1304            wasm.extend_from_slice(b"trailing junk");
1305
1306            let result = get_custom_section_data(&wasm, "sails:idl");
1307
1308            assert!(result.is_err());
1309        }
1310
1311        #[test]
1312        fn empty_input() {
1313            let result = get_custom_section_data(b"", "sails:idl");
1314
1315            assert!(result.is_err());
1316        }
1317    }
1318
1319    #[test]
1320    fn instrumented_code_strips_custom_sections_but_original_keeps_them() {
1321        // Build a valid gear program and inject a `sails:idl` custom section
1322        // into its bytes before instrumentation.
1323        let wat = r#"
1324            (module
1325                (import "env" "memory" (memory 1))
1326                (export "init" (func $init))
1327                (func $init)
1328            )
1329        "#;
1330        let base_bytes = wat2wasm(wat);
1331
1332        // Same mechanism sails tooling uses to embed the IDL.
1333        let idl_payload: Vec<u8> = (0..64u8).collect();
1334        let module = gear_wasm_instrument::Module::new(&base_bytes).unwrap();
1335        let mut builder = gear_wasm_instrument::ModuleBuilder::from_module(module);
1336        builder.push_custom_section("sails:idl", idl_payload);
1337        let original_with_idl = builder.build();
1338
1339        // Sanity: the constructed original actually carries the section.
1340        assert!(
1341            original_with_idl
1342                .custom_sections
1343                .as_ref()
1344                .is_some_and(|cs| cs.iter().any(|(section, _)| section == "sails:idl")),
1345            "test fixture must contain the sails:idl custom section before instrumentation"
1346        );
1347        let original_with_idl = original_with_idl.serialize().unwrap();
1348
1349        // Run through the full Code pipeline.
1350        let code = Code::try_new_mock_const_or_no_rules(
1351            original_with_idl.clone(),
1352            true,
1353            TryNewCodeConfig::default(),
1354        )
1355        .expect("valid gear program must instrument");
1356
1357        // OriginalCode preserves the section; IDL readers (RPC) rely on this.
1358        assert_eq!(code.original_code(), original_with_idl);
1359
1360        // InstrumentedCode must have the sails:idl section stripped.
1361        let instrumented = gear_wasm_instrument::Module::new(code.instrumented_code().bytes())
1362            .expect("instrumented code must parse");
1363        assert!(
1364            instrumented
1365                .custom_sections
1366                .as_ref()
1367                .is_none_or(|cs| cs.is_empty()),
1368            "InstrumentedCode must have no non-name custom sections"
1369        );
1370    }
1371}