1use std::{cmp::Ordering, collections::HashMap, fmt::Debug, ops::Range};
6
7use anyhow::{Context, Result, anyhow, bail, ensure};
8pub use symbols::{StaticModuleInfo, SymbolMap};
9use wasmparser::{ElementItems, ElementKind, TypeRef};
10
11use crate::{
12 index::{
13 AnySymbolId, DefinedFuncId, ElementId, ExportId, FuncTypeId, IdMap, ImportId, InputFuncId,
14 InputGlobalId, TableId,
15 },
16 read,
17};
18mod debug;
19pub mod dep_graph;
20pub mod split_point;
21pub mod symbols;
22#[cfg(test)]
23mod testing;
24
25#[derive(Debug, PartialEq, Eq, Clone)]
26pub struct ImportInfo {
27 pub imported_funcs: Vec<ImportId>,
29 pub imported_func_map: IdMap<ImportId, InputFuncId>,
30
31 pub imported_globals: Vec<ImportId>,
32 pub imported_global_map: IdMap<ImportId, InputGlobalId>,
33}
34
35pub struct ModuleInfo<'src> {
39 pub import_info: ImportInfo,
40
41 pub wasm: read::InputModule<'src>,
42 pub export_map: HashMap<(isize, AnySymbolId), (ExportId, &'src str)>,
43 pub symbols: SymbolMap<'src>,
44
45 pub indirect_function_table_id: (TableId, ElementId),
46 pub indirect_function_list: Vec<InputFuncId>,
47}
48
49impl<'src> ModuleInfo<'src> {
50 pub fn from_wasm_bytes(wasm_bytes: &'src [u8]) -> Result<Self> {
51 let module = read::InputModule::parse(&wasm_bytes)?;
52 Self::from_raw_module(module)
53 }
54 pub fn from_raw_module(module: read::InputModule<'src>) -> Result<Self> {
55 let mut imported_funcs: Vec<ImportId> = Vec::new();
57 let mut imported_globals: Vec<ImportId> = Vec::new();
58
59 for (import_id, import) in module.imports.iter() {
60 match import.ty {
61 TypeRef::Global(_) => {
62 imported_globals.push(import_id);
63 continue;
64 }
65 TypeRef::Func(_) => {
66 imported_funcs.push(import_id);
67 }
68 _ => {}
69 }
70 }
71 let imported_func_map = imported_funcs
72 .iter()
73 .enumerate()
74 .map(|(func_id, &import_id)| (import_id, InputFuncId::from_index(func_id)))
75 .collect();
76 let imported_global_map = imported_globals
77 .iter()
78 .enumerate()
79 .map(|(global_id, &import_id)| (import_id, InputGlobalId::from_index(global_id)))
80 .collect();
81
82 let import_funcs_info = ImportInfo {
83 imported_funcs,
84 imported_func_map,
85 imported_globals,
86 imported_global_map,
87 };
88 let export_map = module
89 .exports
90 .iter()
91 .map(|(i, export)| {
92 (
93 (export.kind as isize, export.index as AnySymbolId),
94 (i, export.name),
95 )
96 })
97 .collect();
98
99 let (_table_name, table_id) = module
100 .tables
101 .iter()
102 .filter_map(|(id, _)| module.names.tables.get(id).map(|name| (*name, id)))
103 .find(|(name, _)| *name == "__indirect_function_table")
104 .unwrap_or_else(|| {
105 assert!(
106 module.tables.len() == 1,
107 "No named __indirect_function_table was found, and there is not one table in the module."
108 );
109 (
110 "__indirect_function_table",
111 module.tables.iter().next().unwrap().0,
112 )
113 });
114
115 let mut indirect_element = None;
116 for (id, element) in module.elements.iter() {
117 let ElementKind::Active {
118 table_index,
119 offset_expr,
120 } = &element.kind
121 else {
122 continue;
123 };
124
125 if !table_index.is_none() && table_index.unwrap() == table_id.as_raw_index() as u32
127 {
128 continue;
129 }
130
131 let offset = Self::read_const_expr(offset_expr)
132 .with_context(|| format!("Failed to read offset expression for element {id:?}"))?;
133
134 ensure!(
135 offset == 1,
136 "Element segment {id:?} should be inited with 1 offset, but got {offset}, which is not supported"
137 );
138
139 let ElementItems::Functions(functions) = &element.items else {
140 bail!("Only function elements are supported, but got constant instead");
141 };
142
143 let mut function_list = Vec::with_capacity(functions.count() as usize);
144 for function_id in functions.clone().into_iter() {
145 let raw_function_id = function_id
146 .with_context(|| format!("Failed to read function ID from element {id:?}"))?;
147 function_list.push(InputFuncId::from_index(raw_function_id));
148 }
149 indirect_element = Some((id, function_list));
150 break;
151 }
152 let (indirect_element_id, indirect_function_list) = indirect_element
153 .ok_or_else(|| anyhow!("No element segment with __indirect_function_table found"))?;
154
155 let symbols_map = symbols::SymbolMap::new(&module, import_funcs_info.imported_funcs.len())?;
156
157 Ok(ModuleInfo {
158 import_info: import_funcs_info,
159 symbols: symbols_map,
160 wasm: module,
161 export_map,
162 indirect_function_list,
163 indirect_function_table_id: (table_id, indirect_element_id),
164 })
165 }
166
167 pub(crate) fn read_const_expr(offset_expr: &wasmparser::ConstExpr<'_>) -> Result<i32> {
168 let mut reader = offset_expr.get_operators_reader();
169
170 let val = match reader.read()? {
171 wasmparser::Operator::I32Const { value } => Ok(value),
172 op => bail!("Expected only I32.const operator, found: {:?}", op),
173 };
174 match reader.read()? {
175 wasmparser::Operator::End => {}
176 op => bail!("Expected End after I32.const: {:?}", op),
177 }
178 val
179 }
180 pub fn function_id_iter<'any>(
181 &'any self,
182 ) -> impl Iterator<Item = InputFuncId> + use<'any, 'src> {
183 (0..self.import_info.imported_funcs.len())
184 .map(InputFuncId::from_index)
185 .chain(
186 self.wasm
187 .code
188 .section_payload
189 .defined_funcs
190 .iter()
191 .enumerate()
192 .map(|(index, _)| {
193 InputFuncId::from_index(index + self.import_info.imported_funcs.len())
194 }),
195 )
196 }
197
198 pub fn is_imported_function(&self, func_id: InputFuncId) -> bool {
199 func_id.as_raw_index() < self.import_info.imported_funcs.len()
200 }
201
202 pub fn as_defined_function_id(&self, func_id: InputFuncId) -> Option<DefinedFuncId> {
203 if self.is_imported_function(func_id) {
204 None
205 } else {
206 Some(DefinedFuncId::from_index(
207 func_id
208 .as_raw_index()
209 .checked_sub(self.import_info.imported_funcs.len())
210 .expect("Function ID is out of bounds") as u32,
211 ))
212 }
213 }
214
215 pub fn get_function_type_id(&self, func_id: InputFuncId) -> FuncTypeId {
216 let Some(defined_index) = self.as_defined_function_id(func_id) else {
217 let import_id = self.import_info.imported_funcs[func_id.as_raw_index()];
219 let TypeRef::Func(ty) = self.wasm.imports[import_id].ty else {
220 panic!("Expected function type")
221 };
222 return FuncTypeId::from_index(ty);
223 };
224 self.wasm.defined_func_type_id(defined_index)
226 }
227
228 pub fn get_function_import_id(&self, func_id: InputFuncId) -> Option<ImportId> {
229 self.import_info
230 .imported_funcs
231 .get(func_id.as_raw_index())
232 .copied()
233 }
234 pub fn get_global_import_id(&self, global_id: InputGlobalId) -> Option<ImportId> {
235 self.import_info
236 .imported_globals
237 .get(global_id.as_raw_index())
238 .copied()
239 }
240
241 pub fn find_function_id_by_name(&self, name: &str) -> Option<InputFuncId> {
242 let func = self.wasm.names.functions.iter().find(|f| *f.1 == name)?;
243 Some(func.0)
244 }
245
246 pub fn find_global_id_by_name(&self, name: &str) -> Option<InputGlobalId> {
247 let global = self.wasm.names.globals.iter().find(|f| *f.1 == name)?;
248 Some(global.0)
249 }
250
251 pub fn find_function_id_containing_range(&self, range: Range<usize>) -> Result<InputFuncId> {
252 let func_index = Self::find_by_range(
253 self.wasm.code.section_payload.defined_funcs.as_slice(),
254 &range,
255 |defined_func| defined_func.body.range(),
256 )
257 .with_context(|| format!("No match for function relocation range {range:?}"))?;
258 Ok(InputFuncId::from_index(
259 func_index + self.import_info.imported_funcs.len(),
260 ))
261 }
262
263 fn find_by_range<T: Debug, U: Debug + Ord, F: Fn(&T) -> Range<U>>(
264 items: &[T],
265 range: &Range<U>,
266 get_range: F,
267 ) -> anyhow::Result<usize> {
268 let index = items
269 .binary_search_by(|item| {
270 let item_range = get_range(item);
271 if item_range.end <= range.start {
272 Ordering::Less
273 } else if item_range.start <= range.start {
274 Ordering::Equal
275 } else {
276 Ordering::Greater
277 }
278 })
279 .or_else(|index| {
280 bail!(
281 "Prev range is: {:?}, next range is: {:?}",
282 index
283 .checked_sub(1)
284 .and_then(|i| items.get(i).map(|item| (item, get_range(item)))),
285 items.get(index).map(|item| (item, get_range(item)))
286 )
287 })?;
288 if range.end > get_range(&items[index]).end {
289 bail!(
290 "Item {:?} has incompatible range {:?}",
291 items[index],
292 get_range(&items[index])
293 )
294 }
295 Ok(index)
296 }
297}
298
299impl<'src> Debug for ModuleInfo<'src> {
300 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301 f.debug_struct("ModuleInfo")
302 .field("import_funcs_info", &self.import_info)
303 .finish()
304 }
305}