ic_wasm/
info.rs

1use std::fmt;
2use std::io::Write;
3use walrus::Module;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use crate::{utils::*, Error};
9
10/// External information about a Wasm, such as API methods.
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub struct WasmInfo {
13    language: LanguageSpecificInfo,
14    number_of_types: usize,
15    number_of_globals: usize,
16    number_of_data_sections: usize,
17    size_of_data_sections: usize,
18    number_of_functions: usize,
19    number_of_callbacks: usize,
20    start_function: Option<String>,
21    exported_methods: Vec<ExportedMethodInfo>,
22    imported_ic0_system_api: Vec<String>,
23    custom_sections: Vec<CustomSectionInfo>,
24}
25
26/// External information that is specific to one language
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28pub enum LanguageSpecificInfo {
29    Motoko {
30        embedded_wasm: Vec<(String, WasmInfo)>,
31    },
32    Unknown,
33}
34
35/// Information about an exported method.
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37#[derive(Debug)]
38pub struct ExportedMethodInfo {
39    pub name: String,
40    pub internal_name: String,
41}
42
43/// Statistics about a custom section.
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45pub struct CustomSectionInfo {
46    name: String,
47    size: usize,
48}
49
50impl From<&Module> for WasmInfo {
51    fn from(m: &Module) -> WasmInfo {
52        let (number_of_data_sections, size_of_data_sections) = m
53            .data
54            .iter()
55            .fold((0, 0), |(count, size), d| (count + 1, size + d.value.len()));
56
57        WasmInfo {
58            language: LanguageSpecificInfo::from(m),
59            number_of_types: m.types.iter().count(),
60            number_of_globals: m.globals.iter().count(),
61            number_of_data_sections,
62            size_of_data_sections,
63            number_of_functions: m.funcs.iter().count(),
64            number_of_callbacks: m.elements.iter().count(),
65            start_function: m.start.map(|id| get_func_name(m, id)),
66            exported_methods: get_exported_methods(m),
67            imported_ic0_system_api: m
68                .imports
69                .iter()
70                .filter(|i| i.module == "ic0")
71                .map(|i| i.name.clone())
72                .collect(),
73            custom_sections: m
74                .customs
75                .iter()
76                .map(|(_, s)| CustomSectionInfo {
77                    name: s.name().to_string(),
78                    size: s.data(&Default::default()).len(),
79                })
80                .collect(),
81        }
82    }
83}
84
85impl From<&Module> for LanguageSpecificInfo {
86    fn from(m: &Module) -> LanguageSpecificInfo {
87        if is_motoko_canister(m) {
88            let mut embedded_wasm = Vec::new();
89            for (data_id, embedded_module) in get_motoko_wasm_data_sections(m) {
90                embedded_wasm.push((format!("{data_id:?}"), WasmInfo::from(&embedded_module)));
91            }
92            return LanguageSpecificInfo::Motoko { embedded_wasm };
93        }
94        LanguageSpecificInfo::Unknown
95    }
96}
97
98impl fmt::Display for WasmInfo {
99    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100        write!(f, "{}", self.language)?;
101        writeln!(f, "Number of types: {}", self.number_of_types)?;
102        writeln!(f, "Number of globals: {}", self.number_of_globals)?;
103        writeln!(f)?;
104        writeln!(
105            f,
106            "Number of data sections: {}",
107            self.number_of_data_sections
108        )?;
109        writeln!(
110            f,
111            "Size of data sections: {} bytes",
112            self.size_of_data_sections
113        )?;
114        writeln!(f)?;
115        writeln!(f, "Number of functions: {}", self.number_of_functions)?;
116        writeln!(f, "Number of callbacks: {}", self.number_of_callbacks)?;
117        writeln!(f, "Start function: {:?}", self.start_function)?;
118        let exports: Vec<_> = self
119            .exported_methods
120            .iter()
121            .map(
122                |ExportedMethodInfo {
123                     name,
124                     internal_name,
125                 }| {
126                    if name == internal_name {
127                        internal_name.clone()
128                    } else {
129                        format!("{name} ({internal_name})")
130                    }
131                },
132            )
133            .collect();
134        writeln!(f, "Exported methods: {exports:#?}")?;
135        writeln!(f)?;
136        writeln!(
137            f,
138            "Imported IC0 System API: {:#?}",
139            self.imported_ic0_system_api
140        )?;
141        writeln!(f)?;
142        let customs: Vec<_> = self
143            .custom_sections
144            .iter()
145            .map(|section_info| format!("{} ({} bytes)", section_info.name, section_info.size))
146            .collect();
147        writeln!(f, "Custom sections with size: {customs:#?}")?;
148        Ok(())
149    }
150}
151
152impl fmt::Display for LanguageSpecificInfo {
153    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154        match self {
155            LanguageSpecificInfo::Motoko { embedded_wasm } => {
156                writeln!(f, "This is a Motoko canister")?;
157                for (_, wasm_info) in embedded_wasm {
158                    writeln!(f, "--- Start decoding an embedded Wasm ---")?;
159                    write!(f, "{wasm_info}")?;
160                    writeln!(f, "--- End of decoding ---")?;
161                }
162                writeln!(f)
163            }
164            LanguageSpecificInfo::Unknown => Ok(()),
165        }
166    }
167}
168
169/// Print general summary of the Wasm module
170pub fn info(m: &Module, output: &mut dyn Write) -> Result<(), Error> {
171    write!(output, "{}", WasmInfo::from(m))?;
172    Ok(())
173}