1use 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 #[clap(long = "verbose", short = 'v', action = clap::ArgAction::Count)]
20 verbose: u8,
21
22 #[clap(long = "color", default_value = "auto")]
28 pub color: ColorChoice,
29}
30
31impl GeneralOpts {
32 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#[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 #[clap(
77 long,
78 value_name = "lines|full",
79 conflicts_with = "generate_full_dwarf"
80 )]
81 generate_dwarf: Option<GenerateDwarf>,
82
83 #[clap(short, conflicts_with = "generate_dwarf")]
85 generate_full_dwarf: bool,
86}
87
88#[derive(clap::Parser)]
89pub struct InputArg {
90 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 #[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}