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
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 #[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 #[clap(
119 long,
120 value_name = "lines|full",
121 conflicts_with = "generate_full_dwarf"
122 )]
123 generate_dwarf: Option<GenerateDwarf>,
124
125 #[clap(short, conflicts_with = "generate_dwarf")]
127 generate_full_dwarf: bool,
128}
129
130#[derive(clap::Parser)]
131pub struct InputArg {
132 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 #[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}