Skip to main content

wasm_bindgen_cli_support/
lib.rs

1use anyhow::{bail, Context, Error};
2use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
3use std::env;
4use std::fs;
5use std::mem;
6use std::path::{Path, PathBuf};
7use std::str;
8use walrus::Module;
9
10pub(crate) const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__";
11/// The module that JS String Builtins string constants are imported from, as
12/// named by the WebAssembly ESM integration and passed as
13/// `importedStringConstants` when wasm-bindgen compiles the module itself.
14pub(crate) const STRING_CONSTANTS_MODULE: &str = "wasm:js/string-constants";
15
16/// The fixed layout of the `externref` table and JS heap: slots below
17/// `INITIAL_HEAP_OFFSET` are the stack, followed by `INITIAL_HEAP_VALUES`.
18/// Must be kept in sync with `src/rt/mod.rs` of the `wasm-bindgen` crate.
19pub(crate) const INITIAL_HEAP_OFFSET: usize = 1024;
20pub(crate) const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
21
22mod decode;
23mod descriptor;
24mod descriptors;
25mod externref;
26mod interpreter;
27mod intrinsic;
28mod js;
29mod multivalue;
30mod suggest;
31mod transforms;
32pub mod wasm2es6js;
33mod wasm_conventions;
34mod wit;
35
36pub struct Bindgen {
37    input: Input,
38    out_name: Option<String>,
39    mode: OutputMode,
40    debug: bool,
41    typescript: bool,
42    omit_imports: bool,
43    demangle: bool,
44    keep_lld_exports: bool,
45    export_wasm: bool,
46    keep_debug: bool,
47    split_debug_info: bool,
48    debug_info_url: Option<String>,
49    remove_name_section: bool,
50    remove_producers_section: bool,
51    omit_default_module_path: bool,
52    ts_typed_array_buffers: bool,
53    emit_start: bool,
54    externref: bool,
55    multi_value: bool,
56    encode_into: EncodeInto,
57    split_linked_modules: bool,
58    generate_reset_state: bool,
59    force_enable_abort_handler: bool,
60    js_string_builtins: bool,
61}
62
63pub struct Output {
64    module: walrus::Module,
65    stem: String,
66    split_debug_info: bool,
67    debug_info_url: Option<String>,
68    generated: Generated,
69}
70
71struct Generated {
72    mode: OutputMode,
73    js: String,
74    ts: String,
75    start: Option<String>,
76    snippets: BTreeMap<String, Vec<String>>,
77    local_modules: HashMap<String, String>,
78    npm_dependencies: HashMap<String, (PathBuf, String)>,
79    typescript: bool,
80    /// For `OutputMode::Emscripten` only: the contents of a sidecar file
81    /// emcc loads via `--extern-pre-js`, containing ESM `import` statements
82    /// that must live at module top-level. Empty for other modes.
83    emscripten_extern_pre_js: String,
84}
85
86#[derive(Clone)]
87enum OutputMode {
88    Bundler { browser_only: bool },
89    Web,
90    NoModules { global: String },
91    Node { module: bool },
92    Deno,
93    Module,
94    Emscripten,
95}
96
97enum Input {
98    Path(PathBuf),
99    Module(Module, String),
100    Bytes(Vec<u8>, String),
101    None,
102}
103
104#[derive(Debug, Clone, Copy)]
105pub enum EncodeInto {
106    Test,
107    Always,
108    Never,
109}
110
111impl Bindgen {
112    pub fn new() -> Bindgen {
113        let externref =
114            env::var("WASM_BINDGEN_ANYREF").is_ok() || env::var("WASM_BINDGEN_EXTERNREF").is_ok();
115        let multi_value = env::var("WASM_BINDGEN_MULTI_VALUE").is_ok();
116        Bindgen {
117            input: Input::None,
118            out_name: None,
119            mode: OutputMode::Bundler {
120                browser_only: false,
121            },
122            debug: false,
123            typescript: false,
124            omit_imports: false,
125            demangle: true,
126            keep_lld_exports: false,
127            export_wasm: false,
128            keep_debug: false,
129            split_debug_info: false,
130            debug_info_url: None,
131            remove_name_section: false,
132            remove_producers_section: false,
133            emit_start: true,
134            externref,
135            multi_value,
136            encode_into: EncodeInto::Test,
137            omit_default_module_path: true,
138            ts_typed_array_buffers: false,
139            split_linked_modules: false,
140            generate_reset_state: false,
141            force_enable_abort_handler: false,
142            js_string_builtins: false,
143        }
144    }
145
146    pub fn input_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Bindgen {
147        self.input = Input::Path(path.as_ref().to_path_buf());
148        self
149    }
150
151    pub fn out_name(&mut self, name: &str) -> &mut Bindgen {
152        self.out_name = Some(name.to_string());
153        self
154    }
155
156    #[deprecated = "automatically detected via `-Ctarget-feature=+reference-types`"]
157    pub fn reference_types(&mut self, enable: bool) -> &mut Bindgen {
158        self.externref = enable;
159        self
160    }
161
162    /// Explicitly specify the already parsed input module.
163    pub fn input_module(&mut self, name: &str, module: Module) -> &mut Bindgen {
164        let name = name.to_string();
165        self.input = Input::Module(module, name);
166        self
167    }
168
169    /// Specify the input as the provided Wasm bytes.
170    pub fn input_bytes(&mut self, name: &str, bytes: Vec<u8>) -> &mut Bindgen {
171        let name = name.to_string();
172        self.input = Input::Bytes(bytes, name);
173        self
174    }
175
176    fn switch_mode(&mut self, mode: OutputMode, flag: &str) -> Result<(), Error> {
177        match self.mode {
178            OutputMode::Bundler { .. } => self.mode = mode,
179            _ => bail!("cannot specify `{flag}` with another output mode already specified"),
180        }
181        Ok(())
182    }
183
184    pub fn nodejs(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
185        if node {
186            self.switch_mode(OutputMode::Node { module: false }, "--target nodejs")?;
187        }
188        Ok(self)
189    }
190
191    pub fn nodejs_module(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
192        if node {
193            self.switch_mode(
194                OutputMode::Node { module: true },
195                "--target experimental-nodejs-module",
196            )?;
197        }
198        Ok(self)
199    }
200
201    pub fn bundler(&mut self, bundler: bool) -> Result<&mut Bindgen, Error> {
202        if bundler {
203            self.switch_mode(
204                OutputMode::Bundler {
205                    browser_only: false,
206                },
207                "--target bundler",
208            )?;
209        }
210        Ok(self)
211    }
212
213    pub fn web(&mut self, web: bool) -> Result<&mut Bindgen, Error> {
214        if web {
215            self.switch_mode(OutputMode::Web, "--target web")?;
216        }
217        Ok(self)
218    }
219
220    pub fn no_modules(&mut self, no_modules: bool) -> Result<&mut Bindgen, Error> {
221        if no_modules {
222            self.switch_mode(
223                OutputMode::NoModules {
224                    global: "wasm_bindgen".to_string(),
225                },
226                "--target no-modules",
227            )?;
228        }
229        Ok(self)
230    }
231
232    pub fn browser(&mut self, browser: bool) -> Result<&mut Bindgen, Error> {
233        if browser {
234            match &mut self.mode {
235                OutputMode::Bundler { browser_only } => *browser_only = true,
236                _ => bail!("cannot specify `--browser` with other output types"),
237            }
238        }
239        Ok(self)
240    }
241
242    pub fn deno(&mut self, deno: bool) -> Result<&mut Bindgen, Error> {
243        if deno {
244            self.switch_mode(OutputMode::Deno, "--target deno")?;
245            self.encode_into(EncodeInto::Always);
246        }
247        Ok(self)
248    }
249
250    pub fn module(&mut self, source_phase: bool) -> Result<&mut Bindgen, Error> {
251        if source_phase {
252            self.switch_mode(OutputMode::Module, "--target module")?;
253        }
254        Ok(self)
255    }
256
257    pub fn no_modules_global(&mut self, name: &str) -> Result<&mut Bindgen, Error> {
258        if !wasm_bindgen_shared::identifier::is_valid_ident(name) {
259            bail!("`--no-modules-global` must be a valid JS identifier, got `{name}`");
260        }
261        match &mut self.mode {
262            OutputMode::NoModules { global } => *global = name.to_string(),
263            _ => bail!("can only specify `--no-modules-global` with `--target no-modules`"),
264        }
265        Ok(self)
266    }
267
268    pub fn debug(&mut self, debug: bool) -> &mut Bindgen {
269        self.debug = debug;
270        self
271    }
272
273    pub fn typescript(&mut self, typescript: bool) -> &mut Bindgen {
274        self.typescript = typescript;
275        self
276    }
277
278    pub fn omit_imports(&mut self, omit_imports: bool) -> &mut Bindgen {
279        self.omit_imports = omit_imports;
280        self
281    }
282
283    pub fn demangle(&mut self, demangle: bool) -> &mut Bindgen {
284        self.demangle = demangle;
285        self
286    }
287
288    pub fn keep_lld_exports(&mut self, keep_lld_exports: bool) -> &mut Bindgen {
289        self.keep_lld_exports = keep_lld_exports;
290        self
291    }
292
293    /// Exports the instance's exports as `__wasm`, as `debug` also does.
294    pub fn export_wasm(&mut self, export_wasm: bool) -> &mut Bindgen {
295        self.export_wasm = export_wasm;
296        self
297    }
298
299    pub fn keep_debug(&mut self, keep_debug: bool) -> &mut Bindgen {
300        self.keep_debug = keep_debug;
301        self
302    }
303
304    pub fn split_debug_info(&mut self, split: bool) -> &mut Bindgen {
305        self.split_debug_info = split;
306        self
307    }
308
309    pub fn debug_info_url(&mut self, url: &str) -> &mut Bindgen {
310        self.debug_info_url = Some(url.to_string());
311        self
312    }
313
314    pub fn remove_name_section(&mut self, remove: bool) -> &mut Bindgen {
315        self.remove_name_section = remove;
316        self
317    }
318
319    pub fn remove_producers_section(&mut self, remove: bool) -> &mut Bindgen {
320        self.remove_producers_section = remove;
321        self
322    }
323
324    pub fn emit_start(&mut self, emit: bool) -> &mut Bindgen {
325        self.emit_start = emit;
326        self
327    }
328
329    pub fn encode_into(&mut self, mode: EncodeInto) -> &mut Bindgen {
330        self.encode_into = mode;
331        self
332    }
333
334    pub fn omit_default_module_path(&mut self, omit_default_module_path: bool) -> &mut Bindgen {
335        self.omit_default_module_path = omit_default_module_path;
336        self
337    }
338
339    pub fn ts_typed_array_buffers(&mut self, ts_typed_array_buffers: bool) -> &mut Bindgen {
340        self.ts_typed_array_buffers = ts_typed_array_buffers;
341        self
342    }
343
344    pub fn split_linked_modules(&mut self, split_linked_modules: bool) -> &mut Bindgen {
345        self.split_linked_modules = split_linked_modules;
346        self
347    }
348
349    /// Imports `static_string` constants as JS String Builtins string
350    /// constants from `wasm:js/string-constants` instead of defining them in
351    /// the JS glue.
352    pub fn js_string_builtins(&mut self, js_string_builtins: bool) -> &mut Bindgen {
353        self.js_string_builtins = js_string_builtins;
354        self
355    }
356
357    pub fn reset_state_function(&mut self, generate_reset_state: bool) -> &mut Bindgen {
358        self.generate_reset_state = generate_reset_state;
359        self
360    }
361
362    pub fn force_enable_abort_handler(&mut self, force_enable_abort_handler: bool) -> &mut Self {
363        self.force_enable_abort_handler = force_enable_abort_handler;
364        self
365    }
366
367    pub fn generate<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
368        self.generate_output()?.emit(path.as_ref())
369    }
370
371    pub fn stem(&self) -> Result<&str, Error> {
372        Ok(match &self.input {
373            Input::None => bail!("must have an input by now"),
374            Input::Module(_, name) | Input::Bytes(_, name) => name,
375            Input::Path(path) => match &self.out_name {
376                Some(name) => name,
377                None => path.file_stem().unwrap().to_str().unwrap(),
378            },
379        })
380    }
381
382    pub fn generate_output(&mut self) -> Result<Output, Error> {
383        if self.debug_info_url.is_some() && !self.split_debug_info {
384            bail!("cannot specify `debug_info_url` without `split_debug_info`");
385        }
386        let mut module = match self.input {
387            Input::None => bail!("must have an input by now"),
388            Input::Module(ref mut m, _) => {
389                let blank_module = Module::default();
390                mem::replace(m, blank_module)
391            }
392            Input::Path(ref path) => {
393                let bytes = std::fs::read(path)
394                    .with_context(|| format!("failed reading '{}'", path.display()))?;
395                self.module_from_bytes(&bytes).with_context(|| {
396                    format!("failed getting Wasm module for '{}'", path.display())
397                })?
398            }
399            Input::Bytes(ref bytes, _) => self
400                .module_from_bytes(bytes)
401                .context("failed getting Wasm module")?,
402        };
403
404        if module
405            .customs
406            .remove_raw("__wasm_bindgen_emscripten_marker")
407            .is_some()
408        {
409            // Force the internal configuration to Emscripten mode.
410            self.mode = OutputMode::Emscripten;
411        }
412
413        // Emitted by the runtime under `--cfg wasm_bindgen_unstable_jspi`:
414        // JSPI goes through Emscripten's lifecycle hooks.
415        let emscripten_jspi_hooks = module
416            .customs
417            .remove_raw("__wasm_bindgen_emscripten_jspi_marker")
418            .is_some()
419            && matches!(self.mode, OutputMode::Emscripten);
420
421        // Enable reference type transformations if the module is already using
422        // it. `strip = true` drops the `target_features` section, in which case
423        // the runtime's externref intrinsics show it was built with reference
424        // types.
425        let has_target_features = module
426            .customs
427            .iter()
428            .any(|(_, custom)| custom.name() == "target_features");
429        let reference_types = if has_target_features {
430            wasm_conventions::target_feature(&module, "reference-types").unwrap_or(false)
431        } else if module
432            .exports
433            .iter()
434            .any(|export| export.name == "__externref_table_alloc")
435        {
436            eprintln!(
437                "warning: the `target_features` custom section is missing, likely due to \
438                 `strip = true`, so wasm-bindgen cannot detect which Wasm features are \
439                 enabled. Avoid `strip = true` and run `wasm-opt` on the wasm-bindgen \
440                 output instead."
441            );
442            true
443        } else {
444            false
445        };
446        if reference_types {
447            self.externref = true;
448        }
449
450        // Enable multivalue transformations if the module is already using it.
451        if let Ok(true) = wasm_conventions::target_feature(&module, "multivalue") {
452            self.multi_value = true;
453        }
454
455        // Check that no exported symbol is called "default" if we target web.
456        if matches!(self.mode, OutputMode::Web)
457            && module.exports.iter().any(|export| export.name == "default")
458        {
459            bail!("exported symbol \"default\" not allowed for --target web")
460        }
461
462        // Check that reset_state is only used with --target module, web, or node
463        if self.generate_reset_state
464            && !matches!(
465                self.mode,
466                OutputMode::Module | OutputMode::Web | OutputMode::Node { module: false }
467            )
468        {
469            bail!("--experimental-reset-state-function is only supported for --target module, --target web, or --target nodejs")
470        }
471
472        let thread_count = if self.mode.emscripten() {
473            // Emscripten owns multithreading bootstrap logic.
474            None
475        } else {
476            transforms::threads::run(&mut module)
477                .with_context(|| "failed to prepare module for threading")?
478        };
479
480        // If requested, turn all mangled symbols into prettier unmangled
481        // symbols with the help of `rustc-demangle`.
482        if self.demangle {
483            demangle(&mut module);
484        }
485        if !self.keep_lld_exports && !self.mode.emscripten() {
486            unexported_unused_lld_things(&mut module);
487        }
488        // Quick fix for https://github.com/wasm-bindgen/wasm-bindgen/pull/4931
489        // which is likely a compiler bug
490        {
491            let exn_import = module.imports.iter().find_map(|impt| match impt.kind {
492                walrus::ImportKind::Tag(id)
493                    if impt.module == "env" && impt.name == "__cpp_exception" =>
494                {
495                    Some((impt, id))
496                }
497                _ => None,
498            });
499            if let Some((import, id)) = exn_import {
500                let original_import_id = import.id();
501                let tag = module.tags.get_mut(id);
502                tag.kind = walrus::TagKind::Local;
503                module.imports.delete(original_import_id);
504                module.exports.add("__cpp_exception", tag.id);
505            }
506
507            // We're making quite a few changes, list ourselves as a producer.
508            module
509                .producers
510                .add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version());
511        }
512        // Parse and remove our custom section before executing descriptors.
513        // That includes checking that the binary has the same schema version
514        // as this version of the CLI, which is why we do it first - to make
515        // sure that this binary was produced by a compatible version of the
516        // wasm-bindgen macro before attempting to interpret our unstable
517        // descriptor format. That way, we give a more helpful version mismatch
518        // error instead of an unhelpful panic if an incompatible descriptor is
519        // found.
520        let mut storage = Vec::new();
521        let programs =
522            wit::extract_programs(&mut module, &mut storage).map_err(|err| match &self.input {
523                Input::Path(path) => err.context(format!(
524                    "failed to read wasm-bindgen data from '{}'",
525                    path.display()
526                )),
527                _ => err,
528            })?;
529
530        // Learn about the type signatures of all wasm-bindgen imports and
531        // exports by executing `__wbindgen_describe_*` functions. This'll
532        // effectively move all the descriptor functions to their own custom
533        // sections.
534        descriptors::execute(&mut module)?;
535
536        // Process the custom section we extracted earlier. In its stead insert
537        // a forward-compatible Wasm interface types section as well as an
538        // auxiliary section for all sorts of miscellaneous information and
539        // features #[wasm_bindgen] supports that aren't covered by wasm
540        // interface types.
541        wit::process(self, &mut module, programs, thread_count)?;
542
543        // Now that we've got type information from the webidl processing pass,
544        // touch up the output of rustc to insert externref shims where necessary.
545        // This is only done if the externref pass is enabled, which it's
546        // currently off-by-default since `externref` is still in development in
547        // engines.
548        //
549        // If the externref pass isn't necessary, then we blanket delete the
550        // export of all our externref intrinsics which will get cleaned up in the
551        // GC pass before JS generation.
552        if self.externref {
553            externref::process(&mut module)?;
554        } else {
555            let ids = module
556                .exports
557                .iter()
558                .filter(|e| e.name.starts_with("__externref"))
559                .map(|e| e.id())
560                .collect::<Vec<_>>();
561            for id in ids {
562                module.exports.delete(id);
563            }
564            // Clean up element segments as well if they have holes in them
565            // after some of our transformations, because non-externref engines
566            // only support contiguous arrays of function references in element
567            // segments.
568            externref::force_contiguous_elements(&mut module)?;
569        }
570
571        // Using all of our metadata convert our module to a multi-value using
572        // module if applicable.
573        if self.multi_value {
574            multivalue::run(&mut module)
575                .context("failed to transform return pointers into multi-value Wasm")?;
576        }
577
578        // Detect the exception-handling version before the JSPI transform
579        // runs: the JSPI suspending wrappers contain `try_table`s of their
580        // own, which would otherwise reclassify a `panic=abort` module as
581        // having full (unwinding) exception support.
582        let eh_version =
583            transforms::detect_exception_handling_version(&module, self.force_enable_abort_handler);
584
585        // Instrument JSPI exports and suspending imports with the in-wasm
586        // shadow-stack save/restore wrappers. This must run after the
587        // externref/multi-value passes (which repoint export items) and
588        // before the catch-wrapper pass, which then wraps *outside* the
589        // suspending wrappers (via the repointed `implements` entries) so
590        // that promise rejections are consumed innermost as data while
591        // SuspendError misuse and rethrown exceptions still reach the
592        // abort/catch machinery over a restored shadow stack. On emscripten
593        // under `wasm_bindgen_unstable_jspi` the fibers belong to
594        // emscripten's JSPI runtime, so the wrappers call its lifecycle hooks
595        // instead and touch no stack state.
596        let hooks = emscripten_jspi_hooks.then_some(eh_version);
597        run_jspi_transform(&mut module, self.externref, hooks)?;
598
599        // Generate Wasm catch wrappers for imports with #[wasm_bindgen(catch)].
600        // This runs after externref processing so that we have access to the
601        // externref table and allocation function.
602        //
603        // Emscripten output may contain wasm exception-handling instructions
604        // from linked libc++ / embind that have no relation to wasm-bindgen's
605        // `#[wasm_bindgen(catch)]` machinery, and the wasm-bindgen runtime
606        // intrinsics (`__externref_table`, `__externref_table_alloc`,
607        // `__wbindgen_exn_store`) may be absent. Skip the transform until
608        // proper emscripten-mode catch support lands.
609        if !matches!(self.mode, OutputMode::Emscripten) {
610            run_exception_handling_transforms(&mut module, eh_version)?;
611        }
612
613        // We've done a whole bunch of transformations to the Wasm module, many
614        // of which leave "garbage" lying around, so let's prune out all our
615        // unnecessary things here.
616        gc_module_and_adapters(&mut module);
617
618        let stem = self.stem()?;
619
620        // Now we execute the JS generation passes to actually emit JS/TypeScript/etc.
621        let aux = module
622            .customs
623            .delete_typed::<wit::WasmBindgenAux>()
624            .expect("aux section should be present");
625        let adapters = module
626            .customs
627            .delete_typed::<wit::NonstandardWitSection>()
628            .unwrap();
629        let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?;
630        cx.generate()?;
631        let js::FinalizedOutput {
632            js,
633            ts,
634            start,
635            emscripten_extern_pre_js,
636        } = cx.finalize(stem)?;
637        let generated = Generated {
638            snippets: aux.snippets.clone(),
639            local_modules: aux.local_modules.clone(),
640            mode: self.mode.clone(),
641            typescript: self.typescript,
642            npm_dependencies: cx.npm_dependencies.clone(),
643            js,
644            ts,
645            start,
646            emscripten_extern_pre_js,
647        };
648
649        Ok(Output {
650            module,
651            stem: stem.to_string(),
652            split_debug_info: self.split_debug_info,
653            debug_info_url: self.debug_info_url.clone(),
654            generated,
655        })
656    }
657
658    fn module_from_bytes(&self, bytes: &[u8]) -> Result<Module, Error> {
659        walrus::ModuleConfig::new()
660            // Skip validation of the module as LLVM's output is
661            // generally already well-formed and so we won't gain much
662            // from re-validating. Additionally LLVM's current output
663            // for threads includes atomic instructions but doesn't
664            // include shared memory, so it fails that part of
665            // validation!
666            .strict_validate(false)
667            .generate_dwarf(self.keep_debug || self.split_debug_info)
668            .generate_name_section(!self.remove_name_section)
669            .generate_producers_section(!self.remove_producers_section)
670            .parse(bytes)
671            .context("failed to parse input as wasm")
672    }
673
674    fn local_module_name(&self, module: &str) -> String {
675        format!("./snippets/{module}")
676    }
677
678    fn inline_js_module_name(
679        &self,
680        unique_crate_identifier: &str,
681        snippet_idx_in_crate: usize,
682    ) -> String {
683        format!("./snippets/{unique_crate_identifier}/inline{snippet_idx_in_crate}.js",)
684    }
685}
686
687fn reset_indentation(s: &str) -> String {
688    let mut indent: u32 = 0;
689    let mut dst = String::new();
690
691    fn is_doc_comment(line: &str) -> bool {
692        line.starts_with("*")
693    }
694
695    static TAB: &str = "    ";
696
697    for line in s.trim().lines() {
698        let line = line.trim();
699
700        // handle doc comments separately
701        if is_doc_comment(line) {
702            for _ in 0..indent {
703                dst.push_str(TAB);
704            }
705            dst.push(' ');
706            dst.push_str(line);
707            dst.push('\n');
708            continue;
709        }
710
711        if line.starts_with('}') {
712            indent = indent.saturating_sub(1);
713        }
714
715        let extra = if line.starts_with(':') || line.starts_with('?') {
716            1
717        } else {
718            0
719        };
720        if !line.is_empty() {
721            for _ in 0..indent + extra {
722                dst.push_str(TAB);
723            }
724            dst.push_str(line);
725        }
726        dst.push('\n');
727
728        if line.ends_with('{') {
729            indent += 1;
730        }
731    }
732    dst
733}
734
735/// Since Rust will soon adopt v0 mangling as the default,
736/// and the `rustc_demangle` crate doesn't output closure disambiguators,
737/// duplicate symbols can appear. We handle this case manually.
738///
739/// issue: <https://github.com/wasm-bindgen/wasm-bindgen/issues/4820>
740fn demangle(module: &mut Module) {
741    let (lower, upper) = module.funcs.iter().size_hint();
742    let mut counter: HashMap<String, i32> = HashMap::with_capacity(upper.unwrap_or(lower));
743
744    for func in module.funcs.iter_mut() {
745        let Some(name) = &func.name else {
746            continue;
747        };
748
749        let Ok(sym) = rustc_demangle::try_demangle(name) else {
750            continue;
751        };
752
753        let demangled = sym.to_string();
754        match counter.entry(demangled) {
755            Entry::Occupied(mut entry) => {
756                func.name = Some(format!("{}[{}]", entry.key(), entry.get()));
757                *entry.get_mut() += 1;
758            }
759            Entry::Vacant(entry) => {
760                func.name = Some(entry.key().clone());
761                entry.insert(1);
762            }
763        }
764    }
765}
766
767impl OutputMode {
768    fn uses_es_modules(&self) -> bool {
769        matches!(
770            self,
771            OutputMode::Bundler { .. }
772                | OutputMode::Web
773                | OutputMode::Node { module: true }
774                | OutputMode::Deno
775                | OutputMode::Module
776        )
777    }
778
779    fn nodejs(&self) -> bool {
780        matches!(self, OutputMode::Node { .. })
781    }
782
783    fn no_modules(&self) -> bool {
784        matches!(self, OutputMode::NoModules { .. })
785    }
786
787    fn bundler(&self) -> bool {
788        matches!(self, OutputMode::Bundler { .. })
789    }
790
791    fn emscripten(&self) -> bool {
792        matches!(self, OutputMode::Emscripten)
793    }
794}
795
796/// Remove a number of internal exports that are synthesized by Rust's linker,
797/// LLD. These exports aren't typically ever needed and just add extra space to
798/// the binary.
799fn unexported_unused_lld_things(module: &mut Module) {
800    let mut to_remove = Vec::new();
801    for export in module.exports.iter() {
802        match export.name.as_str() {
803            "__heap_base" | "__data_end" | "__indirect_function_table" => {
804                to_remove.push(export.id());
805            }
806            _ => {}
807        }
808    }
809    for id in to_remove {
810        module.exports.delete(id);
811    }
812}
813
814impl Output {
815    pub fn js(&self) -> &str {
816        &self.generated.js
817    }
818
819    pub fn ts(&self) -> Option<&str> {
820        if self.generated.typescript {
821            Some(&self.generated.ts)
822        } else {
823            None
824        }
825    }
826
827    pub fn start(&self) -> Option<&String> {
828        self.generated.start.as_ref()
829    }
830
831    pub fn snippets(&self) -> &BTreeMap<String, Vec<String>> {
832        &self.generated.snippets
833    }
834
835    pub fn local_modules(&self) -> &HashMap<String, String> {
836        &self.generated.local_modules
837    }
838
839    pub fn npm_dependencies(&self) -> &HashMap<String, (PathBuf, String)> {
840        &self.generated.npm_dependencies
841    }
842
843    pub fn wasm(&self) -> &walrus::Module {
844        &self.module
845    }
846
847    pub fn wasm_mut(&mut self) -> &mut walrus::Module {
848        &mut self.module
849    }
850
851    pub fn emit(&mut self, out_dir: impl AsRef<Path>) -> Result<(), Error> {
852        self._emit(out_dir.as_ref())
853    }
854
855    fn _emit(&mut self, out_dir: &Path) -> Result<(), Error> {
856        let wasm_name = format!("{}_bg", self.stem);
857        let wasm_path = out_dir.join(&wasm_name).with_extension("wasm");
858        fs::create_dir_all(out_dir)?;
859
860        let wasm_bytes = self.module.emit_wasm();
861        let wasm_bytes = if self.split_debug_info {
862            let debug_name = format!("{wasm_name}.debug.wasm");
863            let url = self.debug_info_url.as_deref().unwrap_or(&debug_name);
864            let main_bytes = split_debug_info(&wasm_bytes, url)?;
865            let debug_path = out_dir.join(&debug_name);
866            fs::write(&debug_path, &wasm_bytes)
867                .with_context(|| format!("failed to write `{}`", debug_path.display()))?;
868            main_bytes
869        } else {
870            wasm_bytes
871        };
872        fs::write(&wasm_path, wasm_bytes)
873            .with_context(|| format!("failed to write `{}`", wasm_path.display()))?;
874
875        let gen = &self.generated;
876
877        // Write out all local JS snippets to the final destination now that
878        // we've collected them from all the programs.
879        for (identifier, list) in gen.snippets.iter() {
880            for (i, js) in list.iter().enumerate() {
881                let name = format!("inline{i}.js");
882                let path = out_dir.join("snippets").join(identifier).join(name);
883                fs::create_dir_all(path.parent().unwrap())?;
884                fs::write(&path, js)
885                    .with_context(|| format!("failed to write `{}`", path.display()))?;
886            }
887        }
888
889        for (path, contents) in gen.local_modules.iter() {
890            let path = out_dir.join("snippets").join(path);
891            fs::create_dir_all(path.parent().unwrap())?;
892            fs::write(&path, contents)
893                .with_context(|| format!("failed to write `{}`", path.display()))?;
894        }
895
896        let is_genmode_nodemodule = matches!(gen.mode, OutputMode::Node { module: true });
897        if !gen.npm_dependencies.is_empty() || is_genmode_nodemodule {
898            #[derive(serde::Serialize)]
899            struct PackageJson<'a> {
900                #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
901                ty: Option<&'static str>,
902                dependencies: BTreeMap<&'a str, &'a str>,
903            }
904            let pj = PackageJson {
905                ty: is_genmode_nodemodule.then_some("module"),
906                dependencies: gen
907                    .npm_dependencies
908                    .iter()
909                    .map(|(k, v)| (k.as_str(), v.1.as_str()))
910                    .collect(),
911            };
912            let json = serde_json::to_string_pretty(&pj)?;
913            fs::write(out_dir.join("package.json"), json)?;
914        }
915
916        // And now that we've got all our JS and TypeScript, actually write it
917        // out to the filesystem.
918        let extension = "js";
919
920        fn write<P, C>(path: P, contents: C) -> Result<(), anyhow::Error>
921        where
922            P: AsRef<Path>,
923            C: AsRef<[u8]>,
924        {
925            fs::write(&path, contents)
926                .with_context(|| format!("failed to write `{}`", path.as_ref().display()))
927        }
928
929        let js_path = out_dir.join(&self.stem).with_extension(extension);
930        if matches!(self.generated.mode, OutputMode::Emscripten) {
931            let emscripten_js_path = out_dir.join("library_bindgen.js");
932            write(&emscripten_js_path, reset_indentation(&gen.js))?;
933            // When the user crate imports from an ESM module
934            // (`#[wasm_bindgen(module = "...")]`), we emit those imports to a
935            // sidecar `library_bindgen.extern-pre.js`. Consumers pass it to
936            // emcc with `--extern-pre-js`, which prepends it before emcc's
937            // modularize wrapper — ESM imports can only legally live there.
938            // Skip writing when empty so consumers don't accidentally pick
939            // up a stale file from a previous build.
940            let extern_pre_js_path = out_dir.join("library_bindgen.extern-pre.js");
941            if gen.emscripten_extern_pre_js.is_empty() {
942                let _ = fs::remove_file(&extern_pre_js_path);
943            } else {
944                write(
945                    &extern_pre_js_path,
946                    reset_indentation(&gen.emscripten_extern_pre_js),
947                )?;
948            }
949        } else {
950            write(&js_path, reset_indentation(&gen.js))?;
951        }
952
953        if let Some(start) = &gen.start {
954            let js_path = out_dir.join(wasm_name).with_extension(extension);
955            write(&js_path, reset_indentation(start))?;
956        }
957
958        if gen.typescript {
959            let ts_path = js_path.with_extension("d.ts");
960            fs::write(&ts_path, reset_indentation(&gen.ts))
961                .with_context(|| format!("failed to write `{}`", ts_path.display()))?;
962        }
963
964        if gen.typescript {
965            let ts_path = wasm_path.with_extension("wasm.d.ts");
966            let ts = wasm2es6js::typescript(&self.module)?;
967            fs::write(&ts_path, reset_indentation(&ts))
968                .with_context(|| format!("failed to write `{}`", ts_path.display()))?;
969        }
970
971        Ok(())
972    }
973}
974
975/// Remove the `.debug_*` custom sections from an emitted Wasm module and
976/// append an `external_debug_info` custom section that holds `url`.
977///
978/// Ref: https://github.com/WebAssembly/tool-conventions/blob/main/Debugging.md
979fn split_debug_info(wasm: &[u8], url: &str) -> Result<Vec<u8>, Error> {
980    let mut kept = Vec::new();
981    let mut keep_from = 0;
982    let mut section_start = 0;
983    for payload in wasmparser::Parser::new(0).parse_all(wasm) {
984        let payload = payload?;
985        if let wasmparser::Payload::CustomSection(section) = &payload {
986            if section.name().starts_with(".debug_") {
987                kept.push(keep_from..section_start);
988                keep_from = section.range().end;
989            }
990        }
991        if let wasmparser::Payload::Version { range, .. } = &payload {
992            section_start = range.end;
993        } else if let Some((_, range)) = payload.as_section() {
994            section_start = range.end;
995        }
996    }
997    kept.push(keep_from..wasm.len());
998
999    let mut contents = Vec::new();
1000    let name = "external_debug_info";
1001    leb128::write::unsigned(&mut contents, name.len() as u64)?;
1002    contents.extend_from_slice(name.as_bytes());
1003    leb128::write::unsigned(&mut contents, url.len() as u64)?;
1004    contents.extend_from_slice(url.as_bytes());
1005
1006    let kept_len: usize = kept.iter().map(|range| range.len()).sum();
1007    let mut out = Vec::with_capacity(kept_len + contents.len() + 6);
1008    for range in kept {
1009        out.extend_from_slice(&wasm[range]);
1010    }
1011    out.push(0);
1012    leb128::write::unsigned(&mut out, contents.len() as u64)?;
1013    out.extend_from_slice(&contents);
1014    Ok(out)
1015}
1016
1017/// Instrument `#[wasm_bindgen(jspi)]` exports and `#[wasm_bindgen(suspending)]`
1018/// imports with in-wasm shadow-stack management, or with emscripten's JSPI
1019/// lifecycle hooks when `hooks` is set. See `transforms::jspi`.
1020fn run_jspi_transform(
1021    module: &mut Module,
1022    externref: bool,
1023    hooks: Option<transforms::ExceptionHandlingVersion>,
1024) -> Result<(), Error> {
1025    let mut aux = module
1026        .customs
1027        .delete_typed::<wit::WasmBindgenAux>()
1028        .expect("aux section should exist");
1029    let mut wit = module
1030        .customs
1031        .delete_typed::<wit::NonstandardWitSection>()
1032        .expect("wit section should exist");
1033
1034    let result = transforms::jspi::run(module, &mut aux, &mut wit, externref, hooks)
1035        .context("failed to instrument module for JSPI");
1036
1037    module.customs.add(*wit);
1038    module.customs.add(*aux);
1039
1040    result
1041}
1042
1043/// Run the exception-handling transforms: catch wrappers for imports marked
1044/// `#[wasm_bindgen(catch)]`, then shadow stack restore wrappers for the exports
1045/// a panic can unwind out of.
1046///
1047/// They share a function so they share one EH detection. Re-detecting in
1048/// between would see `catch_handler`'s own `try_table`s and reclassify a
1049/// `panic=abort` module as `Modern`.
1050fn run_exception_handling_transforms(
1051    module: &mut Module,
1052    eh_version: transforms::ExceptionHandlingVersion,
1053) -> Result<(), Error> {
1054    log::debug!("Exception handling version: {eh_version:?}");
1055
1056    if eh_version == transforms::ExceptionHandlingVersion::None {
1057        return Ok(());
1058    }
1059
1060    // We need to temporarily remove the custom sections to avoid borrow issues
1061    let mut aux = module
1062        .customs
1063        .delete_typed::<wit::WasmBindgenAux>()
1064        .expect("aux section should exist");
1065    let wit = module
1066        .customs
1067        .delete_typed::<wit::NonstandardWitSection>()
1068        .expect("wit section should exist");
1069
1070    log::debug!(
1071        "Running catch handler: imports_with_catch={}, externref_table={:?}, externref_alloc={:?}, exn_store={:?}",
1072        aux.imports_with_catch.len(),
1073        aux.externref_table,
1074        aux.externref_alloc,
1075        aux.exn_store
1076    );
1077
1078    let result = transforms::catch_handler::run(module, &mut aux, &wit, eh_version)
1079        .context("failed to generate catch wrappers");
1080    if result.is_ok() {
1081        transforms::export_sp_restore::run(module, &wit, eh_version);
1082    }
1083
1084    // Re-add the custom sections
1085    module.customs.add(*wit);
1086    module.customs.add(*aux);
1087
1088    result
1089}
1090
1091fn gc_module_and_adapters(module: &mut Module) {
1092    loop {
1093        // Fist up, cleanup the native Wasm module. Note that roots can come
1094        // from custom sections, namely our Wasm interface types custom section
1095        // as well as the aux section.
1096        walrus::passes::gc::run(module);
1097
1098        // ... and afterwards we can delete any `implements` directives for any
1099        // imports that have been deleted.
1100        let imports_remaining = module
1101            .imports
1102            .iter()
1103            .map(|i| i.id())
1104            .collect::<HashSet<_>>();
1105        let mut section = module
1106            .customs
1107            .delete_typed::<wit::NonstandardWitSection>()
1108            .unwrap();
1109        section
1110            .implements
1111            .retain(|pair| imports_remaining.contains(&pair.0));
1112
1113        // ... and after we delete the `implements` directive we try to
1114        // delete some adapters themselves. If nothing is deleted, then we're
1115        // good to go. If something is deleted though then we may have free'd up
1116        // some functions in the main module to get deleted, so go again to gc
1117        // things.
1118        let any_removed = section.gc();
1119        module.customs.add(*section);
1120        if !any_removed {
1121            break;
1122        }
1123    }
1124}
1125
1126/// Returns a sorted iterator over a hash map, sorted based on key.
1127///
1128/// The intention of this API is to be used whenever the iteration order of a
1129/// `HashMap` might affect the generated JS bindings. We want to ensure that the
1130/// generated output is deterministic and we do so by ensuring that iteration of
1131/// hash maps is consistently sorted.
1132fn sorted_iter<K, V>(map: &HashMap<K, V>) -> impl Iterator<Item = (&K, &V)>
1133where
1134    K: Ord,
1135{
1136    let mut pairs = map.iter().collect::<Vec<_>>();
1137    pairs.sort_by_key(|(k, _)| *k);
1138    pairs.into_iter()
1139}