//! The polyfill for the WebIDL bindings proposal in wasm-bindgen. //! //! This module contains the polyfill (or at least the current state of and as //! closely as we can match) the WebIDL bindings proposal. The module exports //! one main function, `process`, which takes a `walrus::Module`. This module is //! expected to have two items: //! //! * First it contains all of the raw wasm-bindgen modules emitted by the Rust //! compiler. These raw custom sections are extracted, removed, decoded, and //! handled here. They contain information such as what's exported where, //! what's imported, comments, etc. //! * Second, the `descriptors.rs` pass must have run previously to execute all //! the descriptor functions in the wasm module. Through the synthesized //! custom section there we learn the type information of all //! functions/imports/exports in the module. //! //! The output of this function is then a new `walrus::Module` with the previous //! custom sections all removed and two new ones inserted. One is the webidl //! bindings custom section (or at least a close approximate) and the second is //! an auxiliary section for wasm-bindgen itself. The goal is for this auxiliary //! section to eventually be empty or inconsequential, allowing us to emit //! something that doesn't even need a JS shim one day. For now we're still //! pretty far away from that, so we'll settle for using webidl bindings as //! aggressively as possible! use crate::decode; use crate::descriptor::{Descriptor, Function}; use crate::descriptors::WasmBindgenDescriptorsSection; use crate::intrinsic::Intrinsic; use failure::{bail, format_err, Error}; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::str; use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId}; use wasm_bindgen_shared::struct_function_export_name; use wasm_webidl_bindings::ast; const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__"; mod bindings; mod incoming; mod outgoing; pub use self::incoming::NonstandardIncoming; pub use self::outgoing::NonstandardOutgoing; /// A nonstandard wasm-bindgen-specific WebIDL custom section. /// /// This nonstandard section is intended to convey all information that /// wasm-bindgen itself needs to know about binding functions. This means that /// it basically uses `NonstandardIncoming` instead of /// `IncomingBindingExpression` and such. It's also in a bit easier to work with /// format than the official WebIDL bindings custom section. /// /// Note that this is intended to be consumed during generation of JS shims and /// bindings. There it can be transformed, however, into an actual WebIDL /// binding section using all of the values it has internally. #[derive(Default, Debug)] pub struct NonstandardWebidlSection { /// Store of all WebIDL types. Used currently to store all function types /// specified in `Bindings`. This is intended to be passed through verbatim /// to a final WebIDL bindings section. pub types: ast::WebidlTypes, /// A mapping from all bound exported functions to the binding that we have /// listed for them. This is the master list of every binding that will be /// bound and have a shim generated for it in the wasm module. pub exports: HashMap, /// Similar to `exports` above, except for imports. This will describe all /// imports from the wasm module to indicate what the JS shim is expected to /// do. pub imports: HashMap, /// For closures and such we'll be calling entries in the function table /// with rich arguments (just like we call exports) so to do that we /// describe all the elem indices that we need to modify here as well. /// /// This is a list of pairs where the first element in the list is the /// element index in the function table being described and the `Binding` /// describes the signature that it's supposed to have. /// /// The index within this table itself is then used to call actually /// transformed functions. pub elems: Vec<(u32, Binding)>, } pub type NonstandardWebidlSectionId = TypedCustomSectionId; /// A non-standard wasm-bindgen-specifi WebIDL binding. This is meant to vaguely /// resemble a `FuctionBinding` in the official WebIDL bindings proposal, or at /// least make it very easy to manufacture an official value from this one. #[derive(Debug, Clone)] pub struct Binding { /// The WebAssembly type that the function is expected to have. Note that /// this may not match the actual bound function's type! That's because this /// type includes `anyref` but the Rust compiler never emits anyref. This /// is, however, used for the `anyref` pass to know what to transform to /// `anyref`. pub wasm_ty: walrus::TypeId, /// The WebIDL type of this binding, which is an index into the webidl /// binding section's `types` field. pub webidl_ty: ast::WebidlFunctionId, /// A list of incoming bindings. For exports this is the list of arguments, /// and for imports this is the return value. pub incoming: Vec, /// A list of outgoing bindings. For exports this is the return value and /// for imports this is the list of arguments. pub outgoing: Vec, /// An unfortunate necessity of today's implementation. Ideally WebIDL /// bindings are used with multi-value support in wasm everywhere, but today /// few engines support multi-value and LLVM certainly doesn't. Aggregates /// are then always returned through an out-ptr, so this indicates that if /// an out-ptr is present what wasm types are being transmitted through it. pub return_via_outptr: Option>, } impl Binding { /// Does this binding's wasm function signature have any `anyref`s? pub fn contains_anyref(&self, module: &walrus::Module) -> bool { let ty = module.types.get(self.wasm_ty); ty.params() .iter() .chain(ty.results()) .any(|ty| *ty == walrus::ValType::Anyref) } } /// A synthetic custom section which is not standardized, never will be, and /// cannot be serialized or parsed. This is synthesized from all of the /// compiler-emitted wasm-bindgen sections and then immediately removed to be /// processed in the JS generation pass. #[derive(Default, Debug)] pub struct WasmBindgenAux { /// Extra typescript annotations that should be appended to the generated /// TypeScript file. This is provided via a custom attribute in Rust code. pub extra_typescript: String, /// A map from identifier to the contents of each local module defined via /// the `#[wasm_bindgen(module = "/foo.js")]` import options. pub local_modules: HashMap, /// A map from unique crate identifier to the list of inline JS snippets for /// that crate identifier. pub snippets: HashMap>, /// A list of all `package.json` files that are intended to be included in /// the final build. pub package_jsons: HashSet, /// A map from exported function id to where it's expected to be exported /// to. pub export_map: HashMap, /// A map from imported function id to what it's expected to import. pub import_map: HashMap, /// Small bits of metadata about imports. pub imports_with_catch: HashSet, pub imports_with_variadic: HashSet, pub imports_with_assert_no_shim: HashSet, /// Auxiliary information to go into JS/TypeScript bindings describing the /// exported enums from Rust. pub enums: Vec, /// Auxiliary information to go into JS/TypeScript bindings describing the /// exported structs from Rust and their fields they've got exported. pub structs: Vec, } pub type WasmBindgenAuxId = TypedCustomSectionId; #[derive(Debug)] pub struct AuxExport { /// When generating errors about this export, a helpful name to remember it /// by. pub debug_name: String, /// Comments parsed in Rust and forwarded here to show up in JS bindings. pub comments: String, /// Argument names in Rust forwarded here to configure the names that show /// up in TypeScript bindings. pub arg_names: Option>, /// What kind of function this is and where it shows up pub kind: AuxExportKind, } /// All possible kinds of exports from a wasm module. /// /// This `enum` says where to place an exported wasm function. For example it /// may want to get hooked up to a JS class, or it may want to be exported as a /// free function (etc). /// /// TODO: it feels like this should not really be here per se. We probably want /// to either construct the JS object itself from within wasm or somehow move /// more of this information into some other section. Really what this is is /// sort of an "export map" saying how to wire up all the free functions from /// the wasm module into the output expected JS module. All our functions here /// currently take integer parameters and require a JS wrapper, but ideally /// we'd change them one day to taking/receiving `anyref` which then use some /// sort of webidl import to customize behavior or something like that. In any /// case this doesn't feel quite right in terms of priviledge separation, so /// we'll want to work on this. For now though it works. #[derive(Debug)] pub enum AuxExportKind { /// A free function that's just listed on the exported module Function(String), /// A function that's used to create an instane of a class. The function /// actually return just an integer which is put on an JS object currently. Constructor(String), /// This function is intended to be a getter for a field on a class. The /// first argument is the internal pointer and the returned value is /// expected to be the field. Getter { class: String, field: String }, /// This function is intended to be a setter for a field on a class. The /// first argument is the internal pointer and the second argument is /// expected to be the field's new value. Setter { class: String, field: String }, /// This is a free function (ish) but scoped inside of a class name. StaticFunction { class: String, name: String }, /// This is a member function of a class where the first parameter is the /// implicit integer stored in the class instance. Method { class: String, name: String, /// Whether or not this is calling a by-value method in Rust and should /// clear the internal pointer in JS automatically. consumed: bool, }, } #[derive(Debug)] pub struct AuxEnum { /// The name of this enum pub name: String, /// The copied Rust comments to forward to JS pub comments: String, /// A list of variants with their name and value pub variants: Vec<(String, u32)>, } #[derive(Debug)] pub struct AuxStruct { /// The name of this struct pub name: String, /// The copied Rust comments to forward to JS pub comments: String, } /// All possible types of imports that can be imported by a wasm module. /// /// This `enum` is intended to map out what an imported value is. For example /// this contains a ton of shims and various ways you can call a function. The /// base variant here is `Value` which simply means "hook this up to the import" /// and the signatures will match up. /// /// Note that this is *not* the same as the webidl bindings section. This is /// intended to be coupled with that to map out what actually gets hooked up to /// an import in the wasm module. The two work in tandem. /// /// Some of these items here are native to JS (like `Value`, indexing /// operations, etc). Others are shims generated by wasm-bindgen (like `Closure` /// or `Instanceof`). #[derive(Debug)] pub enum AuxImport { /// This import is expected to simply be whatever is the value that's /// imported Value(AuxValue), /// This import is expected to be a function that takes an `anyref` and /// returns a `bool`. It's expected that it tests if the argument is an /// instance of (using `instanceof`) the name specified. /// /// TODO: can we use `Reflect` or something like that to avoid an extra kind /// of import here? Instanceof(JsImport), /// This import is expected to be a shim that returns the JS value named by /// `JsImport`. Static(JsImport), /// This import is intended to manufacture a JS closure with the given /// signature and then return that back to Rust. Closure { mutable: bool, // whether or not this was a `FnMut` closure dtor: u32, // table element index of the destructor function binding_idx: u32, nargs: usize, }, /// This import is expected to be a shim that simply calls the `foo` method /// on the first object, passing along all other parameters and returning /// the resulting value. StructuralMethod(String), /// This import is a "structural getter" which simply returns the `.field` /// value of the first argument as an object. /// /// e.g. `function(x) { return x.foo; }` StructuralGetter(String), /// This import is a "structural getter" which simply returns the `.field` /// value of the specified class /// /// e.g. `function() { return TheClass.foo; }` StructuralClassGetter(JsImport, String), /// This import is a "structural setter" which simply sets the `.field` /// value of the first argument to the second argument. /// /// e.g. `function(x, y) { x.foo = y; }` StructuralSetter(String), /// This import is a "structural setter" which simply sets the `.field` /// value of the specified class to the first argument of the function. /// /// e.g. `function(x) { TheClass.foo = x; }` StructuralClassSetter(JsImport, String), /// This import is expected to be a shim that is an indexing getter of the /// JS class here, where the first argument of the function is the field to /// look up. The return value is the value of the field. /// /// e.g. `function(x) { return TheClass[x]; }` /// /// TODO: can we use `Reflect` or something like that to avoid an extra kind /// of import here? IndexingGetterOfClass(JsImport), /// This import is expected to be a shim that is an indexing getter of the /// first argument interpreted as an object where the field to look up is /// the second argument. /// /// e.g. `function(x, y) { return x[y]; }` /// /// TODO: can we use `Reflect` or something like that to avoid an extra kind /// of import here? IndexingGetterOfObject, /// This import is expected to be a shim that is an indexing setter of the /// JS class here, where the first argument of the function is the field to /// set and the second is the value to set it to. /// /// e.g. `function(x, y) { TheClass[x] = y; }` /// /// TODO: can we use `Reflect` or something like that to avoid an extra kind /// of import here? IndexingSetterOfClass(JsImport), /// This import is expected to be a shim that is an indexing setter of the /// first argument interpreted as an object where the next two fields are /// the field to set and the value to set it to. /// /// e.g. `function(x, y, z) { x[y] = z; }` /// /// TODO: can we use `Reflect` or something like that to avoid an extra kind /// of import here? IndexingSetterOfObject, /// This import is expected to be a shim that is an indexing deleter of the /// JS class here, where the first argument of the function is the field to /// delete. /// /// e.g. `function(x) { delete TheClass[x]; }` /// /// TODO: can we use `Reflect` or something like that to avoid an extra kind /// of import here? IndexingDeleterOfClass(JsImport), /// This import is expected to be a shim that is an indexing deleter of the /// first argument interpreted as an object where the second argument is /// the field to delete. /// /// e.g. `function(x, y) { delete x[y]; }` /// /// TODO: can we use `Reflect` or something like that to avoid an extra kind /// of import here? IndexingDeleterOfObject, /// This import is a generated shim which will wrap the provided pointer in /// a JS object corresponding to the Class name given here. The class name /// is one that is exported from the Rust/wasm. /// /// TODO: sort of like the export map below we should ideally create the /// `anyref` from within Rust itself and then return it directly rather than /// requiring an intrinsic here to do so. WrapInExportedClass(String), /// This is an intrinsic function expected to be implemented with a JS glue /// shim. Each intrinsic has its own expected signature and implementation. Intrinsic(Intrinsic), } /// Values that can be imported verbatim to hook up to an import. #[derive(Debug)] pub enum AuxValue { /// A bare JS value, no transformations, just put it in the slot. Bare(JsImport), /// A getter function for the class listed for the field, acquired using /// `getOwnPropertyDescriptor`. Getter(JsImport, String), /// Like `Getter`, but accesses a field of a class instead of an instance /// of the class. ClassGetter(JsImport, String), /// Like `Getter`, except the `set` property. Setter(JsImport, String), /// Like `Setter`, but for class fields instead of instance fields. ClassSetter(JsImport, String), } /// What can actually be imported and typically a value in each of the variants /// above of `AuxImport` /// /// A `JsImport` is intended to indicate what exactly is being imported for a /// particular operation. #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub struct JsImport { /// The base of whatever is being imported, either from a module, the global /// namespace, or similar. pub name: JsImportName, /// Various field accesses (like `.foo.bar.baz`) to hang off the `name` /// above. pub fields: Vec, } /// Return value of `determine_import` which is where we look at an imported /// function AST and figure out where it's actually being imported from /// (performing some validation checks and whatnot). #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub enum JsImportName { /// An item is imported from the global scope. The `name` is what's /// imported. Global { name: String }, /// Same as `Global`, except the `name` is imported via an ESM import from /// the specified `module` path. Module { module: String, name: String }, /// Same as `Module`, except we're importing from a local module defined in /// a local JS snippet. LocalModule { module: String, name: String }, /// Same as `Module`, except we're importing from an `inline_js` attribute InlineJs { unique_crate_identifier: String, snippet_idx_in_crate: usize, name: String, }, /// A global import which may have a number of vendor prefixes associated /// with it, like `webkitAudioPrefix`. The `name` is the name to test /// whether it's prefixed. VendorPrefixed { name: String, prefixes: Vec }, } struct Context<'a> { start_found: bool, module: &'a mut Module, bindings: NonstandardWebidlSection, aux: WasmBindgenAux, function_exports: HashMap, function_imports: HashMap, vendor_prefixes: HashMap>, unique_crate_identifier: &'a str, descriptors: HashMap, anyref_enabled: bool, support_start: bool, } pub fn process( module: &mut Module, anyref_enabled: bool, support_start: bool, ) -> Result<(NonstandardWebidlSectionId, WasmBindgenAuxId), Error> { let mut storage = Vec::new(); let programs = extract_programs(module, &mut storage)?; let mut cx = Context { bindings: Default::default(), aux: Default::default(), function_exports: Default::default(), function_imports: Default::default(), vendor_prefixes: Default::default(), descriptors: Default::default(), unique_crate_identifier: "", module, start_found: false, anyref_enabled, support_start, }; cx.init()?; for program in programs { cx.program(program)?; } if let Some(standard) = cx.module.customs.delete_typed::() { cx.standard(&standard)?; } cx.verify()?; let bindings = cx.module.customs.add(cx.bindings); let aux = cx.module.customs.add(cx.aux); Ok((bindings, aux)) } impl<'a> Context<'a> { fn init(&mut self) -> Result<(), Error> { // Make a map from string name to ids of all exports for export in self.module.exports.iter() { if let walrus::ExportItem::Function(f) = export.item { self.function_exports .insert(export.name.clone(), (export.id(), f)); } } // Make a map from string name to ids of all imports from our // placeholder module name which we'll want to be sure that we've got a // location listed of what to import there for each item. let mut intrinsics = Vec::new(); for import in self.module.imports.iter() { if import.module != PLACEHOLDER_MODULE { continue; } if let walrus::ImportKind::Function(f) = import.kind { self.function_imports .insert(import.name.clone(), (import.id(), f)); if let Some(intrinsic) = Intrinsic::from_symbol(&import.name) { intrinsics.push((import.id(), intrinsic)); } } } for (id, intrinsic) in intrinsics { self.bind_intrinsic(id, intrinsic)?; } self.inject_anyref_initialization()?; if let Some(custom) = self .module .customs .delete_typed::() { let WasmBindgenDescriptorsSection { descriptors, closure_imports, } = *custom; // Store all the executed descriptors in our own field so we have // access to them while processing programs. self.descriptors.extend(descriptors); // Register all the injected closure imports as that they're expected // to manufacture a particular type of closure. // // First we register the imported function shim which returns a // `JsValue` for the closure. We manufacture this signature's // binding since it's not listed anywhere. // // Next we register the corresponding table element's binding in // the webidl bindings section. This binding will later be used to // generate a shim (if necessary) for the table element. // // Finally we store all this metadata in the import map which we've // learned so when a binding for the import is generated we can // generate all the appropriate shims. for (id, descriptor) in closure_imports { let binding = Function { shim_idx: 0, arguments: vec![Descriptor::I32; 3], ret: Descriptor::Anyref, }; bindings::register_import( self.module, &mut self.bindings, id, binding, ast::WebidlFunctionKind::Static, )?; // Synthesize the two integer pointers we pass through which // aren't present in the signature but are present in the wasm // signature. let mut function = descriptor.function.clone(); let nargs = function.arguments.len(); function.arguments.insert(0, Descriptor::I32); function.arguments.insert(0, Descriptor::I32); let binding_idx = bindings::register_table_element( self.module, &mut self.bindings, descriptor.shim_idx, function, )?; self.aux.import_map.insert( id, AuxImport::Closure { dtor: descriptor.dtor_idx, mutable: descriptor.mutable, binding_idx, nargs, }, ); } } Ok(()) } // Ensure that the `start` function for this module calls the // `__wbindgen_init_anyref_table` function. This'll ensure that all // instances of this module have the initial slots of the anyref table // initialized correctly. fn inject_anyref_initialization(&mut self) -> Result<(), Error> { if !self.anyref_enabled { return Ok(()); } // Make sure to export the `anyref` table for the JS bindings since it // will need to be initialized. If it doesn't exist though then the // module must not use it, so we skip it. let table = self .module .tables .iter() .find(|t| match t.kind { walrus::TableKind::Anyref(_) => true, _ => false, }); let table = match table { Some(t) => t.id(), None => return Ok(()), }; self.module.exports.add("__wbg_anyref_table", table); let ty = self.module.types.add(&[], &[]); let (import, import_id) = self.module.add_import_func( PLACEHOLDER_MODULE, "__wbindgen_init_anyref_table", ty, ); self.module.start = Some(match self.module.start { Some(prev_start) => { let mut builder = walrus::FunctionBuilder::new(); let call_init = builder.call(import, Box::new([])); let call_prev = builder.call(prev_start, Box::new([])); builder.finish(ty, Vec::new(), vec![call_init, call_prev], self.module) } None => import, }); self.bind_intrinsic(import_id, Intrinsic::InitAnyrefTable)?; Ok(()) } fn bind_intrinsic(&mut self, id: ImportId, intrinsic: Intrinsic) -> Result<(), Error> { bindings::register_import( self.module, &mut self.bindings, id, intrinsic.binding(), ast::WebidlFunctionKind::Static, )?; self.aux .import_map .insert(id, AuxImport::Intrinsic(intrinsic)); Ok(()) } fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> { self.unique_crate_identifier = program.unique_crate_identifier; let decode::Program { exports, enums, imports, structs, typescript_custom_sections, local_modules, inline_js, unique_crate_identifier, package_json, } = program; for module in local_modules { // All local modules we find should be unique, but the same module // may have showed up in a few different blocks. If that's the case // all the same identifiers should have the same contents. if let Some(prev) = self .aux .local_modules .insert(module.identifier.to_string(), module.contents.to_string()) { assert_eq!(prev, module.contents); } } if let Some(s) = package_json { self.aux.package_jsons.insert(s.into()); } for export in exports { self.export(export)?; } // Register vendor prefixes for all types before we walk over all the // imports to ensure that if a vendor prefix is listed somewhere it'll // apply to all the imports. for import in imports.iter() { if let decode::ImportKind::Type(ty) = &import.kind { if ty.vendor_prefixes.len() == 0 { continue; } self.vendor_prefixes .entry(ty.name.to_string()) .or_insert(Vec::new()) .extend(ty.vendor_prefixes.iter().map(|s| s.to_string())); } } for import in imports { self.import(import)?; } for enum_ in enums { self.enum_(enum_)?; } for struct_ in structs { self.struct_(struct_)?; } for section in typescript_custom_sections { self.aux.extra_typescript.push_str(section); self.aux.extra_typescript.push_str("\n\n"); } self.aux .snippets .entry(unique_crate_identifier.to_string()) .or_insert(Vec::new()) .extend(inline_js.iter().map(|s| s.to_string())); Ok(()) } fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> { let wasm_name = match &export.class { Some(class) => struct_function_export_name(class, export.function.name), None => export.function.name.to_string(), }; let mut descriptor = match self.descriptors.remove(&wasm_name) { None => return Ok(()), Some(d) => d.unwrap_function(), }; let (export_id, id) = self.function_exports[&wasm_name]; if export.start { self.add_start_function(id)?; } let kind = match export.class { Some(class) => { let class = class.to_string(); match export.method_kind { decode::MethodKind::Constructor => AuxExportKind::Constructor(class), decode::MethodKind::Operation(op) => match op.kind { decode::OperationKind::Getter(f) => { descriptor.arguments.insert(0, Descriptor::I32); AuxExportKind::Getter { class, field: f.to_string(), } } decode::OperationKind::Setter(f) => { descriptor.arguments.insert(0, Descriptor::I32); AuxExportKind::Setter { class, field: f.to_string(), } } _ if op.is_static => AuxExportKind::StaticFunction { class, name: export.function.name.to_string(), }, _ => { descriptor.arguments.insert(0, Descriptor::I32); AuxExportKind::Method { class, name: export.function.name.to_string(), consumed: export.consumed, } } }, } } None => AuxExportKind::Function(export.function.name.to_string()), }; self.aux.export_map.insert( export_id, AuxExport { debug_name: wasm_name, comments: concatenate_comments(&export.comments), arg_names: Some(export.function.arg_names), kind, }, ); bindings::register_export(self.module, &mut self.bindings, export_id, descriptor)?; Ok(()) } fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> { if self.start_found { bail!("cannot specify two `start` functions"); } self.start_found = true; // Skip this when we're generating tests if !self.support_start { return Ok(()); } let prev_start = match self.module.start { Some(f) => f, None => { self.module.start = Some(id); return Ok(()); } }; // Note that we call the previous start function, if any, first. This is // because the start function currently only shows up when it's injected // through thread/anyref transforms. These injected start functions need // to happen before user code, so we always schedule them first. let mut builder = walrus::FunctionBuilder::new(); let call1 = builder.call(prev_start, Box::new([])); let call2 = builder.call(id, Box::new([])); let ty = self.module.funcs.get(id).ty(); let new_start = builder.finish(ty, Vec::new(), vec![call1, call2], self.module); self.module.start = Some(new_start); Ok(()) } fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> { match &import.kind { decode::ImportKind::Function(f) => self.import_function(&import, f), decode::ImportKind::Static(s) => self.import_static(&import, s), decode::ImportKind::Type(t) => self.import_type(&import, t), decode::ImportKind::Enum(_) => Ok(()), } } fn import_function( &mut self, import: &decode::Import<'_>, function: &decode::ImportFunction<'_>, ) -> Result<(), Error> { let decode::ImportFunction { shim, catch, variadic, method, structural, function, assert_no_shim, } = function; let (import_id, _id) = match self.function_imports.get(*shim) { Some(pair) => *pair, None => return Ok(()), }; let descriptor = match self.descriptors.remove(*shim) { None => return Ok(()), Some(d) => d.unwrap_function(), }; // Record this for later as it affects JS binding generation, but note // that this doesn't affect the WebIDL interface at all. if *variadic { self.aux.imports_with_variadic.insert(import_id); } if *catch { self.aux.imports_with_catch.insert(import_id); } if *assert_no_shim { self.aux.imports_with_assert_no_shim.insert(import_id); } // Perform two functions here. First we're saving off our WebIDL // bindings signature, indicating what we think our import is going to // be. Next we're saving off other metadata indicating where this item // is going to be imported from. The `import_map` table will record, for // each import, what is getting hooked up to that slot of the import // table to the WebAssembly instance. let import = match method { Some(data) => { let class = self.determine_import(import, &data.class)?; match &data.kind { // NB: `structural` is ignored for constructors since the // js type isn't expected to change anyway. decode::MethodKind::Constructor => { bindings::register_import( self.module, &mut self.bindings, import_id, descriptor, ast::WebidlFunctionKind::Constructor, )?; AuxImport::Value(AuxValue::Bare(class)) } decode::MethodKind::Operation(op) => { let (import, method) = self.determine_import_op(class, function, *structural, op)?; let kind = if method { let kind = ast::WebidlFunctionKindMethod { // TODO: what should this actually be? ty: ast::WebidlScalarType::Any.into(), }; ast::WebidlFunctionKind::Method(kind) } else { ast::WebidlFunctionKind::Static }; bindings::register_import( self.module, &mut self.bindings, import_id, descriptor, kind, )?; import } } } // NB: `structural` is ignored for free functions since it's // expected that the binding isn't changing anyway. None => { bindings::register_import( self.module, &mut self.bindings, import_id, descriptor, ast::WebidlFunctionKind::Static, )?; let name = self.determine_import(import, function.name)?; AuxImport::Value(AuxValue::Bare(name)) } }; self.aux.import_map.insert(import_id, import); Ok(()) } /// The `bool` returned indicates whether the imported value should be /// invoked as a method (first arg is implicitly `this`) or if the imported /// value is a simple function-like shim fn determine_import_op( &mut self, mut class: JsImport, function: &decode::Function<'_>, structural: bool, op: &decode::Operation<'_>, ) -> Result<(AuxImport, bool), Error> { match op.kind { decode::OperationKind::Regular => { if op.is_static { class.fields.push(function.name.to_string()); Ok((AuxImport::Value(AuxValue::Bare(class)), false)) } else if structural { Ok(( AuxImport::StructuralMethod(function.name.to_string()), false, )) } else { class.fields.push("prototype".to_string()); class.fields.push(function.name.to_string()); Ok((AuxImport::Value(AuxValue::Bare(class)), true)) } } decode::OperationKind::Getter(field) => { if structural { if op.is_static { Ok(( AuxImport::StructuralClassGetter(class, field.to_string()), false, )) } else { Ok((AuxImport::StructuralGetter(field.to_string()), false)) } } else { let val = if op.is_static { AuxValue::ClassGetter(class, field.to_string()) } else { AuxValue::Getter(class, field.to_string()) }; Ok((AuxImport::Value(val), true)) } } decode::OperationKind::Setter(field) => { if structural { if op.is_static { Ok(( AuxImport::StructuralClassSetter(class, field.to_string()), false, )) } else { Ok((AuxImport::StructuralSetter(field.to_string()), false)) } } else { let val = if op.is_static { AuxValue::ClassSetter(class, field.to_string()) } else { AuxValue::Setter(class, field.to_string()) }; Ok((AuxImport::Value(val), true)) } } decode::OperationKind::IndexingGetter => { if !structural { bail!("indexing getters must always be structural"); } if op.is_static { Ok((AuxImport::IndexingGetterOfClass(class), false)) } else { Ok((AuxImport::IndexingGetterOfObject, false)) } } decode::OperationKind::IndexingSetter => { if !structural { bail!("indexing setters must always be structural"); } if op.is_static { Ok((AuxImport::IndexingSetterOfClass(class), false)) } else { Ok((AuxImport::IndexingSetterOfObject, false)) } } decode::OperationKind::IndexingDeleter => { if !structural { bail!("indexing deleters must always be structural"); } if op.is_static { Ok((AuxImport::IndexingDeleterOfClass(class), false)) } else { Ok((AuxImport::IndexingDeleterOfObject, false)) } } } } fn import_static( &mut self, import: &decode::Import<'_>, static_: &decode::ImportStatic<'_>, ) -> Result<(), Error> { let (import_id, _id) = match self.function_imports.get(static_.shim) { Some(pair) => *pair, None => return Ok(()), }; // Register the signature of this imported shim bindings::register_import( self.module, &mut self.bindings, import_id, Function { arguments: Vec::new(), shim_idx: 0, ret: Descriptor::Anyref, }, ast::WebidlFunctionKind::Static, )?; // And then save off that this function is is an instanceof shim for an // imported item. let import = self.determine_import(import, &static_.name)?; self.aux .import_map .insert(import_id, AuxImport::Static(import)); Ok(()) } fn import_type( &mut self, import: &decode::Import<'_>, type_: &decode::ImportType<'_>, ) -> Result<(), Error> { let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) { Some(pair) => *pair, None => return Ok(()), }; // Register the signature of this imported shim bindings::register_import( self.module, &mut self.bindings, import_id, Function { arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], shim_idx: 0, ret: Descriptor::Boolean, }, ast::WebidlFunctionKind::Static, )?; // And then save off that this function is is an instanceof shim for an // imported item. let import = self.determine_import(import, &type_.name)?; self.aux .import_map .insert(import_id, AuxImport::Instanceof(import)); Ok(()) } fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> { let aux = AuxEnum { name: enum_.name.to_string(), comments: concatenate_comments(&enum_.comments), variants: enum_ .variants .iter() .map(|v| (v.name.to_string(), v.value)) .collect(), }; self.aux.enums.push(aux); Ok(()) } fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> { for field in struct_.fields { let getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name); let setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name); let descriptor = match self.descriptors.remove(&getter) { None => continue, Some(d) => d, }; // Register a webidl transformation for the getter let (getter_id, _) = self.function_exports[&getter]; let getter_descriptor = Function { arguments: vec![Descriptor::I32], shim_idx: 0, ret: descriptor.clone(), }; bindings::register_export( self.module, &mut self.bindings, getter_id, getter_descriptor, )?; self.aux.export_map.insert( getter_id, AuxExport { debug_name: format!("getter for `{}::{}`", struct_.name, field.name), arg_names: None, comments: concatenate_comments(&field.comments), kind: AuxExportKind::Getter { class: struct_.name.to_string(), field: field.name.to_string(), }, }, ); // If present, register information for the setter as well. if field.readonly { continue; } let (setter_id, _) = self.function_exports[&setter]; let setter_descriptor = Function { arguments: vec![Descriptor::I32, descriptor], shim_idx: 0, ret: Descriptor::Unit, }; bindings::register_export( self.module, &mut self.bindings, setter_id, setter_descriptor, )?; self.aux.export_map.insert( setter_id, AuxExport { debug_name: format!("setter for `{}::{}`", struct_.name, field.name), arg_names: None, comments: concatenate_comments(&field.comments), kind: AuxExportKind::Setter { class: struct_.name.to_string(), field: field.name.to_string(), }, }, ); } let aux = AuxStruct { name: struct_.name.to_string(), comments: concatenate_comments(&struct_.comments), }; self.aux.structs.push(aux); let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { self.aux.import_map.insert( *import_id, AuxImport::WrapInExportedClass(struct_.name.to_string()), ); let binding = Function { shim_idx: 0, arguments: vec![Descriptor::I32], ret: Descriptor::Anyref, }; bindings::register_import( self.module, &mut self.bindings, *import_id, binding, ast::WebidlFunctionKind::Static, )?; } Ok(()) } fn determine_import(&self, import: &decode::Import<'_>, item: &str) -> Result { let is_local_snippet = match import.module { decode::ImportModule::Named(s) => self.aux.local_modules.contains_key(s), decode::ImportModule::RawNamed(_) => false, decode::ImportModule::Inline(_) => true, decode::ImportModule::None => false, }; // Similar to `--target no-modules`, only allow vendor prefixes // basically for web apis, shouldn't be necessary for things like npm // packages or other imported items. let vendor_prefixes = self.vendor_prefixes.get(item); if let Some(vendor_prefixes) = vendor_prefixes { assert!(vendor_prefixes.len() > 0); if is_local_snippet { bail!( "local JS snippets do not support vendor prefixes for \ the import of `{}` with a polyfill of `{}`", item, &vendor_prefixes[0] ); } if let decode::ImportModule::Named(module) = &import.module { bail!( "import of `{}` from `{}` has a polyfill of `{}` listed, but vendor prefixes aren't supported when importing from modules", item, module, &vendor_prefixes[0], ); } if let Some(ns) = &import.js_namespace { bail!( "import of `{}` through js namespace `{}` isn't supported \ right now when it lists a polyfill", item, ns ); } return Ok(JsImport { name: JsImportName::VendorPrefixed { name: item.to_string(), prefixes: vendor_prefixes.clone(), }, fields: Vec::new(), }); } let (name, fields) = match import.js_namespace { Some(ns) => (ns, vec![item.to_string()]), None => (item, Vec::new()), }; let name = match import.module { decode::ImportModule::Named(module) if is_local_snippet => JsImportName::LocalModule { module: module.to_string(), name: name.to_string(), }, decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => { JsImportName::Module { module: module.to_string(), name: name.to_string(), } } decode::ImportModule::Inline(idx) => { let offset = self .aux .snippets .get(self.unique_crate_identifier) .map(|s| s.len()) .unwrap_or(0); JsImportName::InlineJs { unique_crate_identifier: self.unique_crate_identifier.to_string(), snippet_idx_in_crate: idx as usize + offset, name: name.to_string(), } } decode::ImportModule::None => JsImportName::Global { name: name.to_string(), }, }; Ok(JsImport { name, fields }) } /// Processes bindings from a standard WebIDL bindings custom section. /// /// No module coming out of the Rust compiler will have one of these, but /// eventually there's going to be other producers of the WebIDL bindings /// custom section as well. This functionality is intended to allow /// `wasm-bindgen`-the-CLI-tool to act as a polyfill for those modules as /// well as Rust modules. /// /// Here a standard `WebidlBindings` custom section is taken and we process /// that into our own internal data structures to ensure that we have a /// binding listed for all the described bindings. /// /// In other words, this is a glorified conversion from the "official" /// WebIDL bindings custom section into the wasm-bindgen internal /// representation. fn standard(&mut self, std: &ast::WebidlBindings) -> Result<(), Error> { for (_id, bind) in std.binds.iter() { let binding = self.standard_binding(std, bind)?; let func = self.module.funcs.get(bind.func); match &func.kind { walrus::FunctionKind::Import(i) => { let id = i.import; self.standard_import(binding, id)?; } walrus::FunctionKind::Local(_) => { let export = self .module .exports .iter() .find(|e| match e.item { walrus::ExportItem::Function(f) => f == bind.func, _ => false, }) .ok_or_else(|| format_err!("missing export function for webidl binding"))?; let id = export.id(); self.standard_export(binding, id)?; } walrus::FunctionKind::Uninitialized(_) => unreachable!(), } } Ok(()) } /// Creates a wasm-bindgen-internal `Binding` from an official `Bind` /// structure specified in the upstream binary format. /// /// This will largely just copy some things into our own arenas but also /// processes the list of binding expressions into our own representations. fn standard_binding( &mut self, std: &ast::WebidlBindings, bind: &ast::Bind, ) -> Result { let binding: &ast::FunctionBinding = std .bindings .get(bind.binding) .ok_or_else(|| format_err!("bad binding id"))?; let (wasm_ty, webidl_ty, incoming, outgoing) = match binding { ast::FunctionBinding::Export(e) => ( e.wasm_ty, e.webidl_ty, e.params.bindings.as_slice(), e.result.bindings.as_slice(), ), ast::FunctionBinding::Import(e) => ( e.wasm_ty, e.webidl_ty, e.result.bindings.as_slice(), e.params.bindings.as_slice(), ), }; let webidl_ty = copy_ty(&mut self.bindings.types, webidl_ty, &std.types); let webidl_ty = match webidl_ty { ast::WebidlTypeRef::Id(id) => ::wrap(id), _ => bail!("invalid webidl type listed"), }; return Ok(Binding { wasm_ty, webidl_ty, incoming: incoming .iter() .cloned() .map(NonstandardIncoming::Standard) .collect(), outgoing: outgoing .iter() .cloned() .map(NonstandardOutgoing::Standard) .collect(), return_via_outptr: None, }); /// Recursively clones `ty` into` dst` where it originally indexes /// values in `src`, returning a new type ref which indexes inside of /// `dst`. fn copy_ty( dst: &mut ast::WebidlTypes, ty: ast::WebidlTypeRef, src: &ast::WebidlTypes, ) -> ast::WebidlTypeRef { let id = match ty { ast::WebidlTypeRef::Id(id) => id, ast::WebidlTypeRef::Scalar(_) => return ty, }; let ty: &ast::WebidlCompoundType = src.get(id).unwrap(); match ty { ast::WebidlCompoundType::Function(f) => { let params = f .params .iter() .map(|param| copy_ty(dst, *param, src)) .collect(); let result = f.result.map(|ty| copy_ty(dst, ty, src)); dst.insert(ast::WebidlFunction { kind: f.kind.clone(), params, result, }) .into() } _ => unimplemented!(), } } } /// Registers that `id` has a `binding` which was read from a standard /// webidl bindings section, so the source of `id` is its actual module/name /// listed in the wasm module. fn standard_import(&mut self, binding: Binding, id: walrus::ImportId) -> Result<(), Error> { let import = self.module.imports.get(id); let js = JsImport { name: JsImportName::Module { module: import.module.clone(), name: import.name.clone(), }, fields: Vec::new(), }; let value = AuxValue::Bare(js); assert!(self .aux .import_map .insert(id, AuxImport::Value(value)) .is_none()); assert!(self.bindings.imports.insert(id, binding).is_none()); Ok(()) } /// Registers that `id` has a `binding` and comes from a standard webidl /// bindings section so it doesn't have any documentation or debug names we /// can work with. fn standard_export(&mut self, binding: Binding, id: walrus::ExportId) -> Result<(), Error> { let export = self.module.exports.get(id); let kind = AuxExportKind::Function(export.name.clone()); let export = AuxExport { debug_name: format!("standard export {:?}", id), comments: String::new(), arg_names: None, kind, }; assert!(self.aux.export_map.insert(id, export).is_none()); assert!(self.bindings.exports.insert(id, binding).is_none()); Ok(()) } /// Perform a small verification pass over the module to perform some /// internal sanity checks. fn verify(&self) -> Result<(), Error> { let mut imports_counted = 0; for import in self.module.imports.iter() { if import.module != PLACEHOLDER_MODULE { continue; } match import.kind { walrus::ImportKind::Function(_) => {} _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), } // Ensure that everything imported from the `__wbindgen_placeholder__` // module has a location listed as to where it's expected to be // imported from. if !self.aux.import_map.contains_key(&import.id()) { bail!( "import of `{}` doesn't have an import map item listed", import.name ); } // Also make sure there's a binding listed for it. if !self.bindings.imports.contains_key(&import.id()) { bail!("import of `{}` doesn't have a binding listed", import.name); } imports_counted += 1; } // Make sure there's no extraneous bindings that weren't actually // imported in the module. if self.aux.import_map.len() != imports_counted { bail!("import map is larger than the number of imports"); } if self.bindings.imports.len() != imports_counted { bail!("import binding map is larger than the number of imports"); } // Make sure the export map and export bindings map contain the same // number of entries. for id in self.bindings.exports.keys() { if !self.aux.export_map.contains_key(id) { bail!("bindings map has an entry that the export map does not"); } } if self.bindings.exports.len() != self.aux.export_map.len() { bail!("export map and export bindings map have different sizes"); } Ok(()) } } impl walrus::CustomSection for NonstandardWebidlSection { fn name(&self) -> &str { "webidl custom section" } fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { panic!("shouldn't emit custom sections just yet"); } } impl walrus::CustomSection for WasmBindgenAux { fn name(&self) -> &str { "wasm-bindgen custom section" } fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { panic!("shouldn't emit custom sections just yet"); } } fn extract_programs<'a>( module: &mut Module, program_storage: &'a mut Vec>, ) -> Result>, Error> { let my_version = wasm_bindgen_shared::version(); assert!(program_storage.is_empty()); while let Some(raw) = module.customs.remove_raw("__wasm_bindgen_unstable") { log::debug!( "custom section '{}' looks like a wasm bindgen section", raw.name ); program_storage.push(raw.data); } let mut ret = Vec::new(); for program in program_storage.iter() { let mut payload = &program[..]; while let Some(data) = get_remaining(&mut payload) { // Historical versions of wasm-bindgen have used JSON as the custom // data section format. Newer versions, however, are using a custom // serialization protocol that looks much more like the wasm spec. // // We, however, want a sanity check to ensure that if we're running // against the wrong wasm-bindgen we get a nicer error than an // internal decode error. To that end we continue to verify a tiny // bit of json at the beginning of each blob before moving to the // next blob. This should keep us compatible with older wasm-bindgen // instances as well as forward-compatible for now. // // Note, though, that as `wasm-pack` picks up steam it's hoped we // can just delete this entirely. The `wasm-pack` project already // manages versions for us, so we in theory should need this check // less and less over time. if let Some(their_version) = verify_schema_matches(data)? { bail!( " it looks like the Rust project used to create this wasm file was linked against a different version of wasm-bindgen than this binary: rust wasm file: {} this binary: {} Currently the bindgen format is unstable enough that these two version must exactly match, so it's required that these two version are kept in sync by either updating the wasm-bindgen dependency or this binary. You should be able to update the wasm-bindgen dependency with: cargo update -p wasm-bindgen or you can update the binary with cargo install -f wasm-bindgen-cli if this warning fails to go away though and you're not sure what to do feel free to open an issue at https://github.com/rustwasm/wasm-bindgen/issues! ", their_version, my_version, ); } let next = get_remaining(&mut payload).unwrap(); log::debug!("found a program of length {}", next.len()); ret.push(::decode_all(next)); } } Ok(ret) } fn get_remaining<'a>(data: &mut &'a [u8]) -> Option<&'a [u8]> { if data.len() == 0 { return None; } let len = ((data[0] as usize) << 0) | ((data[1] as usize) << 8) | ((data[2] as usize) << 16) | ((data[3] as usize) << 24); let (a, b) = data[4..].split_at(len); *data = b; Some(a) } fn verify_schema_matches<'a>(data: &'a [u8]) -> Result, Error> { macro_rules! bad { () => { bail!("failed to decode what looked like wasm-bindgen data") }; } let data = match str::from_utf8(data) { Ok(s) => s, Err(_) => bad!(), }; log::debug!("found version specifier {}", data); if !data.starts_with("{") || !data.ends_with("}") { bad!() } let needle = "\"schema_version\":\""; let rest = match data.find(needle) { Some(i) => &data[i + needle.len()..], None => bad!(), }; let their_schema_version = match rest.find("\"") { Some(i) => &rest[..i], None => bad!(), }; if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION { return Ok(None); } let needle = "\"version\":\""; let rest = match data.find(needle) { Some(i) => &data[i + needle.len()..], None => bad!(), }; let their_version = match rest.find("\"") { Some(i) => &rest[..i], None => bad!(), }; Ok(Some(their_version)) } fn concatenate_comments(comments: &[&str]) -> String { comments .iter() .map(|s| s.trim_matches('"')) .collect::>() .join("\n") } /// Do we need to generate JS glue shims for these incoming bindings? pub fn incoming_do_not_require_glue( exprs: &[NonstandardIncoming], from_webidl_tys: &[ast::WebidlTypeRef], to_wasm_tys: &[walrus::ValType], ) -> bool { exprs.len() == from_webidl_tys.len() && exprs.len() == to_wasm_tys.len() && exprs .iter() .zip(from_webidl_tys) .zip(to_wasm_tys) .enumerate() .all(|(i, ((expr, from_webidl_ty), to_wasm_ty))| match expr { NonstandardIncoming::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( *from_webidl_ty, *to_wasm_ty, i as u32, ), _ => false, }) } /// Do we need to generate JS glue shims for these outgoing bindings? pub fn outgoing_do_not_require_glue( exprs: &[NonstandardOutgoing], from_wasm_tys: &[walrus::ValType], to_webidl_tys: &[ast::WebidlTypeRef], ) -> bool { exprs.len() == from_wasm_tys.len() && exprs.len() == to_webidl_tys.len() && exprs .iter() .zip(from_wasm_tys) .zip(to_webidl_tys) .enumerate() .all(|(i, ((expr, from_wasm_ty), to_webidl_ty))| match expr { NonstandardOutgoing::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( *from_wasm_ty, *to_webidl_ty, i as u32, ), _ => false, }) }