wasm_tools/
lib.rs

1//! Shared input/output routines amongst most `wasm-tools` subcommands
2
3use anyhow::{Context, Result, bail};
4use std::fs::File;
5use std::io::IsTerminal;
6use std::io::{BufWriter, Read, Write};
7use std::path::{Path, PathBuf};
8use std::str::FromStr;
9use termcolor::{Ansi, ColorChoice, NoColor, StandardStream, WriteColor};
10
11#[cfg(any(feature = "addr2line", feature = "validate"))]
12pub mod addr2line;
13
14#[derive(clap::Parser)]
15pub struct GeneralOpts {
16    /// Use verbose output (-v info, -vv debug, -vvv trace).
17    #[clap(long = "verbose", short = 'v', action = clap::ArgAction::Count)]
18    verbose: u8,
19
20    /// Configuration over whether terminal colors are used in output.
21    ///
22    /// Supports one of `auto|never|always|always-ansi`. The default is to
23    /// detect what to do based on the terminal environment, for example by
24    /// using `isatty`.
25    #[clap(long = "color", default_value = "auto")]
26    pub color: ColorChoice,
27}
28
29impl GeneralOpts {
30    /// Initializes the logger based on the verbosity level.
31    pub fn init_logger(&self) {
32        let default = match self.verbose {
33            0 => "warn",
34            1 => "info",
35            2 => "debug",
36            _ => "trace",
37        };
38
39        env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default))
40            .format_target(false)
41            .init();
42    }
43}
44
45// This is intended to be included in a struct as:
46//
47//      #[clap(flatten)]
48//      io: wasm_tools::InputOutput,
49//
50// and then the methods are used to read the arguments,
51#[derive(clap::Parser)]
52pub struct InputOutput {
53    #[clap(flatten)]
54    input: InputArg,
55
56    #[clap(flatten)]
57    output: OutputArg,
58
59    #[clap(flatten)]
60    general: GeneralOpts,
61}
62
63#[derive(clap::Parser)]
64pub struct InputArg {
65    /// Input file to process.
66    ///
67    /// If not provided or if this is `-` then stdin is read entirely and
68    /// processed. Note that for most subcommands this input can either be a
69    /// binary `*.wasm` file or a textual format `*.wat` file.
70    input: Option<PathBuf>,
71
72    /// Optionally generate DWARF debugging information from WebAssembly text
73    /// files.
74    ///
75    /// When the input to this command is a WebAssembly text file, such as
76    /// `*.wat`, then this option will instruct the text parser to insert DWARF
77    /// debugging information to map binary locations back to the original
78    /// source locations in the input `*.wat` file. This option has no effect if
79    /// the `INPUT` argument is already a WebAssembly binary or if the text
80    /// format uses `(module binary ...)`.
81    #[clap(
82        long,
83        value_name = "lines|full",
84        conflicts_with = "generate_full_dwarf"
85    )]
86    generate_dwarf: Option<GenerateDwarf>,
87
88    /// Shorthand for `--generate-dwarf full`
89    #[clap(short, conflicts_with = "generate_dwarf")]
90    generate_full_dwarf: bool,
91}
92
93#[derive(Copy, Clone)]
94enum GenerateDwarf {
95    Lines,
96    Full,
97}
98
99impl FromStr for GenerateDwarf {
100    type Err = anyhow::Error;
101
102    fn from_str(s: &str) -> Result<GenerateDwarf> {
103        match s {
104            "lines" => Ok(GenerateDwarf::Lines),
105            "full" => Ok(GenerateDwarf::Full),
106            other => bail!("unknown `--generate-dwarf` setting: {other}"),
107        }
108    }
109}
110
111impl InputArg {
112    pub fn get_binary_wasm(&self) -> Result<Vec<u8>> {
113        let mut parser = wat::Parser::new();
114        match (self.generate_full_dwarf, self.generate_dwarf) {
115            (false, Some(GenerateDwarf::Lines)) => {
116                parser.generate_dwarf(wat::GenerateDwarf::Lines);
117            }
118            (true, _) | (false, Some(GenerateDwarf::Full)) => {
119                parser.generate_dwarf(wat::GenerateDwarf::Full);
120            }
121            (false, None) => {}
122        }
123        if let Some(path) = &self.input {
124            if path != Path::new("-") {
125                let bytes = parser.parse_file(path)?;
126                return Ok(bytes);
127            }
128        }
129        let mut stdin = Vec::new();
130        std::io::stdin()
131            .read_to_end(&mut stdin)
132            .context("failed to read <stdin>")?;
133        let bytes = parser.parse_bytes(Some("<stdin>".as_ref()), &stdin)?;
134        Ok(bytes.into_owned())
135    }
136}
137
138#[derive(clap::Parser)]
139pub struct OutputArg {
140    /// Where to place output.
141    ///
142    /// If not provided then stdout is used.
143    #[clap(short, long)]
144    output: Option<PathBuf>,
145}
146
147pub enum Output<'a> {
148    #[cfg(feature = "component")]
149    Wit {
150        wit: &'a wit_component::DecodedWasm,
151        printer: wit_component::WitPrinter,
152    },
153    Wasm(&'a [u8]),
154    Wat {
155        wasm: &'a [u8],
156        config: wasmprinter::Config,
157    },
158    Json(&'a str),
159}
160
161impl InputOutput {
162    pub fn parse_input_wasm(&self) -> Result<Vec<u8>> {
163        let ret = self.get_input_wasm()?;
164        parse_binary_wasm(wasmparser::Parser::new(0), &ret)?;
165        Ok(ret)
166    }
167
168    pub fn get_input_wasm(&self) -> Result<Vec<u8>> {
169        self.input.get_binary_wasm()
170    }
171
172    pub fn output_wasm(&self, wasm: &[u8], wat: bool) -> Result<()> {
173        if wat {
174            self.output(Output::Wat {
175                wasm,
176                config: Default::default(),
177            })
178        } else {
179            self.output(Output::Wasm(wasm))
180        }
181    }
182
183    pub fn output(&self, bytes: Output<'_>) -> Result<()> {
184        self.output.output(&self.general, bytes)
185    }
186
187    pub fn output_writer(&self) -> Result<Box<dyn WriteColor>> {
188        self.output.output_writer(self.general.color)
189    }
190
191    pub fn output_path(&self) -> Option<&Path> {
192        self.output.output.as_deref()
193    }
194
195    pub fn input_path(&self) -> Option<&Path> {
196        self.input.input.as_deref()
197    }
198
199    pub fn general_opts(&self) -> &GeneralOpts {
200        &self.general
201    }
202}
203
204impl OutputArg {
205    pub fn output_wasm(&self, general: &GeneralOpts, wasm: &[u8], wat: bool) -> Result<()> {
206        if wat {
207            self.output(
208                general,
209                Output::Wat {
210                    wasm,
211                    config: Default::default(),
212                },
213            )
214        } else {
215            self.output(general, Output::Wasm(wasm))
216        }
217    }
218
219    pub fn output(&self, general: &GeneralOpts, output: Output<'_>) -> Result<()> {
220        match output {
221            Output::Wat { wasm, config } => {
222                let mut writer = self.output_writer(general.color)?;
223                config.print(wasm, &mut wasmprinter::PrintTermcolor(&mut writer))
224            }
225            Output::Wasm(bytes) => {
226                match &self.output {
227                    Some(path) => {
228                        std::fs::write(path, bytes)
229                            .context(format!("failed to write `{}`", path.display()))?;
230                    }
231                    None => {
232                        let mut stdout = std::io::stdout();
233                        if stdout.is_terminal() {
234                            bail!(
235                                "cannot print binary wasm output to a terminal, pass the `-t` flag to print the text format"
236                            );
237                        }
238                        stdout
239                            .write_all(bytes)
240                            .context("failed to write to stdout")?;
241                    }
242                }
243                Ok(())
244            }
245            Output::Json(s) => self.output_str(s),
246            #[cfg(feature = "component")]
247            Output::Wit { wit, mut printer } => {
248                let resolve = wit.resolve();
249                let ids = resolve
250                    .packages
251                    .iter()
252                    .map(|(id, _)| id)
253                    .filter(|id| *id != wit.package())
254                    .collect::<Vec<_>>();
255                printer.print(resolve, wit.package(), &ids)?;
256                let output = printer.output.to_string();
257                self.output_str(&output)
258            }
259        }
260    }
261
262    fn output_str(&self, output: &str) -> Result<()> {
263        match &self.output {
264            Some(path) => {
265                std::fs::write(path, output)
266                    .context(format!("failed to write `{}`", path.display()))?;
267            }
268            None => std::io::stdout()
269                .write_all(output.as_bytes())
270                .context("failed to write to stdout")?,
271        }
272        Ok(())
273    }
274
275    pub fn output_path(&self) -> Option<&Path> {
276        self.output.as_deref()
277    }
278
279    pub fn output_writer(&self, color: ColorChoice) -> Result<Box<dyn WriteColor>> {
280        match &self.output {
281            Some(output) => {
282                let writer = BufWriter::new(File::create(&output)?);
283                if color == ColorChoice::AlwaysAnsi {
284                    Ok(Box::new(Ansi::new(writer)))
285                } else {
286                    Ok(Box::new(NoColor::new(writer)))
287                }
288            }
289            None => {
290                let stdout = std::io::stdout();
291                if color == ColorChoice::Auto && !stdout.is_terminal() {
292                    Ok(Box::new(StandardStream::stdout(ColorChoice::Never)))
293                } else {
294                    Ok(Box::new(StandardStream::stdout(color)))
295                }
296            }
297        }
298    }
299}
300
301pub fn parse_binary_wasm(parser: wasmparser::Parser, bytes: &[u8]) -> Result<()> {
302    for payload in parser.parse_all(&bytes) {
303        match payload? {
304            wasmparser::Payload::TypeSection(s) => parse_section(s)?,
305            wasmparser::Payload::ImportSection(s) => parse_section(s)?,
306            wasmparser::Payload::FunctionSection(s) => parse_section(s)?,
307            wasmparser::Payload::TableSection(s) => parse_section(s)?,
308            wasmparser::Payload::MemorySection(s) => parse_section(s)?,
309            wasmparser::Payload::TagSection(s) => parse_section(s)?,
310            wasmparser::Payload::GlobalSection(s) => parse_section(s)?,
311            wasmparser::Payload::ExportSection(s) => parse_section(s)?,
312            wasmparser::Payload::ElementSection(s) => parse_section(s)?,
313            wasmparser::Payload::DataSection(s) => parse_section(s)?,
314            wasmparser::Payload::CodeSectionEntry(body) => {
315                let mut locals = body.get_locals_reader()?.into_iter();
316                for item in locals.by_ref() {
317                    let _ = item?;
318                }
319                let mut ops = locals.into_operators_reader();
320                while !ops.eof() {
321                    ops.read()?;
322                }
323                ops.finish()?;
324            }
325
326            wasmparser::Payload::InstanceSection(s) => parse_section(s)?,
327            wasmparser::Payload::CoreTypeSection(s) => parse_section(s)?,
328            wasmparser::Payload::ComponentInstanceSection(s) => parse_section(s)?,
329            wasmparser::Payload::ComponentAliasSection(s) => parse_section(s)?,
330            wasmparser::Payload::ComponentTypeSection(s) => parse_section(s)?,
331            wasmparser::Payload::ComponentCanonicalSection(s) => parse_section(s)?,
332            wasmparser::Payload::ComponentImportSection(s) => parse_section(s)?,
333            wasmparser::Payload::ComponentExportSection(s) => parse_section(s)?,
334
335            wasmparser::Payload::UnknownSection { id, .. } => {
336                bail!("malformed section id: {}", id)
337            }
338
339            _ => (),
340        }
341    }
342    return Ok(());
343
344    fn parse_section<'a, T>(s: wasmparser::SectionLimited<'a, T>) -> Result<()>
345    where
346        T: wasmparser::FromReader<'a>,
347    {
348        for item in s {
349            let _ = item?;
350        }
351        Ok(())
352    }
353}