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
65fn parse_optionally_name_file(s: &str) -> (&str, &str) {
66    let mut parts = s.splitn(2, '=');
67    let name_or_path = parts.next().unwrap();
68    match parts.next() {
69        Some(path) => (name_or_path, path),
70        None => {
71            let name = Path::new(name_or_path)
72                .file_name()
73                .unwrap()
74                .to_str()
75                .unwrap();
76            let name = match name.find('.') {
77                Some(i) => &name[..i],
78                None => name,
79            };
80            (name, name_or_path)
81        }
82    }
83}
84
85fn parse_adapter(s: &str) -> Result<(String, Vec<u8>)> {
86    let (name, path) = parse_optionally_name_file(s);
87    let wasm = wat::parse_file(path)?;
88    Ok((name.to_string(), wasm))
89}
90
91#[derive(clap::Parser)]
92pub struct AdaptersArg {
93    /// The path to an adapter module to satisfy imports not otherwise bound to
94    /// WIT interfaces.
95    ///
96    /// An adapter module can be used to translate the `wasi_snapshot_preview1`
97    /// ABI, for example, to one that uses the component model. The first
98    /// `[NAME=]` specified in the argument is inferred from the name of file
99    /// specified by `MODULE` if not present and is the name of the import
100    /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`).
101    ///
102    /// The second part of this argument is the path to the adapter module.
103    #[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)]
104    pub adapters: Vec<(String, Vec<u8>)>,
105}
106
107#[derive(clap::Parser)]
108pub struct GenerateDwarfArg {
109    /// Optionally generate DWARF debugging information from WebAssembly text
110    /// files.
111    ///
112    /// When the input to this command is a WebAssembly text file, such as
113    /// `*.wat`, then this option will instruct the text parser to insert DWARF
114    /// debugging information to map binary locations back to the original
115    /// source locations in the input `*.wat` file. This option has no effect if
116    /// the `INPUT` argument is already a WebAssembly binary or if the text
117    /// format uses `(module binary ...)`.
118    #[clap(
119        long,
120        value_name = "lines|full",
121        conflicts_with = "generate_full_dwarf"
122    )]
123    generate_dwarf: Option<GenerateDwarf>,
124
125    /// Shorthand for `--generate-dwarf full`
126    #[clap(short, conflicts_with = "generate_dwarf")]
127    generate_full_dwarf: bool,
128}
129
130#[derive(clap::Parser)]
131pub struct InputArg {
132    /// Input file to process.
133    ///
134    /// If not provided or if this is `-` then stdin is read entirely and
135    /// processed. Note that for most subcommands this input can either be a
136    /// binary `*.wasm` file or a textual format `*.wat` file.
137    input: Option<PathBuf>,
138}
139
140#[derive(Copy, Clone)]
141enum GenerateDwarf {
142    Lines,
143    Full,
144}
145
146impl FromStr for GenerateDwarf {
147    type Err = anyhow::Error;
148
149    fn from_str(s: &str) -> Result<GenerateDwarf> {
150        match s {
151            "lines" => Ok(GenerateDwarf::Lines),
152            "full" => Ok(GenerateDwarf::Full),
153            other => bail!("unknown `--generate-dwarf` setting: {other}"),
154        }
155    }
156}
157
158impl InputArg {
159    pub fn get_binary_wasm(
160        &self,
161        generate_dwarf_optional: Option<&GenerateDwarfArg>,
162    ) -> Result<Vec<u8>> {
163        let mut parser = wat::Parser::new();
164        match generate_dwarf_optional {
165            None => {}
166            Some(generate_dwarf) => match (
167                generate_dwarf.generate_full_dwarf,
168                generate_dwarf.generate_dwarf,
169            ) {
170                (false, Some(GenerateDwarf::Lines)) => {
171                    parser.generate_dwarf(wat::GenerateDwarf::Lines);
172                }
173                (true, _) | (false, Some(GenerateDwarf::Full)) => {
174                    parser.generate_dwarf(wat::GenerateDwarf::Full);
175                }
176                (false, None) => {}
177            },
178        }
179        if let Some(path) = &self.input {
180            if path != Path::new("-") {
181                let bytes = parser.parse_file(path)?;
182                return Ok(bytes);
183            }
184        }
185        let mut stdin = Vec::new();
186        std::io::stdin()
187            .read_to_end(&mut stdin)
188            .context("failed to read <stdin>")?;
189        let bytes = parser.parse_bytes(Some("<stdin>".as_ref()), &stdin)?;
190        Ok(bytes.into_owned())
191    }
192}
193
194#[derive(clap::Parser)]
195pub struct OutputArg {
196    /// Where to place output.
197    ///
198    /// Required when printing WebAssembly binary output.
199    ///
200    /// If not provided, then stdout is used.
201    #[clap(short, long)]
202    output: Option<PathBuf>,
203}
204
205pub enum Output<'a> {
206    #[cfg(feature = "component")]
207    Wit {
208        wit: &'a wit_component::DecodedWasm,
209        printer: wit_component::WitPrinter,
210    },
211    Wasm(&'a [u8]),
212    Wat {
213        wasm: &'a [u8],
214        config: wasmprinter::Config,
215    },
216    Json(&'a str),
217}
218
219impl InputOutput {
220    pub fn parse_input_wasm(&self, generate_dwarf: Option<&GenerateDwarfArg>) -> Result<Vec<u8>> {
221        let ret = self.get_input_wasm(generate_dwarf)?;
222        parse_binary_wasm(wasmparser::Parser::new(0), &ret)?;
223        Ok(ret)
224    }
225
226    pub fn get_input_wasm(&self, generate_dwarf: Option<&GenerateDwarfArg>) -> Result<Vec<u8>> {
227        self.input.get_binary_wasm(generate_dwarf)
228    }
229
230    pub fn output_wasm(&self, wasm: &[u8], wat: bool) -> Result<()> {
231        if wat {
232            self.output(Output::Wat {
233                wasm,
234                config: Default::default(),
235            })
236        } else {
237            self.output(Output::Wasm(wasm))
238        }
239    }
240
241    pub fn output(&self, bytes: Output<'_>) -> Result<()> {
242        self.output.output(&self.general, bytes)
243    }
244
245    pub fn output_writer(&self) -> Result<Box<dyn WriteColor>> {
246        self.output.output_writer(self.general.color)
247    }
248
249    pub fn output_path(&self) -> Option<&Path> {
250        self.output.output.as_deref()
251    }
252
253    pub fn input_path(&self) -> Option<&Path> {
254        self.input.input.as_deref()
255    }
256
257    pub fn general_opts(&self) -> &GeneralOpts {
258        &self.general
259    }
260}
261
262impl OutputArg {
263    pub fn output_wasm(&self, general: &GeneralOpts, wasm: &[u8], wat: bool) -> Result<()> {
264        if wat {
265            self.output(
266                general,
267                Output::Wat {
268                    wasm,
269                    config: Default::default(),
270                },
271            )
272        } else {
273            self.output(general, Output::Wasm(wasm))
274        }
275    }
276
277    pub fn output(&self, general: &GeneralOpts, output: Output<'_>) -> Result<()> {
278        match output {
279            Output::Wat { wasm, config } => {
280                let mut writer = self.output_writer(general.color)?;
281                config.print(wasm, &mut wasmprinter::PrintTermcolor(&mut writer))
282            }
283            Output::Wasm(bytes) => {
284                match &self.output {
285                    Some(path) => {
286                        std::fs::write(path, bytes)
287                            .context(format!("failed to write `{}`", path.display()))?;
288                    }
289                    None => {
290                        let mut stdout = std::io::stdout();
291                        if stdout.is_terminal() {
292                            bail!(
293                                "cannot print binary wasm output to a terminal, pass the `-t` flag to print the text format"
294                            );
295                        }
296                        stdout
297                            .write_all(bytes)
298                            .context("failed to write to stdout")?;
299                    }
300                }
301                Ok(())
302            }
303            Output::Json(s) => self.output_str(s),
304            #[cfg(feature = "component")]
305            Output::Wit { wit, mut printer } => {
306                let resolve = wit.resolve();
307                let ids = resolve
308                    .packages
309                    .iter()
310                    .map(|(id, _)| id)
311                    .filter(|id| *id != wit.package())
312                    .collect::<Vec<_>>();
313                printer.print(resolve, wit.package(), &ids)?;
314                let output = printer.output.to_string();
315                self.output_str(&output)
316            }
317        }
318    }
319
320    fn output_str(&self, output: &str) -> Result<()> {
321        match &self.output {
322            Some(path) => {
323                std::fs::write(path, output)
324                    .context(format!("failed to write `{}`", path.display()))?;
325            }
326            None => std::io::stdout()
327                .write_all(output.as_bytes())
328                .context("failed to write to stdout")?,
329        }
330        Ok(())
331    }
332
333    pub fn output_path(&self) -> Option<&Path> {
334        self.output.as_deref()
335    }
336
337    pub fn output_writer(&self, color: ColorChoice) -> Result<Box<dyn WriteColor>> {
338        match &self.output {
339            Some(output) => {
340                let writer = BufWriter::new(File::create(&output)?);
341                if color == ColorChoice::AlwaysAnsi {
342                    Ok(Box::new(Ansi::new(writer)))
343                } else {
344                    Ok(Box::new(NoColor::new(writer)))
345                }
346            }
347            None => {
348                let stdout = std::io::stdout();
349                if color == ColorChoice::Auto && !stdout.is_terminal() {
350                    Ok(Box::new(StandardStream::stdout(ColorChoice::Never)))
351                } else {
352                    Ok(Box::new(StandardStream::stdout(color)))
353                }
354            }
355        }
356    }
357}
358
359pub fn parse_binary_wasm(parser: wasmparser::Parser, bytes: &[u8]) -> Result<()> {
360    for payload in parser.parse_all(&bytes) {
361        match payload? {
362            wasmparser::Payload::TypeSection(s) => parse_section(s)?,
363            wasmparser::Payload::ImportSection(s) => parse_section(s)?,
364            wasmparser::Payload::FunctionSection(s) => parse_section(s)?,
365            wasmparser::Payload::TableSection(s) => parse_section(s)?,
366            wasmparser::Payload::MemorySection(s) => parse_section(s)?,
367            wasmparser::Payload::TagSection(s) => parse_section(s)?,
368            wasmparser::Payload::GlobalSection(s) => parse_section(s)?,
369            wasmparser::Payload::ExportSection(s) => parse_section(s)?,
370            wasmparser::Payload::ElementSection(s) => parse_section(s)?,
371            wasmparser::Payload::DataSection(s) => parse_section(s)?,
372            wasmparser::Payload::CodeSectionEntry(body) => {
373                let mut locals = body.get_locals_reader()?.into_iter();
374                for item in locals.by_ref() {
375                    let _ = item?;
376                }
377                let mut ops = locals.into_operators_reader();
378                while !ops.eof() {
379                    ops.read()?;
380                }
381                ops.finish()?;
382            }
383
384            wasmparser::Payload::InstanceSection(s) => parse_section(s)?,
385            wasmparser::Payload::CoreTypeSection(s) => parse_section(s)?,
386            wasmparser::Payload::ComponentInstanceSection(s) => parse_section(s)?,
387            wasmparser::Payload::ComponentAliasSection(s) => parse_section(s)?,
388            wasmparser::Payload::ComponentTypeSection(s) => parse_section(s)?,
389            wasmparser::Payload::ComponentCanonicalSection(s) => parse_section(s)?,
390            wasmparser::Payload::ComponentImportSection(s) => parse_section(s)?,
391            wasmparser::Payload::ComponentExportSection(s) => parse_section(s)?,
392
393            wasmparser::Payload::UnknownSection { id, .. } => {
394                bail!("malformed section id: {}", id)
395            }
396
397            _ => (),
398        }
399    }
400    return Ok(());
401
402    fn parse_section<'a, T>(s: wasmparser::SectionLimited<'a, T>) -> Result<()>
403    where
404        T: wasmparser::FromReader<'a>,
405    {
406        for item in s {
407            let _ = item?;
408        }
409        Ok(())
410    }
411}