cosmwasm_vm/
parsed_wasm.rs1use std::{fmt, mem, str};
2
3use wasmer::wasmparser::{
4 BinaryReaderError, CompositeInnerType, Export, FuncToValidate, FunctionBody, Import,
5 MemoryType, Parser, Payload, TableType, ValidPayload, Validator, ValidatorResources,
6 WasmFeatures,
7};
8
9use crate::{VmError, VmResult};
10
11#[derive(Default)]
22pub struct OpaqueDebug<T>(pub T);
23
24impl<T> fmt::Debug for OpaqueDebug<T> {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.debug_struct(std::any::type_name::<T>())
27 .finish_non_exhaustive()
28 }
29}
30
31#[derive(Debug)]
32pub enum FunctionValidator<'a> {
33 Pending(OpaqueDebug<Vec<(FuncToValidate<ValidatorResources>, FunctionBody<'a>)>>),
34 Success,
35 Error(BinaryReaderError),
36}
37
38impl<'a> FunctionValidator<'a> {
39 fn push(&mut self, item: (FuncToValidate<ValidatorResources>, FunctionBody<'a>)) {
40 let Self::Pending(OpaqueDebug(ref mut funcs)) = self else {
41 panic!("attempted to push function into non-pending validator");
42 };
43
44 funcs.push(item);
45 }
46}
47
48#[derive(Debug)]
51pub struct ParsedWasm<'a> {
52 pub version: u16,
53 pub exports: Vec<Export<'a>>,
54 pub imports: Vec<Import<'a>>,
55 pub tables: Vec<TableType>,
56 pub memories: Vec<MemoryType>,
57 pub function_count: usize,
58 pub type_count: u32,
59 pub type_params: Vec<usize>,
62 pub max_func_params: usize,
64 pub max_func_results: usize,
66 pub total_func_params: usize,
68 pub func_validator: FunctionValidator<'a>,
70 pub contract_migrate_version: Option<u64>,
72}
73
74impl<'a> ParsedWasm<'a> {
75 pub fn parse(wasm: &'a [u8]) -> VmResult<Self> {
76 let features = WasmFeatures::MUTABLE_GLOBAL
77 | WasmFeatures::SATURATING_FLOAT_TO_INT
78 | WasmFeatures::SIGN_EXTENSION
79 | WasmFeatures::MULTI_VALUE
80 | WasmFeatures::FLOATS
81 | WasmFeatures::REFERENCE_TYPES;
82
83 let mut validator = Validator::new_with_features(features);
84
85 let mut this = Self {
86 version: 0,
87 exports: vec![],
88 imports: vec![],
89 tables: vec![],
90 memories: vec![],
91 function_count: 0,
92 type_count: 0,
93 type_params: Vec::new(),
94 max_func_params: 0,
95 max_func_results: 0,
96 total_func_params: 0,
97 func_validator: FunctionValidator::Pending(OpaqueDebug::default()),
98 contract_migrate_version: None,
99 };
100
101 for p in Parser::new(0).parse_all(wasm) {
102 let p = p?;
103 if let ValidPayload::Func(fv, body) = validator.payload(&p)? {
105 this.func_validator.push((fv, body));
107 this.function_count += 1;
108 }
109
110 match p {
111 Payload::TypeSection(t) => {
112 this.type_count = 0;
113 this.type_params = Vec::with_capacity(t.count() as usize);
115 for group in t.into_iter() {
116 let types = group?.into_types();
117 this.type_count += types.len() as u32;
119
120 for ty in types {
121 match ty.composite_type.inner {
122 CompositeInnerType::Func(ft) => {
123 this.type_params.push(ft.params().len());
124
125 this.max_func_params =
126 core::cmp::max(ft.params().len(), this.max_func_params);
127 this.max_func_results =
128 core::cmp::max(ft.results().len(), this.max_func_results);
129 }
130 CompositeInnerType::Array(_) | CompositeInnerType::Struct(_) => {
131 }
134 }
135 }
136 }
137 }
138 Payload::FunctionSection(section) => {
139 for a in section {
144 let type_index = a? as usize;
145 this.total_func_params +=
146 this.type_params.get(type_index).ok_or_else(|| {
147 VmError::static_validation_err(
149 "Wasm bytecode error: function uses unknown type index",
150 )
151 })?
152 }
153 }
154 Payload::Version { num, .. } => this.version = num,
155 Payload::ImportSection(i) => {
156 this.imports = i.into_iter().collect::<Result<Vec<_>, _>>()?;
157 }
158 Payload::TableSection(t) => {
159 this.tables = t
160 .into_iter()
161 .map(|r| r.map(|t| t.ty))
162 .collect::<Result<Vec<_>, _>>()?;
163 }
164 Payload::MemorySection(m) => {
165 this.memories = m.into_iter().collect::<Result<Vec<_>, _>>()?;
166 }
167 Payload::ExportSection(e) => {
168 this.exports = e.into_iter().collect::<Result<Vec<_>, _>>()?;
169 }
170 Payload::CustomSection(reader) if reader.name() == "cw_migrate_version" => {
171 let raw_version = str::from_utf8(reader.data())
173 .map_err(|err| VmError::static_validation_err(err.to_string()))?;
174
175 this.contract_migrate_version = Some(
176 raw_version
177 .parse::<u64>()
178 .map_err(|err| VmError::static_validation_err(err.to_string()))?,
179 );
180 }
181 _ => {} }
183 }
184
185 Ok(this)
186 }
187
188 pub fn validate_funcs(&mut self) -> VmResult<()> {
192 match self.func_validator {
193 FunctionValidator::Pending(OpaqueDebug(ref mut funcs)) => {
194 let result = (|| {
195 let mut allocations = <_>::default();
196 for (func, body) in mem::take(funcs) {
197 let mut validator = func.into_validator(allocations);
198 validator.validate(&body)?;
199 allocations = validator.into_allocations();
200 }
201 Ok(())
202 })();
203
204 self.func_validator = match result {
205 Ok(()) => FunctionValidator::Success,
206 Err(err) => FunctionValidator::Error(err),
207 };
208
209 self.validate_funcs()
210 }
211 FunctionValidator::Success => Ok(()),
212 FunctionValidator::Error(ref err) => Err(err.clone().into()),
213 }
214 }
215}
216
217#[cfg(test)]
218mod test {
219 use super::ParsedWasm;
220
221 #[test]
222 fn read_migrate_version() {
223 let wasm_data =
224 wat::parse_str(r#"( module ( @custom "cw_migrate_version" "42" ) )"#).unwrap();
225 let parsed = ParsedWasm::parse(&wasm_data).unwrap();
226
227 assert_eq!(parsed.contract_migrate_version, Some(42));
228 }
229
230 #[test]
231 fn read_migrate_version_fails() {
232 let wasm_data =
233 wat::parse_str(r#"( module ( @custom "cw_migrate_version" "not a number" ) )"#)
234 .unwrap();
235 assert!(ParsedWasm::parse(&wasm_data).is_err());
236 }
237
238 #[test]
239 fn parsed_wasm_counts_functions_correctly() {
240 let wasm = wat::parse_str(r#"(module)"#).unwrap();
241 let module = ParsedWasm::parse(&wasm).unwrap();
242 assert_eq!(module.function_count, 0);
243
244 let wasm = wat::parse_str(
245 r#"(module
246 (type (func))
247 (func (type 0) nop)
248 (func (type 0) nop)
249 (export "foo" (func 0))
250 (export "bar" (func 0))
251 )"#,
252 )
253 .unwrap();
254 let module = ParsedWasm::parse(&wasm).unwrap();
255 assert_eq!(module.function_count, 2);
256 }
257
258 #[test]
259 fn parsed_wasm_counts_func_io_correctly() {
260 let wasm = wat::parse_str(r#"(module)"#).unwrap();
261 let module = ParsedWasm::parse(&wasm).unwrap();
262 assert_eq!(module.max_func_params, 0);
263 assert_eq!(module.max_func_results, 0);
264
265 let wasm = wat::parse_str(
266 r#"(module
267 (type (func (param i32 i32 i32) (result i32)))
268 (type (func (param i32) (result i32 i32)))
269 (func (type 1) i32.const 42 i32.const 42)
270 (func (type 0) i32.const 42)
271 )"#,
272 )
273 .unwrap();
274 let module = ParsedWasm::parse(&wasm).unwrap();
275 assert_eq!(module.max_func_params, 3);
276 assert_eq!(module.max_func_results, 2);
277 }
278}