// This file is part of Substrate. // Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . use crate::{ host::HostContext, runtime::{Store, StoreData}, }; use sc_executor_common::error::WasmError; use sp_wasm_interface::{FunctionContext, HostFunctions}; use std::{collections::HashMap, convert::TryInto}; use wasmtime::{Extern, ExternType, Func, FuncType, ImportType, Memory, MemoryType, Module, Trap}; pub struct Imports { /// Contains the index into `externs` where the memory import is stored if any. `None` if there /// is none. pub memory_import_index: Option, pub externs: Vec, } /// Goes over all imports of a module and prepares a vector of `Extern`s that can be used for /// instantiation of the module. Returns an error if there are imports that cannot be satisfied. pub(crate) fn resolve_imports( store: &mut Store, module: &Module, heap_pages: u64, allow_missing_func_imports: bool, ) -> Result where H: HostFunctions, { let mut externs = vec![]; let mut memory_import_index = None; let mut pending_func_imports = HashMap::new(); for (index, import_ty) in module.imports().enumerate() { let name = import_name(&import_ty)?; if import_ty.module() != "env" { return Err(WasmError::Other(format!( "host doesn't provide any imports from non-env module: {}:{}", import_ty.module(), name, ))) } if name == "memory" { memory_import_index = Some(index); externs.push((index, resolve_memory_import(store, &import_ty, heap_pages)?)); continue } match import_ty.ty() { ExternType::Func(func_ty) => { pending_func_imports.insert(name.to_owned(), (index, import_ty, func_ty)); }, _ => return Err(WasmError::Other(format!( "host doesn't provide any non function imports besides 'memory': {}:{}", import_ty.module(), name, ))), }; } let mut registry = Registry { store, externs, pending_func_imports }; H::register_static(&mut registry)?; let mut externs = registry.externs; if !registry.pending_func_imports.is_empty() { if allow_missing_func_imports { for (_, (index, import_ty, func_ty)) in registry.pending_func_imports { externs.push(( index, MissingHostFuncHandler::new(&import_ty)?.into_extern(store, &func_ty), )); } } else { let mut names = Vec::new(); for (name, (_, import_ty, _)) in registry.pending_func_imports { names.push(format!("'{}:{}'", import_ty.module(), name)); } let names = names.join(", "); return Err(WasmError::Other(format!( "runtime requires function imports which are not present on the host: {}", names ))) } } externs.sort_unstable_by_key(|&(index, _)| index); let externs = externs.into_iter().map(|(_, ext)| ext).collect(); Ok(Imports { memory_import_index, externs }) } struct Registry<'a, 'b> { store: &'a mut Store, externs: Vec<(usize, Extern)>, pending_func_imports: HashMap, FuncType)>, } impl<'a, 'b> sp_wasm_interface::HostFunctionRegistry for Registry<'a, 'b> { type State = StoreData; type Error = WasmError; type FunctionContext = HostContext<'a>; fn with_function_context( caller: wasmtime::Caller, callback: impl FnOnce(&mut dyn FunctionContext) -> R, ) -> R { callback(&mut HostContext { caller }) } fn register_static( &mut self, fn_name: &str, func: impl wasmtime::IntoFunc, ) -> Result<(), Self::Error> { if let Some((index, _, _)) = self.pending_func_imports.remove(fn_name) { let func = Func::wrap(&mut *self.store, func); self.externs.push((index, Extern::Func(func))); } Ok(()) } } /// When the module linking proposal is supported the import's name can be `None`. /// Because we are not using this proposal we could safely unwrap the name. /// However, we opt for an error in order to avoid panics at all costs. fn import_name<'a, 'b: 'a>(import: &'a ImportType<'b>) -> Result<&'a str, WasmError> { let name = import.name().ok_or_else(|| { WasmError::Other("The module linking proposal is not supported.".to_owned()) })?; Ok(name) } fn resolve_memory_import( store: &mut Store, import_ty: &ImportType, heap_pages: u64, ) -> Result { let requested_memory_ty = match import_ty.ty() { ExternType::Memory(memory_ty) => memory_ty, _ => return Err(WasmError::Other(format!( "this import must be of memory type: {}:{}", import_ty.module(), import_name(&import_ty)?, ))), }; // Increment the min (a.k.a initial) number of pages by `heap_pages` and check if it exceeds the // maximum specified by the import. let initial = requested_memory_ty.minimum().saturating_add(heap_pages); if let Some(max) = requested_memory_ty.maximum() { if initial > max { return Err(WasmError::Other(format!( "incremented number of pages by heap_pages (total={}) is more than maximum requested\ by the runtime wasm module {}", initial, max, ))) } } // Note that the return value of `maximum` and `minimum`, while a u64, // will always fit into a u32 for 32-bit memories. // 64-bit memories are part of the memory64 proposal for WebAssembly which is not standardized // yet. let minimum: u32 = initial.try_into().map_err(|_| { WasmError::Other(format!( "minimum number of memory pages ({}) doesn't fit into u32", initial )) })?; let maximum: Option = match requested_memory_ty.maximum() { Some(max) => Some(max.try_into().map_err(|_| { WasmError::Other(format!( "maximum number of memory pages ({}) doesn't fit into u32", max )) })?), None => None, }; let memory_ty = MemoryType::new(minimum, maximum); let memory = Memory::new(store, memory_ty).map_err(|e| { WasmError::Other(format!( "failed to create a memory during resolving of memory import: {}", e, )) })?; Ok(Extern::Memory(memory)) } /// A `Callable` handler for missing functions. struct MissingHostFuncHandler { module: String, name: String, } impl MissingHostFuncHandler { fn new(import_ty: &ImportType) -> Result { Ok(Self { module: import_ty.module().to_string(), name: import_name(import_ty)?.to_string(), }) } fn into_extern(self, store: &mut Store, func_ty: &FuncType) -> Extern { let Self { module, name } = self; let func = Func::new(store, func_ty.clone(), move |_, _, _| { Err(Trap::new(format!("call to a missing function {}:{}", module, name))) }); Extern::Func(func) } }