use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy};
use wasm_instrument::{
export_mutable_globals,
parity_wasm::elements::{
deserialize_buffer, serialize, DataSegment, ExportEntry, External, Internal, MemorySection,
MemoryType, Module, Section,
},
};
#[derive(Clone)]
pub struct RuntimeBlob {
raw_module: Module,
}
impl RuntimeBlob {
pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result<Self, WasmError> {
use sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT;
let wasm_code = sp_maybe_compressed_blob::decompress(wasm_code, CODE_BLOB_BOMB_LIMIT)
.map_err(|e| WasmError::Other(format!("Decompression error: {:?}", e)))?;
Self::new(&wasm_code)
}
pub fn new(wasm_code: &[u8]) -> Result<Self, WasmError> {
let raw_module: Module = deserialize_buffer(wasm_code)
.map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?;
Ok(Self { raw_module })
}
pub(super) fn data_segments(&self) -> Vec<DataSegment> {
self.raw_module.data_section().map(|ds| ds.entries()).unwrap_or(&[]).to_vec()
}
pub fn declared_globals_count(&self) -> u32 {
self.raw_module
.global_section()
.map(|gs| gs.entries().len() as u32)
.unwrap_or(0)
}
pub fn imported_globals_count(&self) -> u32 {
self.raw_module.import_section().map(|is| is.globals() as u32).unwrap_or(0)
}
pub fn expose_mutable_globals(&mut self) {
export_mutable_globals(&mut self.raw_module, "exported_internal_global");
}
pub fn inject_stack_depth_metering(self, stack_depth_limit: u32) -> Result<Self, WasmError> {
let injected_module =
wasm_instrument::inject_stack_limiter(self.raw_module, stack_depth_limit).map_err(
|e| WasmError::Other(format!("cannot inject the stack limiter: {:?}", e)),
)?;
Ok(Self { raw_module: injected_module })
}
pub fn entry_point_exists(&self, entry_point: &str) -> bool {
self.raw_module
.export_section()
.map(|e| {
e.entries().iter().any(|e| {
matches!(e.internal(), Internal::Function(_)) && e.field() == entry_point
})
})
.unwrap_or_default()
}
pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> {
let import_section = match self.raw_module.import_section_mut() {
Some(import_section) => import_section,
None => return Ok(()),
};
let import_entries = import_section.entries_mut();
for index in 0..import_entries.len() {
let entry = &import_entries[index];
let memory_ty = match entry.external() {
External::Memory(memory_ty) => *memory_ty,
_ => continue,
};
let memory_name = entry.field().to_owned();
import_entries.remove(index);
self.raw_module
.insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty])))
.map_err(|error| {
WasmError::Other(format!(
"can't convert a memory import into an export: failed to insert a new memory section: {}",
error
))
})?;
if self.raw_module.export_section_mut().is_none() {
self.raw_module
.insert_section(Section::Export(Default::default()))
.expect("an export section can be always inserted if it doesn't exist; qed");
}
self.raw_module
.export_section_mut()
.expect("export section already existed or we just added it above, so it always exists; qed")
.entries_mut()
.push(ExportEntry::new(memory_name, Internal::Memory(0)));
break
}
Ok(())
}
pub fn setup_memory_according_to_heap_alloc_strategy(
&mut self,
heap_alloc_strategy: HeapAllocStrategy,
) -> Result<(), WasmError> {
let memory_section = self
.raw_module
.memory_section_mut()
.ok_or_else(|| WasmError::Other("no memory section found".into()))?;
if memory_section.entries().is_empty() {
return Err(WasmError::Other("memory section is empty".into()))
}
for memory_ty in memory_section.entries_mut() {
let initial = memory_ty.limits().initial();
let (min, max) = match heap_alloc_strategy {
HeapAllocStrategy::Dynamic { maximum_pages } => {
(maximum_pages.map(|m| m.min(initial)).unwrap_or(initial), maximum_pages)
},
HeapAllocStrategy::Static { extra_pages } => {
let pages = initial.saturating_add(extra_pages);
(pages, Some(pages))
},
};
*memory_ty = MemoryType::new(min, max);
}
Ok(())
}
pub(super) fn exported_internal_global_names(&self) -> impl Iterator<Item = &str> {
let exports = self.raw_module.export_section().map(|es| es.entries()).unwrap_or(&[]);
exports.iter().filter_map(|export| match export.internal() {
Internal::Global(_) if export.field().starts_with("exported_internal_global") =>
Some(export.field()),
_ => None,
})
}
pub fn custom_section_contents(&self, section_name: &str) -> Option<&[u8]> {
self.raw_module
.custom_sections()
.find(|cs| cs.name() == section_name)
.map(|cs| cs.payload())
}
pub fn serialize(self) -> Vec<u8> {
serialize(self.raw_module).expect("serializing into a vec should succeed; qed")
}
pub fn into_inner(self) -> Module {
self.raw_module
}
}