Skip to main content

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