diff --git a/crates/test-support/src/lib.rs b/crates/test-support/src/lib.rs index 58adab3b..ce6514a6 100644 --- a/crates/test-support/src/lib.rs +++ b/crates/test-support/src/lib.rs @@ -15,6 +15,7 @@ thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst)); pub struct Project { files: Vec<(String, String)>, debug: bool, + uglify: bool, } pub fn project() -> Project { @@ -27,6 +28,7 @@ pub fn project() -> Project { .read_to_string(&mut lockfile).unwrap(); Project { debug: true, + uglify: false, files: vec![ ("Cargo.toml".to_string(), format!(r#" [package] @@ -125,6 +127,11 @@ impl Project { self } + pub fn uglify(&mut self, uglify: bool) -> &mut Project { + self.uglify = uglify; + self + } + pub fn test(&mut self) { let root = root(); drop(fs::remove_dir_all(&root)); @@ -159,6 +166,7 @@ impl Project { .input_path(&out) .nodejs(true) .debug(self.debug) + .uglify_wasm_names(self.uglify) .generate() .expect("failed to run bindgen"); obj.write_ts_to(root.join("out.ts")).expect("failed to write ts"); diff --git a/crates/wasm-bindgen-cli-support/src/lib.rs b/crates/wasm-bindgen-cli-support/src/lib.rs index 90d7ca45..694fc098 100644 --- a/crates/wasm-bindgen-cli-support/src/lib.rs +++ b/crates/wasm-bindgen-cli-support/src/lib.rs @@ -4,23 +4,28 @@ extern crate parity_wasm; extern crate wasm_bindgen_shared as shared; extern crate serde_json; -use std::path::{Path, PathBuf}; +use std::collections::HashMap; use std::fs::File; use std::io::Write; +use std::path::{Path, PathBuf}; use failure::{Error, ResultExt}; use parity_wasm::elements::*; mod ts; +mod mapped; + +use mapped::Mapped; pub struct Bindgen { path: Option, nodejs: bool, debug: bool, + uglify: bool, } pub struct Object { - module: Module, + module: Mapped, program: shared::Program, nodejs: bool, debug: bool, @@ -32,6 +37,7 @@ impl Bindgen { path: None, nodejs: false, debug: false, + uglify: false, } } @@ -50,6 +56,11 @@ impl Bindgen { self } + pub fn uglify_wasm_names(&mut self, uglify: bool) -> &mut Bindgen { + self.uglify = uglify; + self + } + pub fn generate(&mut self) -> Result { let input = match self.path { Some(ref path) => path, @@ -59,8 +70,16 @@ impl Bindgen { format_err!("{:?}", e) })?; let program = extract_program(&mut module); - Ok(Object { + let mut mapped = Mapped { module, + imports: HashMap::new(), + exports: HashMap::new(), + }; + if self.uglify { + mapped.uglify(&program); + } + Ok(Object { + module: mapped, program, nodejs: self.nodejs, debug: self.debug, @@ -89,7 +108,7 @@ impl Object { } fn _write_wasm_to(self, path: &Path) -> Result<(), Error> { - parity_wasm::serialize_to_file(path, self.module).map_err(|e| { + parity_wasm::serialize_to_file(path, self.module.module).map_err(|e| { format_err!("{:?}", e) })?; Ok(()) diff --git a/crates/wasm-bindgen-cli-support/src/mapped.rs b/crates/wasm-bindgen-cli-support/src/mapped.rs new file mode 100644 index 00000000..c188f4a5 --- /dev/null +++ b/crates/wasm-bindgen-cli-support/src/mapped.rs @@ -0,0 +1,118 @@ +use std::collections::hash_map::{HashMap, Entry}; + +use parity_wasm::elements::*; +use shared; + +pub struct Mapped { + pub module: Module, + pub imports: HashMap, + pub exports: HashMap, +} + +impl Mapped { + pub fn export_name<'a>(&'a self, name: &'a str) -> &'a str { + self.exports.get(name).map(|s| &**s).unwrap_or(name) + } + + pub fn orig_export_name<'a>(&'a self, name: &'a str) -> &'a str { + self.exports.iter() + .find(|&(_k, v)| name == v) + .map(|p| &**p.0) + .unwrap_or(name) + } + + pub fn import_name<'a>(&'a self, name: &'a str) -> &'a str { + self.imports.get(name).map(|s| &**s).unwrap_or(name) + } + + pub fn orig_import_name<'a>(&'a self, name: &'a str) -> &'a str { + self.imports.iter() + .find(|&(_k, v)| name == v) + .map(|p| &**p.0) + .unwrap_or(name) + } + + pub fn uglify(&mut self, program: &shared::Program) { + let mut i = 0; + let mut generate = || { + let ret = generate(i); + i += 1; + return ret + }; + + for import in program.imports.iter() { + self.imports.insert(import.name.clone(), generate()); + } + + for f in program.free_functions.iter() { + self.exports.insert(f.free_function_export_name(), generate()); + } + + for s in program.structs.iter() { + for f in s.functions.iter() { + self.exports.insert(f.struct_function_export_name(&s.name), + generate()); + } + for f in s.methods.iter() { + self.exports.insert(f.function.struct_function_export_name(&s.name), + generate()); + } + } + + for section in self.module.sections_mut() { + match *section { + Section::Import(ref mut section) => { + for import in section.entries_mut() { + let new_name = match self.imports.entry(import.field().to_string()) { + Entry::Occupied(n) => n.into_mut(), + Entry::Vacant(v) => { + if !import.field().starts_with("__wbindgen") { + continue + } + v.insert(generate()) + } + }; + *import = ImportEntry::new( + import.module().to_string(), + new_name.to_string(), + import.external().clone(), + ); + } + } + Section::Export(ref mut e) => { + for e in e.entries_mut() { + let new_name = match self.exports.entry(e.field().to_string()) { + Entry::Occupied(n) => n.into_mut(), + Entry::Vacant(v) => { + if !e.field().starts_with("__wbindgen") { + continue + } + v.insert(generate()) + } + }; + *e = ExportEntry::new(new_name.to_string(), + e.internal().clone()); + } + } + _ => {} + } + } + } +} + +fn generate(mut i: usize) -> String { + const LETTERS: &str = "\ + abcdefghijklmnopqrstuvwxyz\ + ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + "; + let mut ret = String::new(); + loop { + let j = i % LETTERS.len(); + i /= LETTERS.len(); + ret.push(LETTERS.as_bytes()[j] as char); + if i == 0 { + break + } + } + return ret; +} diff --git a/crates/wasm-bindgen-cli-support/src/ts.rs b/crates/wasm-bindgen-cli-support/src/ts.rs index d7e1db9f..b22b634c 100644 --- a/crates/wasm-bindgen-cli-support/src/ts.rs +++ b/crates/wasm-bindgen-cli-support/src/ts.rs @@ -3,6 +3,8 @@ use std::collections::{HashSet, HashMap}; use shared; use parity_wasm::elements::*; +use super::Mapped; + #[derive(Default)] pub struct Js { globals: String, @@ -18,27 +20,32 @@ impl Js { pub fn generate_program(&mut self, program: &shared::Program, - _wasm: &Module) { + m: &Mapped) { for f in program.free_functions.iter() { - self.generate_free_function(f); + self.generate_free_function(f, m); } for s in program.structs.iter() { - self.generate_struct(s); + self.generate_struct(s, m); } } - pub fn generate_free_function(&mut self, func: &shared::Function) { + pub fn generate_free_function(&mut self, + func: &shared::Function, + m: &Mapped) { let (js, ts) = self.generate_function("function", &func.name, &func.name, false, &func.arguments, - func.ret.as_ref()); + func.ret.as_ref(), + m); self.exports.push((func.name.clone(), js, ts)); } - pub fn generate_struct(&mut self, s: &shared::Struct) { + pub fn generate_struct(&mut self, + s: &shared::Struct, + m: &Mapped) { let mut dst = String::new(); self.expose_wasm_exports(); dst.push_str(&format!(" @@ -63,7 +70,7 @@ impl Js { this.ptr = 0; wasm_exports.{}(ptr); }} - ", s.free_function())); + ", m.export_name(&s.free_function()))); self.wasm_exports_bound.insert(s.name.clone()); @@ -75,6 +82,7 @@ impl Js { false, &function.arguments, function.ret.as_ref(), + m, ); dst.push_str(&js); dst.push_str("\n"); @@ -87,6 +95,7 @@ impl Js { true, &method.function.arguments, method.function.ret.as_ref(), + m, ); dst.push_str(&js); dst.push_str("\n"); @@ -104,7 +113,8 @@ impl Js { wasm_name: &str, is_method: bool, arguments: &[shared::Type], - ret: Option<&shared::Type>) -> (String, String) { + ret: Option<&shared::Type>, + m: &Mapped) -> (String, String) { let mut dst = format!("{}(", name); let mut passed_args = String::new(); let mut arg_conversions = String::new(); @@ -151,7 +161,7 @@ impl Js { shared::Type::BorrowedStr | shared::Type::String => { dst.push_str("string"); - self.expose_pass_string_to_wasm(); + self.expose_pass_string_to_wasm(m); arg_conversions.push_str(&format!("\ const [ptr{i}, len{i}] = passStringToWasm({arg}); ", i = i, arg = name)); @@ -160,8 +170,8 @@ impl Js { if let shared::Type::BorrowedStr = *arg { self.expose_wasm_exports(); destructors.push_str(&format!("\n\ - wasm_exports.__wbindgen_free(ptr{i}, len{i});\n\ - ", i = i)); + wasm_exports.{free}(ptr{i}, len{i});\n\ + ", i = i, free = m.export_name("__wbindgen_free"))); } } shared::Type::ByRef(ref s) | @@ -203,7 +213,7 @@ impl Js { arg_conversions.push_str(&format!("\ const idx{i} = addBorrowedObject({arg}); ", i = i, arg = name)); - destructors.push_str("popBorrowedObject();\n"); + destructors.push_str("stack.pop();\n"); pass(&format!("idx{}", i)); } } @@ -248,12 +258,16 @@ impl Js { self.expose_get_string_from_wasm(); self.expose_wasm_exports(); format!(" - const ptr = wasm_exports.__wbindgen_boxed_str_ptr(ret); - const len = wasm_exports.__wbindgen_boxed_str_len(ret); + const ptr = wasm_exports.{}(ret); + const len = wasm_exports.{}(ret); const realRet = getStringFromWasm(ptr, len); - wasm_exports.__wbindgen_boxed_str_free(ret); + wasm_exports.{}(ret); return realRet; - ") + ", + m.export_name("__wbindgen_boxed_str_ptr"), + m.export_name("__wbindgen_boxed_str_len"), + m.export_name("__wbindgen_boxed_str_free"), + ) } }; let mut dst_ts = dst.clone(); @@ -265,7 +279,11 @@ impl Js { dst.push_str(&format!("\ const ret = wasm_exports.{f}({passed}); {convert_ret} - ", f = wasm_name, passed = passed_args, convert_ret = convert_ret)); + ", + f = m.export_name(wasm_name), + passed = passed_args, + convert_ret = convert_ret, + )); } else { dst.push_str(&format!("\ try {{ @@ -274,8 +292,12 @@ impl Js { }} finally {{ {destructors} }} - ", f = wasm_name, passed = passed_args, destructors = destructors, - convert_ret = convert_ret)); + ", + f = m.export_name(wasm_name), + passed = passed_args, + destructors = destructors, + convert_ret = convert_ret, + )); } dst.push_str("}"); self.wasm_exports_bound.insert(wasm_name.to_string()); @@ -374,7 +396,7 @@ impl Js { (dst, ts_dst) } - pub fn to_string(&mut self, m: &Module, program: &shared::Program) -> String { + pub fn to_string(&mut self, m: &Mapped, program: &shared::Program) -> String { if self.debug { self.expose_global_slab(); self.expose_global_stack(); @@ -398,7 +420,7 @@ impl Js { self.globals.push_str(class); self.globals.push_str("\n"); } - let wasm_exports = self.typescript_wasm_exports(m); + let wasm_exports = self.typescript_wasm_exports(&m.module); let mut exports_interface = String::new(); let mut extra_exports_interface = String::new(); let mut exports = format!("\ @@ -419,14 +441,16 @@ impl Js { // well. for (export, typescript) in wasm_exports.iter() { // ignore any internal functions we have for ourselves - if export.starts_with("__wbindgen") { + let orig_export = m.orig_export_name(export); + if orig_export.starts_with("__wbindgen") { continue } // Ignore anything we just bound above, - if self.wasm_exports_bound.contains(export) { + if self.wasm_exports_bound.contains(orig_export) { continue } + assert_eq!(orig_export, export); if extra_exports_interface.len() == 0 { extra_exports_interface.push_str("export interface ExtraExports {\n"); exports_interface.push_str("extra: ExtraExports;\n"); @@ -445,7 +469,7 @@ impl Js { exports.push_str("},\n"); } exports.push_str("}"); - let wasm_imports = self.typescript_wasm_imports(m); + let wasm_imports = self.typescript_wasm_imports(&m.module); let mut imports_object = String::new(); let mut extra_imports_interface = String::new(); @@ -456,12 +480,13 @@ impl Js { // the wasm module, an optimization pass at some point may have // ended up removing the code that needed the import, removing the // import. - if !wasm_imports.contains_key(&import.name) { + let name = m.import_name(&import.name); + if !wasm_imports.contains_key(name) { continue } - imports_bound.insert(import.name.clone()); + imports_bound.insert(name.to_string()); let (val, ts) = self.generate_import(import); - imports_object.push_str(&import.name); + imports_object.push_str(&name); imports_object.push_str(":"); imports_object.push_str(&val); imports_object.push_str(",\n"); @@ -474,7 +499,8 @@ impl Js { // well. for (import, typescript) in wasm_imports.iter() { // ignore any internal functions we have for ourselves - if import.starts_with("__wbindgen") { + let orig_import = m.orig_import_name(import); + if orig_import.starts_with("__wbindgen") { continue } // Ignore anything we just bound above, @@ -482,6 +508,7 @@ impl Js { continue } + assert_eq!(orig_import, import); if extra_imports_interface.len() == 0 { extra_imports_interface.push_str("export interface ExtraImports {\n"); imports_interface.push_str("env: ExtraImports;\n"); @@ -511,7 +538,7 @@ impl Js { String::from("(val as {cnt:number}).cnt += 1;") }; imports_object.push_str(&format!(" - __wbindgen_object_clone_ref: function(idx: number): number {{ + {}: function(idx: number): number {{ // If this object is on the stack promote it to the heap. if ((idx & 1) === 1) return addHeapObject(getObject(idx)); @@ -522,19 +549,24 @@ impl Js { {} return idx; }}, - ", bump_cnt)); + ", m.import_name("__wbindgen_object_clone_ref"), bump_cnt)); } if self.wasm_import_needed("__wbindgen_object_drop_ref", m) { self.expose_drop_ref(); - imports_object.push_str("__wbindgen_object_drop_ref: dropRef,\n"); + let name = m.import_name("__wbindgen_object_drop_ref"); + imports_object.push_str(&format!("{}: dropRef,\n", name)); } if self.wasm_import_needed("__wbindgen_throw", m) { self.expose_get_string_from_wasm(); - imports_object.push_str("__wbindgen_throw: function(ptr: number, len: number) { - throw new Error(getStringFromWasm(ptr, len)); - },\n"); + imports_object.push_str(&format!("\ + {}: function(ptr: number, len: number) {{ + throw new Error(getStringFromWasm(ptr, len)); + }}, + ", + m.import_name("__wbindgen_throw"), + )); } let mut writes = String::new(); @@ -608,14 +640,14 @@ impl Js { ) } - fn wasm_import_needed(&self, name: &str, m: &Module) -> bool { - let imports = match m.import_section() { + fn wasm_import_needed(&self, name: &str, m: &Mapped) -> bool { + let imports = match m.module.import_section() { Some(s) => s, None => return false, }; imports.entries().iter().any(|i| { - i.module() == "env" && i.field() == name + i.module() == "env" && i.field() == m.import_name(name) }) } @@ -883,37 +915,37 @@ impl Js { "); } - fn expose_pass_string_to_wasm(&mut self) { + fn expose_pass_string_to_wasm(&mut self, m: &Mapped) { if !self.exposed_globals.insert("pass_string_to_wasm") { return } self.expose_wasm_exports(); self.expose_global_memory(); if self.nodejs { - self.globals.push_str(" - function passStringToWasm(arg: string): [number, number] { + self.globals.push_str(&format!(" + function passStringToWasm(arg: string): [number, number] {{ if (typeof(arg) !== 'string') throw new Error('expected a string argument'); const buf = Buffer.from(arg); const len = buf.length; - const ptr = wasm_exports.__wbindgen_malloc(len); + const ptr = wasm_exports.{}(len); buf.copy(Buffer.from(memory.buffer), ptr); return [ptr, len]; - } - "); + }} + ", m.export_name("__wbindgen_malloc"))); } else { - self.globals.push_str(" - function passStringToWasm(arg: string): [number, number] { + self.globals.push_str(&format!(" + function passStringToWasm(arg: string): [number, number] {{ if (typeof(arg) !== 'string') throw new Error('expected a string argument'); const buf = new TextEncoder('utf-8').encode(arg); const len = buf.length; - const ptr = wasm_exports.__wbindgen_malloc(len); + const ptr = wasm_exports.{}(len); let array = new Uint8Array(memory.buffer); array.set(buf, ptr); return [ptr, len]; - } - "); + }} + ", m.export_name("__wbindgen_malloc"))); } } @@ -966,10 +998,6 @@ impl Js { stack.push(obj); return ((stack.length - 1) << 1) | 1; } - - function popBorrowedObject(): void { - stack.pop(); - } "); } diff --git a/crates/wasm-bindgen-cli/src/main.rs b/crates/wasm-bindgen-cli/src/main.rs index 05e13565..e18f148b 100644 --- a/crates/wasm-bindgen-cli/src/main.rs +++ b/crates/wasm-bindgen-cli/src/main.rs @@ -37,9 +37,10 @@ fn main() { .unwrap_or_else(|e| e.exit()); let mut b = Bindgen::new(); - b.input_path(&args.arg_input); - b.nodejs(args.flag_nodejs); - b.debug(args.flag_debug); + b.input_path(&args.arg_input) + .nodejs(args.flag_nodejs) + .debug(args.flag_debug) + .uglify_wasm_names(!args.flag_debug); let ret = b.generate().expect("failed to generate bindings"); if let Some(ref ts) = args.flag_output_ts { ret.write_ts_to(ts).expect("failed to write TypeScript output file"); diff --git a/tests/uglify.rs b/tests/uglify.rs new file mode 100644 index 00000000..06c66afc --- /dev/null +++ b/tests/uglify.rs @@ -0,0 +1,42 @@ +extern crate test_support; + +#[test] +fn works() { + test_support::project() + .uglify(true) + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + wasm_bindgen! { + pub struct A {} + + impl A { + pub fn new() -> A { + A {} + } + } + pub fn clone(a: &JsObject) -> JsObject { + drop(a.clone()); + a.clone() + } + } + "#) + .file("test.ts", r#" + import * as assert from "assert"; + import { Exports, Imports } from "./out"; + + export const imports: Imports = {}; + + export function test(wasm: Exports) { + let sym = Symbol('a'); + assert.strictEqual(wasm.clone(sym), sym); + let a = wasm.A.new(); + a.free(); + } + "#) + .test(); +}