soroban_env_host/vm/
parsed_module.rs

1use crate::{
2    budget::{AsBudget, Budget},
3    err,
4    host::metered_clone::MeteredContainer,
5    meta,
6    xdr::{
7        ContractCodeEntry, ContractCodeEntryExt, ContractCostType, Limited, ReadXdr,
8        ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion, ScErrorCode, ScErrorType,
9    },
10    ErrorHandler, Host, HostError, Val, DEFAULT_XDR_RW_LIMITS,
11};
12
13use super::{Vm, HOST_FUNCTIONS};
14use std::{collections::BTreeSet, io::Cursor, sync::Arc};
15
16#[derive(Debug, Clone)]
17pub enum VersionedContractCodeCostInputs {
18    V0 { wasm_bytes: usize },
19    V1(crate::xdr::ContractCodeCostInputs),
20}
21
22impl VersionedContractCodeCostInputs {
23    pub fn is_v0(&self) -> bool {
24        match self {
25            Self::V0 { .. } => true,
26            Self::V1(_) => false,
27        }
28    }
29
30    pub fn charge_for_parsing(&self, budget: &impl AsBudget) -> Result<(), HostError> {
31        let budget = budget.as_budget();
32        match self {
33            Self::V0 { wasm_bytes } => {
34                budget.charge(ContractCostType::VmInstantiation, Some(*wasm_bytes as u64))?;
35            }
36            Self::V1(inputs) => {
37                budget.charge(
38                    ContractCostType::ParseWasmInstructions,
39                    Some(inputs.n_instructions as u64),
40                )?;
41                budget.charge(
42                    ContractCostType::ParseWasmFunctions,
43                    Some(inputs.n_functions as u64),
44                )?;
45                budget.charge(
46                    ContractCostType::ParseWasmGlobals,
47                    Some(inputs.n_globals as u64),
48                )?;
49                budget.charge(
50                    ContractCostType::ParseWasmTableEntries,
51                    Some(inputs.n_table_entries as u64),
52                )?;
53                budget.charge(
54                    ContractCostType::ParseWasmTypes,
55                    Some(inputs.n_types as u64),
56                )?;
57                budget.charge(
58                    ContractCostType::ParseWasmDataSegments,
59                    Some(inputs.n_data_segments as u64),
60                )?;
61                budget.charge(
62                    ContractCostType::ParseWasmElemSegments,
63                    Some(inputs.n_elem_segments as u64),
64                )?;
65                budget.charge(
66                    ContractCostType::ParseWasmImports,
67                    Some(inputs.n_imports as u64),
68                )?;
69                budget.charge(
70                    ContractCostType::ParseWasmExports,
71                    Some(inputs.n_exports as u64),
72                )?;
73                budget.charge(
74                    ContractCostType::ParseWasmDataSegmentBytes,
75                    Some(inputs.n_data_segment_bytes as u64),
76                )?;
77            }
78        }
79        Ok(())
80    }
81
82    pub fn charge_for_instantiation(&self, _host: &Host) -> Result<(), HostError> {
83        match self {
84            Self::V0 { wasm_bytes } => {
85                // Before soroban supported cached instantiation, the full cost
86                // of parsing-and-instantiation was charged to the
87                // VmInstantiation cost type and we already charged it by the
88                // time we got here, in `charge_for_parsing` above. At-and-after
89                // the protocol that enabled cached instantiation, the
90                // VmInstantiation cost type was repurposed to only cover the
91                // cost of parsing, so we have to charge the "second half" cost
92                // of instantiation separately here.
93                _host.charge_budget(
94                    ContractCostType::VmCachedInstantiation,
95                    Some(*wasm_bytes as u64),
96                )?;
97            }
98            Self::V1(inputs) => {
99                _host.charge_budget(ContractCostType::InstantiateWasmInstructions, None)?;
100                _host.charge_budget(
101                    ContractCostType::InstantiateWasmFunctions,
102                    Some(inputs.n_functions as u64),
103                )?;
104                _host.charge_budget(
105                    ContractCostType::InstantiateWasmGlobals,
106                    Some(inputs.n_globals as u64),
107                )?;
108                _host.charge_budget(
109                    ContractCostType::InstantiateWasmTableEntries,
110                    Some(inputs.n_table_entries as u64),
111                )?;
112                _host.charge_budget(ContractCostType::InstantiateWasmTypes, None)?;
113                _host.charge_budget(
114                    ContractCostType::InstantiateWasmDataSegments,
115                    Some(inputs.n_data_segments as u64),
116                )?;
117                _host.charge_budget(
118                    ContractCostType::InstantiateWasmElemSegments,
119                    Some(inputs.n_elem_segments as u64),
120                )?;
121                _host.charge_budget(
122                    ContractCostType::InstantiateWasmImports,
123                    Some(inputs.n_imports as u64),
124                )?;
125                _host.charge_budget(
126                    ContractCostType::InstantiateWasmExports,
127                    Some(inputs.n_exports as u64),
128                )?;
129                _host.charge_budget(
130                    ContractCostType::InstantiateWasmDataSegmentBytes,
131                    Some(inputs.n_data_segment_bytes as u64),
132                )?;
133            }
134        }
135        Ok(())
136    }
137}
138
139// A `CompilationContext` abstracts over the necessary budgeting and
140// error-reporting dimensions of both the `Host` (when building a
141// contract for throwaway use in an isolated context like contract-upload)
142// and other contexts that might want to compile code (like embedders that
143// precompile contracts).
144pub trait CompilationContext: AsBudget + ErrorHandler {}
145impl CompilationContext for Host {}
146
147/// A [ParsedModule] contains the parsed [wasmi::Module] for a given Wasm blob,
148/// as well as a protocol number and set of [ContractCodeCostInputs] extracted
149/// from the module when it was parsed.
150pub struct ParsedModule {
151    pub wasmi_module: wasmi::Module,
152    pub proto_version: u32,
153    pub cost_inputs: VersionedContractCodeCostInputs,
154}
155
156pub fn wasm_module_memory_cost(
157    budget: &Budget,
158    contract_code_entry: &ContractCodeEntry,
159) -> Result<u64, HostError> {
160    match &contract_code_entry.ext {
161        ContractCodeEntryExt::V0 => budget.get_memory_cost(
162            ContractCostType::VmInstantiation,
163            Some(contract_code_entry.code.len() as u64),
164        ),
165        ContractCodeEntryExt::V1(contract_code_entry_v1) => {
166            let cost_inputs = &contract_code_entry_v1.cost_inputs;
167            let mut res = 0_u64;
168            res = res.saturating_add(budget.get_memory_cost(
169                ContractCostType::ParseWasmInstructions,
170                Some(cost_inputs.n_instructions as u64),
171            )?);
172            res = res.saturating_add(budget.get_memory_cost(
173                ContractCostType::ParseWasmFunctions,
174                Some(cost_inputs.n_functions as u64),
175            )?);
176            res = res.saturating_add(budget.get_memory_cost(
177                ContractCostType::ParseWasmGlobals,
178                Some(cost_inputs.n_globals as u64),
179            )?);
180            res = res.saturating_add(budget.get_memory_cost(
181                ContractCostType::ParseWasmTableEntries,
182                Some(cost_inputs.n_table_entries as u64),
183            )?);
184            res = res.saturating_add(budget.get_memory_cost(
185                ContractCostType::ParseWasmTypes,
186                Some(cost_inputs.n_types as u64),
187            )?);
188            res = res.saturating_add(budget.get_memory_cost(
189                ContractCostType::ParseWasmDataSegments,
190                Some(cost_inputs.n_data_segments as u64),
191            )?);
192            res = res.saturating_add(budget.get_memory_cost(
193                ContractCostType::ParseWasmElemSegments,
194                Some(cost_inputs.n_elem_segments as u64),
195            )?);
196            res = res.saturating_add(budget.get_memory_cost(
197                ContractCostType::ParseWasmImports,
198                Some(cost_inputs.n_imports as u64),
199            )?);
200            res = res.saturating_add(budget.get_memory_cost(
201                ContractCostType::ParseWasmExports,
202                Some(cost_inputs.n_exports as u64),
203            )?);
204            res = res.saturating_add(budget.get_memory_cost(
205                ContractCostType::ParseWasmDataSegmentBytes,
206                Some(cost_inputs.n_data_segment_bytes as u64),
207            )?);
208            Ok(res)
209        }
210    }
211}
212
213impl ParsedModule {
214    pub fn new<Ctx: CompilationContext>(
215        context: &Ctx,
216        curr_ledger_protocol: u32,
217        wasmi_engine: &wasmi::Engine,
218        wasm: &[u8],
219        cost_inputs: VersionedContractCodeCostInputs,
220    ) -> Result<Arc<Self>, HostError> {
221        cost_inputs.charge_for_parsing(context.as_budget())?;
222        let (wasmi_module, proto_version) =
223            Self::parse_wasm(context, curr_ledger_protocol, wasmi_engine, wasm)?;
224        Ok(Arc::new(Self {
225            wasmi_module,
226            proto_version,
227            cost_inputs,
228        }))
229    }
230
231    pub fn with_import_symbols<T>(
232        &self,
233        host: &Host,
234        callback: impl FnOnce(&BTreeSet<(&str, &str)>) -> Result<T, HostError>,
235    ) -> Result<T, HostError> {
236        // Cap symbols we're willing to import at 10 characters for each of
237        // module and function name. in practice they are all 1-2 chars, but
238        // we'll leave some future-proofing room here. The important point
239        // is to not be introducing a DoS vector.
240        const SYM_LEN_LIMIT: usize = 10;
241        let symbols: BTreeSet<(&str, &str)> = self
242            .wasmi_module
243            .imports()
244            .filter_map(|i| {
245                if i.ty().func().is_some() {
246                    let mod_str = i.module();
247                    let fn_str = i.name();
248                    if mod_str.len() < SYM_LEN_LIMIT && fn_str.len() < SYM_LEN_LIMIT {
249                        return Some((mod_str, fn_str));
250                    }
251                }
252                None
253            })
254            .collect();
255
256        // We approximate the cost of `BTreeSet` with the cost of initializng a
257        // `Vec` with the same elements, and we are doing it after the set has
258        // been created. The element count has been limited/charged during the
259        // parsing phase, so there is no DOS factor. We don't charge for
260        // insertion/lookups, since they should be cheap and number of
261        // operations on the set is limited.
262        Vec::<(&str, &str)>::charge_bulk_init_cpy(symbols.len() as u64, host)?;
263        callback(&symbols)
264    }
265
266    pub fn make_wasmi_linker(&self, host: &Host) -> Result<wasmi::Linker<Host>, HostError> {
267        self.with_import_symbols(host, |symbols| {
268            Host::make_minimal_wasmi_linker_for_symbols(host, self.wasmi_module.engine(), symbols)
269        })
270    }
271
272    pub fn new_with_isolated_engine(
273        host: &Host,
274        wasm: &[u8],
275        cost_inputs: VersionedContractCodeCostInputs,
276    ) -> Result<Arc<Self>, HostError> {
277        use crate::budget::AsBudget;
278        let wasmi_config = crate::vm::get_wasmi_config(host.as_budget())?;
279        let wasmi_engine = wasmi::Engine::new(&wasmi_config);
280
281        Self::new(
282            host,
283            host.get_ledger_protocol_version()?,
284            &wasmi_engine,
285            wasm,
286            cost_inputs,
287        )
288    }
289
290    /// Parse the Wasm blob into a [Module] and its protocol number, checking its interface version
291    fn parse_wasm<Ctx: CompilationContext>(
292        context: &Ctx,
293        curr_ledger_protocol: u32,
294        wasmi_engine: &wasmi::Engine,
295        wasm: &[u8],
296    ) -> Result<(wasmi::Module, u32), HostError> {
297        let module = {
298            let _span = tracy_span!("wasmi::Module::new");
299            context.map_err(wasmi::Module::new(&wasmi_engine, wasm))?
300        };
301        Self::check_max_args(context, &module)?;
302        let interface_version = Self::check_meta_section(context, curr_ledger_protocol, &module)?;
303        let contract_proto = interface_version.protocol;
304
305        Ok((module, contract_proto))
306    }
307
308    fn check_contract_interface_version<Ctx: CompilationContext>(
309        context: &Ctx,
310        curr_ledger_protocol: u32,
311        interface_version: &ScEnvMetaEntryInterfaceVersion,
312    ) -> Result<(), HostError> {
313        let want_proto = {
314            let env_proto = meta::INTERFACE_VERSION.protocol;
315            if curr_ledger_protocol <= env_proto {
316                // ledger proto should be before or equal to env proto
317                curr_ledger_protocol
318            } else {
319                return Err(context.error(
320                    (ScErrorType::Context, ScErrorCode::InternalError).into(),
321                    "ledger protocol number is ahead of supported env protocol number",
322                    &[
323                        Val::from_u32(curr_ledger_protocol).to_val(),
324                        Val::from_u32(env_proto).to_val(),
325                    ],
326                ));
327            }
328        };
329
330        // Not used when "next" is enabled
331        #[cfg(not(feature = "next"))]
332        let got_pre = interface_version.pre_release;
333
334        let got_proto = interface_version.protocol;
335
336        if got_proto < want_proto {
337            // Old protocols are finalized, we only support contracts
338            // with similarly finalized (zero) prerelease numbers.
339            //
340            // Note that we only enable this check if the "next" feature isn't enabled
341            // because a "next" stellar-core can still run a "curr" test using non-finalized
342            // test Wasms. The "next" feature isn't safe for production and is meant to
343            // simulate the protocol version after the one currently supported in
344            // stellar-core, so bypassing this check for "next" is safe.
345            #[cfg(not(feature = "next"))]
346            if got_pre != 0 {
347                return Err(context.error(
348                    (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
349                    "contract pre-release number for old protocol is nonzero",
350                    &[Val::from_u32(got_pre).to_val()],
351                ));
352            }
353        } else if got_proto == want_proto {
354            // Relax this check as well for the "next" feature to allow for flexibility while testing.
355            // stellar-core can pass in an older protocol version, in which case the pre-release version
356            // will not match up with the "next" feature (The "next" pre-release version is always 1).
357            #[cfg(not(feature = "next"))]
358            {
359                // Current protocol might have a nonzero prerelease number; we will
360                // allow it only if it matches the current prerelease exactly.
361                let want_pre = meta::INTERFACE_VERSION.pre_release;
362                if want_pre != got_pre {
363                    return Err(context.error(
364                        (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
365                        "contract pre-release number for current protocol does not match host",
366                        &[
367                            Val::from_u32(got_pre).to_val(),
368                            Val::from_u32(want_pre).to_val(),
369                        ],
370                    ));
371                }
372            }
373        } else {
374            // Future protocols we don't allow. It might be nice (in the sense
375            // of "allowing uploads of a future-protocol contract that will go
376            // live as soon as the network upgrades to it") but there's a risk
377            // that the "future" protocol semantics baked in to a contract
378            // differ from the final semantics chosen by the network, so to be
379            // conservative we avoid even allowing this.
380            return Err(context.error(
381                (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
382                "contract protocol number is newer than host",
383                &[Val::from_u32(got_proto).to_val()],
384            ));
385        }
386        Ok(())
387    }
388
389    pub(crate) fn check_contract_imports_match_host_protocol(
390        &self,
391        host: &Host,
392    ) -> Result<(), HostError> {
393        // We perform instantiation-time protocol version gating of
394        // all module-imported symbols here.
395        // Reasons for doing link-time instead of run-time check:
396        // 1. VM instantiation is performed in both contract upload and
397        //    execution, thus any errorous contract will be rejected at
398        //    upload time.
399        // 2. If a contract contains a call to an outdated host function,
400        //    i.e. `contract_protocol > hf.max_supported_protocol`, failing
401        //    early is preferred from resource usage perspective.
402        // 3. If a contract contains a call to an non-existent host
403        //    function, the current (correct) behavior is to return
404        //    `Wasmi::errors::LinkerError::MissingDefinition` error (which gets
405        //    converted to a `(WasmVm, InvalidAction)`). If that host
406        //    function is defined in a later protocol, and we replay that
407        //    contract (in the earlier protocol where it belongs), we need
408        //    to return the same error.
409        let _span = tracy_span!("ParsedModule::check_contract_imports_match_host_protocol");
410        let ledger_proto = host.with_ledger_info(|li| Ok(li.protocol_version))?;
411        self.with_import_symbols(host, |module_symbols| {
412                for hf in HOST_FUNCTIONS {
413                    if !module_symbols.contains(&(hf.mod_str, hf.fn_str)) {
414                        continue;
415                    }
416                    if let Some(min_proto) = hf.min_proto {
417                        if self.proto_version < min_proto || ledger_proto < min_proto {
418                            return Err(host.err(
419                                ScErrorType::WasmVm,
420                                ScErrorCode::InvalidAction,
421                                "contract calls a host function not yet supported by current protocol",
422                                &[],
423                            ));
424                        }
425                    }
426                    if let Some(max_proto) = hf.max_proto {
427                        if self.proto_version > max_proto || ledger_proto > max_proto {
428                            return Err(host.err(
429                                ScErrorType::WasmVm,
430                                ScErrorCode::InvalidAction,
431                                "contract calls a host function no longer supported in the current protocol",
432                                &[],
433                            ));
434                        }
435                    }
436                }
437                Ok(())
438            })?;
439        Ok(())
440    }
441
442    fn module_custom_section(m: &wasmi::Module, name: impl AsRef<str>) -> Option<&[u8]> {
443        m.custom_sections().iter().find_map(|s| {
444            if &*s.name == name.as_ref() {
445                Some(&*s.data)
446            } else {
447                None
448            }
449        })
450    }
451
452    /// Returns the raw bytes content of a named custom section from the Wasm
453    /// module loaded into the [Vm], or `None` if no such custom section exists.
454    pub fn custom_section(&self, name: impl AsRef<str>) -> Option<&[u8]> {
455        Self::module_custom_section(&self.wasmi_module, name)
456    }
457
458    fn check_meta_section<Ctx: CompilationContext>(
459        context: &Ctx,
460        curr_ledger_protocol: u32,
461        m: &wasmi::Module,
462    ) -> Result<ScEnvMetaEntryInterfaceVersion, HostError> {
463        if let Some(env_meta) = Self::module_custom_section(m, meta::ENV_META_V0_SECTION_NAME) {
464            let mut limits = DEFAULT_XDR_RW_LIMITS;
465            limits.len = env_meta.len();
466            let mut cursor = Limited::new(Cursor::new(env_meta), limits);
467            if let Some(env_meta_entry) = ScEnvMetaEntry::read_xdr_iter(&mut cursor).next() {
468                let ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) =
469                    context.map_err(env_meta_entry)?;
470                Self::check_contract_interface_version(context, curr_ledger_protocol, &v)?;
471                Ok(v)
472            } else {
473                Err(context.error(
474                    (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
475                    "contract missing environment interface version",
476                    &[],
477                ))
478            }
479        } else {
480            Err(context.error(
481                (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
482                "contract missing metadata section",
483                &[],
484            ))
485        }
486    }
487
488    fn check_max_args<E: ErrorHandler>(handler: &E, m: &wasmi::Module) -> Result<(), HostError> {
489        for e in m.exports() {
490            match e.ty() {
491                wasmi::ExternType::Func(f) => {
492                    if f.results().len() > Vm::MAX_VM_ARGS {
493                        return Err(handler.error(
494                            (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
495                            "Too many return values in Wasm export",
496                            &[Val::from_u32(f.results().len() as u32).to_val()],
497                        ));
498                    }
499                    if f.params().len() > Vm::MAX_VM_ARGS {
500                        return Err(handler.error(
501                            (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
502                            "Too many arguments Wasm export",
503                            &[Val::from_u32(f.params().len() as u32).to_val()],
504                        ));
505                    }
506                }
507                _ => (),
508            }
509        }
510        Ok(())
511    }
512
513    // Do a second, manual parse of the Wasm blob to extract cost parameters we're
514    // interested in.
515    pub fn extract_refined_contract_cost_inputs(
516        host: &Host,
517        wasm: &[u8],
518    ) -> Result<crate::xdr::ContractCodeCostInputs, HostError> {
519        use wasmparser::{ElementItems, ElementKind, Parser, Payload::*, TableInit};
520
521        if !Parser::is_core_wasm(wasm) {
522            return Err(host.err(
523                ScErrorType::WasmVm,
524                ScErrorCode::InvalidInput,
525                "unsupported non-core wasm module",
526                &[],
527            ));
528        }
529
530        let mut costs = crate::xdr::ContractCodeCostInputs {
531            ext: crate::xdr::ExtensionPoint::V0,
532            n_instructions: 0,
533            n_functions: 0,
534            n_globals: 0,
535            n_table_entries: 0,
536            n_types: 0,
537            n_data_segments: 0,
538            n_elem_segments: 0,
539            n_imports: 0,
540            n_exports: 0,
541            n_data_segment_bytes: 0,
542        };
543
544        let parser = Parser::new(0);
545        let mut elements: u32 = 0;
546        let mut available_memory: u32 = 0;
547        for section in parser.parse_all(wasm) {
548            let section = host.map_err(section)?;
549            match section {
550                // Ignored sections.
551                Version { .. }
552                | DataCountSection { .. }
553                | CustomSection(_)
554                | CodeSectionStart { .. }
555                | End(_) => (),
556
557                // Component-model stuff or other unsupported sections. Error out.
558                StartSection { .. }
559                | ModuleSection { .. }
560                | InstanceSection(_)
561                | CoreTypeSection(_)
562                | ComponentSection { .. }
563                | ComponentInstanceSection(_)
564                | ComponentAliasSection(_)
565                | ComponentTypeSection(_)
566                | ComponentCanonicalSection(_)
567                | ComponentStartSection { .. }
568                | ComponentImportSection(_)
569                | ComponentExportSection(_)
570                | TagSection(_)
571                | UnknownSection { .. } => {
572                    return Err(host.err(
573                        ScErrorType::WasmVm,
574                        ScErrorCode::InvalidInput,
575                        "unsupported wasm section type",
576                        &[],
577                    ))
578                }
579
580                MemorySection(s) => {
581                    for mem in s {
582                        let mem = host.map_err(mem)?;
583                        if mem.memory64 {
584                            return Err(host.err(
585                                ScErrorType::WasmVm,
586                                ScErrorCode::InvalidInput,
587                                "unsupported 64-bit memory",
588                                &[],
589                            ));
590                        }
591                        if mem.shared {
592                            return Err(host.err(
593                                ScErrorType::WasmVm,
594                                ScErrorCode::InvalidInput,
595                                "unsupported shared memory",
596                                &[],
597                            ));
598                        }
599                        if mem
600                            .initial
601                            .saturating_mul(crate::vm::WASM_STD_MEM_PAGE_SIZE_IN_BYTES as u64)
602                            > u32::MAX as u64
603                        {
604                            return Err(host.err(
605                                ScErrorType::WasmVm,
606                                ScErrorCode::InvalidInput,
607                                "unsupported memory size",
608                                &[],
609                            ));
610                        }
611                        available_memory = available_memory.saturating_add(
612                            (mem.initial as u32)
613                                .saturating_mul(crate::vm::WASM_STD_MEM_PAGE_SIZE_IN_BYTES),
614                        );
615                    }
616                }
617
618                TypeSection(s) => costs.n_types = costs.n_types.saturating_add(s.count()),
619                ImportSection(s) => costs.n_imports = costs.n_imports.saturating_add(s.count()),
620                FunctionSection(s) => {
621                    costs.n_functions = costs.n_functions.saturating_add(s.count())
622                }
623                TableSection(s) => {
624                    for table in s {
625                        let table = host.map_err(table)?;
626                        costs.n_table_entries =
627                            costs.n_table_entries.saturating_add(table.ty.initial);
628                        match table.init {
629                            TableInit::RefNull => (),
630                            TableInit::Expr(ref expr) => {
631                                Self::check_const_expr_simple(&host, &expr)?;
632                            }
633                        }
634                    }
635                }
636                GlobalSection(s) => {
637                    costs.n_globals = costs.n_globals.saturating_add(s.count());
638                    for global in s {
639                        let global = host.map_err(global)?;
640                        Self::check_const_expr_simple(&host, &global.init_expr)?;
641                    }
642                }
643                ExportSection(s) => costs.n_exports = costs.n_exports.saturating_add(s.count()),
644                ElementSection(s) => {
645                    costs.n_elem_segments = costs.n_elem_segments.saturating_add(s.count());
646                    for elem in s {
647                        let elem = host.map_err(elem)?;
648                        match elem.kind {
649                            ElementKind::Declared | ElementKind::Passive => (),
650                            ElementKind::Active { offset_expr, .. } => {
651                                Self::check_const_expr_simple(&host, &offset_expr)?
652                            }
653                        }
654                        match elem.items {
655                            ElementItems::Functions(fs) => {
656                                elements = elements.saturating_add(fs.count());
657                            }
658                            ElementItems::Expressions(_, exprs) => {
659                                elements = elements.saturating_add(exprs.count());
660                                for expr in exprs {
661                                    let expr = host.map_err(expr)?;
662                                    Self::check_const_expr_simple(&host, &expr)?;
663                                }
664                            }
665                        }
666                    }
667                }
668                DataSection(s) => {
669                    costs.n_data_segments = costs.n_data_segments.saturating_add(s.count());
670                    for d in s {
671                        let d = host.map_err(d)?;
672                        if d.data.len() > u32::MAX as usize {
673                            return Err(host.err(
674                                ScErrorType::WasmVm,
675                                ScErrorCode::InvalidInput,
676                                "data segment exceeds u32::MAX",
677                                &[],
678                            ));
679                        }
680                        costs.n_data_segment_bytes = costs
681                            .n_data_segment_bytes
682                            .saturating_add(d.data.len() as u32);
683                        match d.kind {
684                            wasmparser::DataKind::Active { offset_expr, .. } => {
685                                Self::check_const_expr_simple(&host, &offset_expr)?
686                            }
687                            wasmparser::DataKind::Passive => (),
688                        }
689                    }
690                }
691                CodeSectionEntry(s) => {
692                    let ops = host.map_err(s.get_operators_reader())?;
693                    for _op in ops {
694                        costs.n_instructions = costs.n_instructions.saturating_add(1);
695                    }
696                }
697            }
698        }
699        if costs.n_data_segment_bytes > available_memory {
700            return Err(err!(
701                host,
702                (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
703                "data segment(s) content exceeds memory size",
704                costs.n_data_segment_bytes,
705                available_memory
706            ));
707        }
708        if elements > costs.n_table_entries {
709            return Err(err!(
710                host,
711                (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
712                "elem segments(s) content exceeds table size",
713                elements,
714                costs.n_table_entries
715            ));
716        }
717        Ok(costs)
718    }
719
720    fn check_const_expr_simple(host: &Host, expr: &wasmparser::ConstExpr) -> Result<(), HostError> {
721        use wasmparser::Operator::*;
722        let mut op = expr.get_operators_reader();
723        while !op.eof() {
724            match host.map_err(op.read())? {
725                I32Const { .. } | I64Const { .. } | RefFunc { .. } | RefNull { .. } | End => (),
726                _ => {
727                    return Err(host.err(
728                        ScErrorType::WasmVm,
729                        ScErrorCode::InvalidInput,
730                        "unsupported complex Wasm constant expression",
731                        &[],
732                    ))
733                }
734            }
735        }
736        Ok(())
737    }
738}