diff --git a/.travis.yml b/.travis.yml index af01b278..70aedb88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,10 @@ matrix: rust: nightly before_script: rustup target add $TARGET script: cargo build --manifest-path crates/cli/Cargo.toml --release --target $TARGET + addons: + apt: + packages: + - musl-tools # Dist OSX binary - os: osx diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 28471927..39f4cf12 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -12,6 +12,7 @@ Shared support for the wasm-bindgen-cli package, an internal dependency [dependencies] base64 = "0.9" +failure = "0.1" parity-wasm = "0.27" serde_json = "1.0" wasm-bindgen-shared = { path = "../shared", version = '=0.2.5' } diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index 9e2aa01d..d4f9d739 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -1,3 +1,5 @@ +use failure::Error; + use super::{indent, Context}; use descriptor::{Descriptor, Function}; @@ -59,12 +61,12 @@ impl<'a, 'b> Js2Rust<'a, 'b> { /// Generates all bindings necessary for the signature in `Function`, /// creating necessary argument conversions and return value processing. - pub fn process(&mut self, function: &Function) -> &mut Self { + pub fn process(&mut self, function: &Function) -> Result<&mut Self, Error> { for arg in function.arguments.iter() { - self.argument(arg); + self.argument(arg)?; } - self.ret(&function.ret); - self + self.ret(&function.ret)?; + Ok(self) } /// Flag this shim as a method call into Rust, so the first Rust argument @@ -100,7 +102,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self } - pub fn argument(&mut self, arg: &Descriptor) -> &mut Self { + pub fn argument(&mut self, arg: &Descriptor) -> Result<&mut Self, Error> { let i = self.arg_idx; self.arg_idx += 1; let name = format!("arg{}", i); @@ -108,8 +110,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> { if let Some(kind) = arg.vector_kind() { self.js_arguments.push((name.clone(), kind.js_ty().to_string())); - let func = self.cx.pass_to_wasm_function(kind); - self.cx.expose_set_global_argument(); + let func = self.cx.pass_to_wasm_function(kind)?; + self.cx.expose_set_global_argument()?; let global_idx = self.global_idx(); self.prelude(&format!("\ const [ptr{i}, len{i}] = {func}({arg});\n\ @@ -119,10 +121,10 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.finally(&format!("\ wasm.__wbindgen_free(ptr{i}, len{i} * {size});\n\ ", i = i, size = kind.size())); - self.cx.require_internal_export("__wbindgen_free"); + self.cx.require_internal_export("__wbindgen_free")?; } self.rust_arguments.push(format!("ptr{}", i)); - return self + return Ok(self) } if let Some(s) = arg.rust_struct() { @@ -144,7 +146,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> { ", i = i, arg = name)); self.rust_arguments.push(format!("ptr{}", i)); } - return self + return Ok(self) } if arg.is_number() { @@ -156,7 +158,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> { } self.rust_arguments.push(name); - return self + return Ok(self) } if arg.is_ref_anyref() { @@ -164,7 +166,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.cx.expose_borrowed_objects(); self.finally("stack.pop();"); self.rust_arguments.push(format!("addBorrowedObject({})", name)); - return self + return Ok(self) } match *arg { @@ -184,19 +186,19 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.rust_arguments.push(format!("addHeapObject({})", name)); } _ => { - panic!("unsupported argument to rust function {:?}", arg) + bail!("unsupported argument to rust function {:?}", arg) } } - self + Ok(self) } - pub fn ret(&mut self, ret: &Option) -> &mut Self { + pub fn ret(&mut self, ret: &Option) -> Result<&mut Self, Error> { let ty = match *ret { Some(ref t) => t, None => { self.ret_ty = "void".to_string(); self.ret_expr = format!("return RET;"); - return self + return Ok(self) } }; @@ -204,18 +206,18 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.ret_ty = "any".to_string(); self.cx.expose_get_object(); self.ret_expr = format!("return getObject(RET);"); - return self + return Ok(self) } if ty.is_by_ref() { - panic!("cannot return references from Rust to JS yet") + bail!("cannot return references from Rust to JS yet") } if let Some(ty) = ty.vector_kind() { self.ret_ty = ty.js_ty().to_string(); let f = self.cx.expose_get_vector_from_wasm(ty); - self.cx.expose_get_global_argument(); - self.cx.require_internal_export("__wbindgen_free"); + self.cx.expose_get_global_argument()?; + self.cx.require_internal_export("__wbindgen_free")?; self.ret_expr = format!("\ const ret = RET;\n\ const len = getGlobalArgument(0);\n\ @@ -223,19 +225,19 @@ impl<'a, 'b> Js2Rust<'a, 'b> { wasm.__wbindgen_free(ret, len * {});\n\ return realRet;\n\ ", f, ty.size()); - return self + return Ok(self) } if let Some(name) = ty.rust_struct() { self.ret_ty = name.to_string(); self.ret_expr = format!("return {name}.__construct(RET);", name = name); - return self + return Ok(self) } if ty.is_number() { self.ret_ty = "number".to_string(); self.ret_expr = format!("return RET;"); - return self + return Ok(self) } match *ty { @@ -248,9 +250,9 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.cx.expose_take_object(); self.ret_expr = format!("return takeObject(RET);"); } - _ => panic!("unsupported return from Rust to JS {:?}", ty), + _ => bail!("unsupported return from Rust to JS {:?}", ty), } - self + Ok(self) } /// Generate the actual function. diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 6aa707b2..f77f54e1 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2,6 +2,7 @@ use std::collections::{HashSet, HashMap}; use std::fmt::Write; use std::mem; +use failure::{Error, ResultExt}; use parity_wasm::elements::*; use parity_wasm; use shared; @@ -68,33 +69,38 @@ impl<'a> Context<'a> { self.global(&global); } - fn require_internal_export(&mut self, name: &'static str) { + fn require_internal_export(&mut self, name: &'static str) + -> Result<(), Error> + { if !self.required_internal_exports.insert(name) { - return + return Ok(()) } if let Some(s) = self.module.export_section() { if s.entries().iter().any(|e| e.field() == name) { - return + return Ok(()) } } - panic!("\n\nthe exported function `{}` is required to generate bindings \ - but it was not found in the wasm file, perhaps the `std` feature \ - of the `wasm-bindgen` crate needs to be enabled?\n\n", - name); + bail!("\n\nthe exported function `{}` is required to generate bindings \ + but it was not found in the wasm file, perhaps the `std` feature \ + of the `wasm-bindgen` crate needs to be enabled?\n\n", + name); } - pub fn finalize(&mut self, module_name: &str) -> (String, String) { + pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> { self.unexport_unused_internal_exports(); - self.gc(); - self.write_classes(); + self.gc()?; + self.write_classes()?; { - let mut bind = |name: &str, f: &Fn(&mut Self) -> String| { + let mut bind = |name: &str, f: &Fn(&mut Self) -> Result| + -> Result<(), Error> + { if !self.wasm_import_needed(name) { - return; + return Ok(()); } - let contents = f(self); + let contents = f(self)?; self.export(name, &contents); + Ok(()) }; bind("__wbindgen_object_clone_ref", &|me| { @@ -109,7 +115,7 @@ impl<'a> Context<'a> { } else { String::from("val.cnt += 1;") }; - format!(" + Ok(format!(" function(idx) {{ // If this object is on the stack promote it to the heap. if ((idx & 1) === 1) @@ -121,33 +127,33 @@ impl<'a> Context<'a> { {} return idx; }} - ", bump_cnt) - }); + ", bump_cnt)) + })?; bind("__wbindgen_object_drop_ref", &|me| { me.expose_drop_ref(); - "function(i) { dropRef(i); }".to_string() - }); + Ok("function(i) { dropRef(i); }".to_string()) + })?; bind("__wbindgen_string_new", &|me| { me.expose_add_heap_object(); me.expose_get_string_from_wasm(); - String::from(" + Ok(String::from(" function(p, l) { return addHeapObject(getStringFromWasm(p, l)); } - ") - }); + ")) + })?; bind("__wbindgen_number_new", &|me| { me.expose_add_heap_object(); - String::from("function(i) { return addHeapObject(i); }") - }); + Ok(String::from("function(i) { return addHeapObject(i); }")) + })?; bind("__wbindgen_number_get", &|me| { me.expose_get_object(); me.expose_uint8_memory(); - format!(" + Ok(format!(" function(n, invalid) {{ let obj = getObject(n); if (typeof(obj) === 'number') @@ -155,53 +161,53 @@ impl<'a> Context<'a> { getUint8Memory()[invalid] = 1; return 0; }} - ") - }); + ")) + })?; bind("__wbindgen_undefined_new", &|me| { me.expose_add_heap_object(); - String::from("function() { return addHeapObject(undefined); }") - }); + Ok(String::from("function() { return addHeapObject(undefined); }")) + })?; bind("__wbindgen_null_new", &|me| { me.expose_add_heap_object(); - String::from(" + Ok(String::from(" function() { return addHeapObject(null); } - ") - }); + ")) + })?; bind("__wbindgen_is_null", &|me| { me.expose_get_object(); - String::from(" + Ok(String::from(" function(idx) { return getObject(idx) === null ? 1 : 0; } - ") - }); + ")) + })?; bind("__wbindgen_is_undefined", &|me| { me.expose_get_object(); - String::from(" + Ok(String::from(" function(idx) { return getObject(idx) === undefined ? 1 : 0; } - ") - }); + ")) + })?; bind("__wbindgen_boolean_new", &|me| { me.expose_add_heap_object(); - String::from(" + Ok(String::from(" function(v) { return addHeapObject(v === 1); } - ") - }); + ")) + })?; bind("__wbindgen_boolean_get", &|me| { me.expose_get_object(); - String::from(" + Ok(String::from(" function(i) { let v = getObject(i); if (typeof(v) === 'boolean') { @@ -210,13 +216,13 @@ impl<'a> Context<'a> { return 2; } } - ") - }); + ")) + })?; bind("__wbindgen_symbol_new", &|me| { me.expose_get_string_from_wasm(); me.expose_add_heap_object(); - format!(" + Ok(format!(" function(ptr, len) {{ let a; console.log(ptr, len); @@ -227,32 +233,32 @@ impl<'a> Context<'a> { }} return addHeapObject(a); }} - ") - }); + ")) + })?; bind("__wbindgen_is_symbol", &|me| { me.expose_get_object(); - String::from(" + Ok(String::from(" function(i) { return typeof(getObject(i)) === 'symbol' ? 1 : 0; } - ") - }); + ")) + })?; bind("__wbindgen_throw", &|me| { me.expose_get_string_from_wasm(); - format!(" + Ok(format!(" function(ptr, len) {{ throw new Error(getStringFromWasm(ptr, len)); }} - ") - }); + ")) + })?; bind("__wbindgen_string_get", &|me| { - me.expose_pass_string_to_wasm(); + me.expose_pass_string_to_wasm()?; me.expose_get_object(); me.expose_uint32_memory(); - String::from(" + Ok(String::from(" function(i, len_ptr) { let obj = getObject(i); if (typeof(obj) !== 'string') @@ -261,27 +267,27 @@ impl<'a> Context<'a> { getUint32Memory()[len_ptr / 4] = len; return ptr; } - ") - }); + ")) + })?; bind("__wbindgen_cb_drop", &|me| { me.expose_drop_ref(); - String::from(" + Ok(String::from(" function(i) { let obj = getObject(i).original; obj.a = obj.b = 0; dropRef(i); } - ") - }); + ")) + })?; bind("__wbindgen_cb_forget", &|me| { me.expose_drop_ref(); - String::from(" + Ok(String::from(" function(i) { dropRef(i); } - ") - }); + ")) + })?; } self.rewrite_imports(module_name); @@ -335,23 +341,24 @@ impl<'a> Context<'a> { }; self.export_table(); - self.gc(); + self.gc()?; while js.contains("\n\n\n") { js = js.replace("\n\n\n", "\n\n"); } - (js, self.typescript.clone()) + Ok((js, self.typescript.clone())) } - fn write_classes(&mut self) { + fn write_classes(&mut self) -> Result<(), Error> { let classes = mem::replace(&mut self.exported_classes, Default::default()); for (class, exports) in classes { - self.write_class(&class, &exports); + self.write_class(&class, &exports)?; } + Ok(()) } - fn write_class(&mut self, name: &str, class: &ExportedClass) { + fn write_class(&mut self, name: &str, class: &ExportedClass) -> Result<(), Error> { let mut dst = format!("class {} {{\n", name); let mut ts_dst = format!("export {}", dst); @@ -415,8 +422,8 @@ impl<'a> Context<'a> { let set = { let mut cx = Js2Rust::new(&field.name, self); cx.method(true) - .argument(&descriptor) - .ret(&None); + .argument(&descriptor)? + .ret(&None)?; ts_dst.push_str(&format!("{}{}: {}\n", if field.readonly { "readonly " } else { "" }, field.name, @@ -425,7 +432,7 @@ impl<'a> Context<'a> { }; let (get, _ts) = Js2Rust::new(&field.name, self) .method(true) - .ret(&Some(descriptor)) + .ret(&Some(descriptor))? .finish("", &format!("wasm.{}", wasm_getter)); dst.push_str("get "); @@ -455,6 +462,8 @@ impl<'a> Context<'a> { self.export(&name, &dst); self.typescript.push_str(&ts_dst); + + Ok(()) } fn export_table(&mut self) { @@ -697,11 +706,11 @@ impl<'a> Context<'a> { ")); } - fn expose_pass_string_to_wasm(&mut self) { + fn expose_pass_string_to_wasm(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("pass_string_to_wasm") { - return; + return Ok(()); } - self.require_internal_export("__wbindgen_malloc"); + self.require_internal_export("__wbindgen_malloc")?; self.expose_text_encoder(); self.expose_uint8_memory(); let debug = if self.config.debug { @@ -721,13 +730,14 @@ impl<'a> Context<'a> { return [ptr, buf.length]; }} ", debug)); + Ok(()) } - fn expose_pass_array8_to_wasm(&mut self) { + fn expose_pass_array8_to_wasm(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("pass_array8_to_wasm") { - return; + return Ok(()); } - self.require_internal_export("__wbindgen_malloc"); + self.require_internal_export("__wbindgen_malloc")?; self.expose_uint8_memory(); self.global(&format!(" function passArray8ToWasm(arg) {{ @@ -736,13 +746,14 @@ impl<'a> Context<'a> { return [ptr, arg.length]; }} ")); + Ok(()) } - fn expose_pass_array16_to_wasm(&mut self) { + fn expose_pass_array16_to_wasm(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("pass_array16_to_wasm") { - return; + return Ok(()); } - self.require_internal_export("__wbindgen_malloc"); + self.require_internal_export("__wbindgen_malloc")?; self.expose_uint16_memory(); self.global(&format!(" function passArray16ToWasm(arg) {{ @@ -751,13 +762,14 @@ impl<'a> Context<'a> { return [ptr, arg.length]; }} ")); + Ok(()) } - fn expose_pass_array32_to_wasm(&mut self) { + fn expose_pass_array32_to_wasm(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("pass_array32_to_wasm") { - return; + return Ok(()) } - self.require_internal_export("__wbindgen_malloc"); + self.require_internal_export("__wbindgen_malloc")?; self.expose_uint32_memory(); self.global(&format!(" function passArray32ToWasm(arg) {{ @@ -766,13 +778,14 @@ impl<'a> Context<'a> { return [ptr, arg.length]; }} ")); + Ok(()) } - fn expose_pass_array_f32_to_wasm(&mut self) { + fn expose_pass_array_f32_to_wasm(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("pass_array_f32_to_wasm") { - return; + return Ok(()) } - self.require_internal_export("__wbindgen_malloc"); + self.require_internal_export("__wbindgen_malloc")?; self.global(&format!(" function passArrayF32ToWasm(arg) {{ const ptr = wasm.__wbindgen_malloc(arg.length * 4); @@ -780,13 +793,14 @@ impl<'a> Context<'a> { return [ptr, arg.length]; }} ")); + Ok(()) } - fn expose_pass_array_f64_to_wasm(&mut self) { + fn expose_pass_array_f64_to_wasm(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("pass_array_f64_to_wasm") { - return; + return Ok(()) } - self.require_internal_export("__wbindgen_malloc"); + self.require_internal_export("__wbindgen_malloc")?; self.global(&format!(" function passArrayF64ToWasm(arg) {{ const ptr = wasm.__wbindgen_malloc(arg.length * 8); @@ -794,6 +808,7 @@ impl<'a> Context<'a> { return [ptr, arg.length]; }} ")); + Ok(()) } fn expose_text_encoder(&mut self) { @@ -1119,39 +1134,40 @@ impl<'a> Context<'a> { }) } - fn pass_to_wasm_function(&mut self, t: VectorKind) -> &'static str { - match t { + fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> { + let s = match t { VectorKind::String => { - self.expose_pass_string_to_wasm(); + self.expose_pass_string_to_wasm()?; "passStringToWasm" } VectorKind::I8 | VectorKind::U8 => { - self.expose_pass_array8_to_wasm(); + self.expose_pass_array8_to_wasm()?; "passArray8ToWasm" } VectorKind::U16 | VectorKind::I16 => { - self.expose_pass_array16_to_wasm(); + self.expose_pass_array16_to_wasm()?; "passArray16ToWasm" } VectorKind::I32 | VectorKind::U32 => { - self.expose_pass_array32_to_wasm(); + self.expose_pass_array32_to_wasm()?; "passArray32ToWasm" } VectorKind::F32 => { - self.expose_pass_array_f32_to_wasm(); + self.expose_pass_array_f32_to_wasm()?; "passArrayF32ToWasm" } VectorKind::F64 => { - self.expose_pass_array_f64_to_wasm(); + self.expose_pass_array_f64_to_wasm()?; "passArrayF64ToWasm" } VectorKind::Anyref => { - panic!("cannot pass list of JsValue to wasm yet") + bail!("cannot pass list of JsValue to wasm yet") } - } + }; + Ok(s) } fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> &'static str { @@ -1199,39 +1215,41 @@ impl<'a> Context<'a> { } } - fn expose_set_global_argument(&mut self) { + fn expose_set_global_argument(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("set_global_argument") { - return; + return Ok(()); } self.expose_uint32_memory(); - self.expose_global_argument_ptr(); + self.expose_global_argument_ptr()?; self.global(" function setGlobalArgument(arg, i) { const idx = globalArgumentPtr() / 4 + i; getUint32Memory()[idx] = arg; } "); + Ok(()) } - fn expose_get_global_argument(&mut self) { + fn expose_get_global_argument(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("get_global_argument") { - return; + return Ok(()); } self.expose_uint32_memory(); - self.expose_global_argument_ptr(); + self.expose_global_argument_ptr()?; self.global(" function getGlobalArgument(arg) { const idx = globalArgumentPtr() / 4 + arg; return getUint32Memory()[idx]; } "); + Ok(()) } - fn expose_global_argument_ptr(&mut self) { + fn expose_global_argument_ptr(&mut self) -> Result<(), Error> { if !self.exposed_globals.insert("global_argument_ptr") { - return; + return Ok(()); } - self.require_internal_export("__wbindgen_global_argument_ptr"); + self.require_internal_export("__wbindgen_global_argument_ptr")?; self.global(" let cachedGlobalArgumentPtr = null; function globalArgumentPtr() { @@ -1240,6 +1258,7 @@ impl<'a> Context<'a> { return cachedGlobalArgumentPtr; } "); + Ok(()) } fn expose_get_inherited_descriptor(&mut self) { @@ -1267,14 +1286,14 @@ impl<'a> Context<'a> { "); } - fn gc(&mut self) { + fn gc(&mut self) -> Result<(), Error> { let module = mem::replace(self.module, Module::default()); - let wasm_bytes = parity_wasm::serialize(module).unwrap(); + let wasm_bytes = parity_wasm::serialize(module)?; let bytes = wasm_gc::Config::new() .demangle(self.config.demangle) - .gc(&wasm_bytes) - .unwrap(); - *self.module = deserialize_buffer(&bytes).unwrap(); + .gc(&wasm_bytes)?; + *self.module = deserialize_buffer(&bytes)?; + Ok(()) } fn describe(&self, name: &str) -> Descriptor { @@ -1298,12 +1317,16 @@ impl<'a> Context<'a> { } impl<'a, 'b> SubContext<'a, 'b> { - pub fn generate(&mut self) { + pub fn generate(&mut self) -> Result<(), Error> { for f in self.program.exports.iter() { - self.generate_export(f); + self.generate_export(f) + .with_context(|_| { + format!("failed to generate bindings for Rust export `{}`", + f.function.name) + })?; } for f in self.program.imports.iter() { - self.generate_import(f); + self.generate_import(f)?; } for e in self.program.enums.iter() { self.generate_enum(e); @@ -1320,29 +1343,38 @@ impl<'a, 'b> SubContext<'a, 'b> { } })); } + + Ok(()) } - pub fn generate_export(&mut self, export: &shared::Export) { + fn generate_export(&mut self, export: &shared::Export) + -> Result<(), Error> + { if let Some(ref class) = export.class { return self.generate_export_for_class(class, export); } let descriptor = self.cx.describe(&export.function.name); let (js, ts) = Js2Rust::new(&export.function.name, self.cx) - .process(descriptor.unwrap_function()) + .process(descriptor.unwrap_function())? .finish("function", &format!("wasm.{}", export.function.name)); self.cx.export(&export.function.name, &js); self.cx.globals.push_str("\n"); self.cx.typescript.push_str("export "); self.cx.typescript.push_str(&ts); self.cx.typescript.push_str("\n"); + Ok(()) } - pub fn generate_export_for_class(&mut self, class_name: &str, export: &shared::Export) { + fn generate_export_for_class( + &mut self, + class_name: &str, + export: &shared::Export, + ) -> Result<(), Error> { let wasm_name = shared::struct_function_export_name(class_name, &export.function.name); let descriptor = self.cx.describe(&wasm_name); let (js, ts) = Js2Rust::new(&export.function.name, self.cx) .method(export.method) - .process(descriptor.unwrap_function()) + .process(descriptor.unwrap_function())? .finish("", &format!("wasm.{}", wasm_name)); let class = self.cx.exported_classes.entry(class_name.to_string()) .or_insert(ExportedClass::default()); @@ -1360,7 +1392,7 @@ impl<'a, 'b> SubContext<'a, 'b> { class.constructor = match constructors.len() { 0 => None, 1 => Some(constructors[0].clone()), - x @ _ => panic!("There must be only one constructor, not {}", x), + x @ _ => bail!("there must be only one constructor, not {}", x), }; class.contents.push_str(&export.function.name); @@ -1368,44 +1400,61 @@ impl<'a, 'b> SubContext<'a, 'b> { class.contents.push_str("\n"); class.typescript.push_str(&ts); class.typescript.push_str("\n"); + Ok(()) } - pub fn generate_import(&mut self, import: &shared::Import) { + fn generate_import(&mut self, import: &shared::Import) -> Result<(), Error> { match import.kind { shared::ImportKind::Function(ref f) => { self.generate_import_function(import, f) + .with_context(|_| { + format!("failed to generate bindings for JS import `{}`", + f.function.name) + })?; } shared::ImportKind::Static(ref s) => { self.generate_import_static(import, s) + .with_context(|_| { + format!("failed to generate bindings for JS import `{}`", + s.name) + })?; } shared::ImportKind::Type(_) => {} } + Ok(()) } - pub fn generate_import_static(&mut self, - info: &shared::Import, - import: &shared::ImportStatic) { + fn generate_import_static( + &mut self, + info: &shared::Import, + import: &shared::ImportStatic, + ) + -> Result<(), Error> + { // TODO: should support more types to import here - let obj = self.import_name(info, &import.name); + let obj = self.import_name(info, &import.name)?; self.cx.expose_add_heap_object(); self.cx.export(&import.shim, &format!(" function() {{ return addHeapObject({}); }} ", obj)); + Ok(()) } - pub fn generate_import_function(&mut self, - info: &shared::Import, - import: &shared::ImportFunction) { + fn generate_import_function(&mut self, + info: &shared::Import, + import: &shared::ImportFunction) + -> Result<(), Error> + { let descriptor = self.cx.describe(&import.shim); let target = match import.class { Some(ref class) if import.js_new => { - format!("new {}", self.import_name(info, class)) + format!("new {}", self.import_name(info, class)?) } Some(ref class) if import.method => { - let class = self.import_name(info, class); + let class = self.import_name(info, class)?; let target = if let Some(ref g) = import.getter { if import.structural { format!("function() {{ return this.{}; }}", g) @@ -1461,14 +1510,14 @@ impl<'a, 'b> SubContext<'a, 'b> { format!("{}_target.call", import.shim) } Some(ref class) => { - let class = self.import_name(info, class); + let class = self.import_name(info, class)?; self.cx.global(&format!(" const {}_target = {}.{}; ", import.shim, class, import.function.name)); format!("{}_target", import.shim) } None => { - let name = self.import_name(info, &import.function.name); + let name = self.import_name(info, &import.function.name)?; if name.contains(".") { self.cx.global(&format!(" const {}_target = {}; @@ -1482,12 +1531,13 @@ impl<'a, 'b> SubContext<'a, 'b> { let js = Rust2Js::new(self.cx) .catch(import.catch) - .process(descriptor.unwrap_function()) + .process(descriptor.unwrap_function())? .finish(&target); self.cx.export(&import.shim, &js); + Ok(()) } - pub fn generate_enum(&mut self, enum_: &shared::Enum) { + fn generate_enum(&mut self, enum_: &shared::Enum) { let mut variants = String::new(); for variant in enum_.variants.iter() { @@ -1504,10 +1554,13 @@ impl<'a, 'b> SubContext<'a, 'b> { self.cx.typescript.push_str("}\n"); } - fn import_name(&mut self, import: &shared::Import, item: &str) -> String { + fn import_name(&mut self, import: &shared::Import, item: &str) + -> Result + { if let Some(ref module) = import.module { if self.cx.config.no_modules { - panic!("import from `{}` module not allowed in `--no-modules`. use `--nodejs` or `--browser` instead", module); + bail!("import from `{}` module not allowed with `--no-modules`; \ + use `--nodejs` or `--browser` instead", module); } let name = import.js_namespace.as_ref().map(|s| &**s).unwrap_or(item); @@ -1524,10 +1577,10 @@ impl<'a, 'b> SubContext<'a, 'b> { } } } - match import.js_namespace { + Ok(match import.js_namespace { Some(ref s) => format!("{}.{}", s, item), None => item.to_string(), - } + }) } } diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index b3831a22..bd46778d 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1,7 +1,7 @@ -use super::Context; -use descriptor::{Descriptor, Function}; +use failure::Error; -use super::{indent, Js2Rust}; +use descriptor::{Descriptor, Function}; +use super::{indent, Context, Js2Rust}; /// Helper struct for manfuacturing a shim in JS used to translate Rust types to /// JS, then invoking an imported JS function. @@ -64,15 +64,15 @@ impl<'a, 'b> Rust2Js<'a, 'b> { /// Generates all bindings necessary for the signature in `Function`, /// creating necessary argument conversions and return value processing. - pub fn process(&mut self, function: &Function) -> &mut Self { + pub fn process(&mut self, function: &Function) -> Result<&mut Self, Error> { for arg in function.arguments.iter() { - self.argument(arg); + self.argument(arg)?; } - self.ret(&function.ret); - self + self.ret(&function.ret)?; + Ok(self) } - fn argument(&mut self, arg: &Descriptor) { + fn argument(&mut self, arg: &Descriptor) -> Result<(), Error> { let i = self.arg_idx; self.arg_idx += 1; @@ -80,7 +80,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { if let Some(ty) = arg.vector_kind() { let f = self.cx.expose_get_vector_from_wasm(ty); - self.cx.expose_get_global_argument(); + self.cx.expose_get_global_argument()?; let next_global = self.global_idx(); self.prelude(&format!("\ let len{0} = getGlobalArgument({next_global});\n\ @@ -91,20 +91,20 @@ impl<'a, 'b> Rust2Js<'a, 'b> { self.prelude(&format!("\ wasm.__wbindgen_free(arg{0}, len{0} * {size});\ ", i, size = ty.size())); - self.cx.require_internal_export("__wbindgen_free"); + self.cx.require_internal_export("__wbindgen_free")?; } self.js_arguments.push(format!("v{}", i)); - return + return Ok(()) } if let Some(class) = arg.rust_struct() { if arg.is_by_ref() { - panic!("cannot invoke JS functions with custom ref types yet") + bail!("cannot invoke JS functions with custom ref types yet") } let assign = format!("let c{0} = {1}.__construct(arg{0});", i, class); self.prelude(&assign); self.js_arguments.push(format!("c{}", i)); - return + return Ok(()) } if let Some((f, mutable)) = arg.stack_closure() { @@ -120,10 +120,10 @@ impl<'a, 'b> Rust2Js<'a, 'b> { } builder .rust_argument("this.b") - .process(f) + .process(f)? .finish("function", "this.f") }; - self.cx.expose_get_global_argument(); + self.cx.expose_get_global_argument()?; self.cx.function_table_needed = true; let next_global = self.global_idx(); self.global_idx(); @@ -135,7 +135,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ", i, js = js, next_global = next_global)); self.finally(&format!("cb{0}.a = cb{0}.b = 0;", i)); self.js_arguments.push(format!("cb{0}.bind(cb{0})", i)); - return + return Ok(()) } if let Some(closure) = arg.ref_closure() { @@ -151,10 +151,10 @@ impl<'a, 'b> Rust2Js<'a, 'b> { } builder .rust_argument("this.b") - .process(&closure.function) + .process(&closure.function)? .finish("function", "this.f") }; - self.cx.expose_get_global_argument(); + self.cx.expose_get_global_argument()?; self.cx.expose_uint32_memory(); self.cx.expose_add_heap_object(); self.cx.function_table_needed = true; @@ -181,7 +181,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ", i, indent(&reset_idx))); self.cx.expose_get_object(); self.js_arguments.push(format!("getObject(idx{})", i)); - return + return Ok(()) } let invoc_arg = match *arg { @@ -195,36 +195,37 @@ impl<'a, 'b> Rust2Js<'a, 'b> { self.cx.expose_get_object(); format!("getObject(arg{})", i) } - _ => panic!("unimplemented argument type in imported function: {:?}", arg), + _ => bail!("unimplemented argument type in imported function: {:?}", arg), }; self.js_arguments.push(invoc_arg); + Ok(()) } - fn ret(&mut self, ret: &Option) { + fn ret(&mut self, ret: &Option) -> Result<(), Error> { let ty = match *ret { Some(ref t) => t, None => { self.ret_expr = "JS;".to_string(); - return + return Ok(()) } }; if ty.is_by_ref() { - panic!("cannot return a reference from JS to Rust") + bail!("cannot return a reference from JS to Rust") } if let Some(ty) = ty.vector_kind() { - let f = self.cx.pass_to_wasm_function(ty); + let f = self.cx.pass_to_wasm_function(ty)?; self.cx.expose_uint32_memory(); - self.cx.expose_set_global_argument(); + self.cx.expose_set_global_argument()?; self.ret_expr = format!("\ const [retptr, retlen] = {}(JS);\n\ setGlobalArgument(retlen, 0);\n\ return retptr;\n\ ", f); - return + return Ok(()) } if ty.is_number() { self.ret_expr = "return JS;".to_string(); - return + return Ok(()) } self.ret_expr = match *ty { Descriptor::Boolean => "return JS ? 1 : 0;".to_string(), @@ -232,8 +233,9 @@ impl<'a, 'b> Rust2Js<'a, 'b> { self.cx.expose_add_heap_object(); "return addHeapObject(JS);".to_string() } - _ => panic!("unimplemented return from JS to Rust: {:?}", ty), - } + _ => bail!("unimplemented return from JS to Rust: {:?}", ty), + }; + Ok(()) } pub fn finish(&self, invoc: &str) -> String { diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index bca88528..29f7dafc 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -3,6 +3,8 @@ extern crate wasm_bindgen_shared as shared; extern crate serde_json; extern crate wasm_gc; extern crate wasmi; +#[macro_use] +extern crate failure; use std::collections::BTreeSet; use std::fmt; @@ -10,6 +12,7 @@ use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; +use failure::{Error, ResultExt}; use parity_wasm::elements::*; mod js; @@ -27,15 +30,6 @@ pub struct Bindgen { demangle: bool, } -#[derive(Debug)] -pub struct Error(String); - -impl From for Error { - fn from(e: E) -> Error { - Error(e.to_string()) - } -} - impl Bindgen { pub fn new() -> Bindgen { Bindgen { @@ -97,11 +91,13 @@ impl Bindgen { fn _generate(&mut self, out_dir: &Path) -> Result<(), Error> { let input = match self.path { Some(ref path) => path, - None => panic!("must have a path input for now"), + None => bail!("must have a path input for now"), }; let stem = input.file_stem().unwrap().to_str().unwrap(); - let mut module = parity_wasm::deserialize_file(input)?; - let programs = extract_programs(&mut module); + let mut module = parity_wasm::deserialize_file(input) + .with_context(|_| "failed to parse input file as wasm")?; + let programs = extract_programs(&mut module) + .with_context(|_| "failed to extract wasm-bindgen custom sections")?; // Here we're actually instantiating the module we've parsed above for // execution. Why, you might be asking, are we executing wasm code? A @@ -116,8 +112,10 @@ impl Bindgen { // This means that whenever we encounter an import or export we'll // execute a shim function which informs us about its type so we can // then generate the appropriate bindings. - let instance = wasmi::Module::from_parity_wasm_module(module.clone())?; - let instance = wasmi::ModuleInstance::new(&instance, &MyResolver)?; + let instance = wasmi::Module::from_parity_wasm_module(module.clone()) + .with_context(|_| "failed to create wasmi module")?; + let instance = wasmi::ModuleInstance::new(&instance, &MyResolver) + .with_context(|_| "failed to instantiate wasm module")?; let instance = instance.not_started_instance(); let (js, ts) = { @@ -146,19 +144,21 @@ impl Bindgen { js::SubContext { program, cx: &mut cx, - }.generate(); + }.generate()?; } - cx.finalize(stem) + cx.finalize(stem)? }; let js_path = out_dir.join(stem).with_extension("js"); - File::create(&js_path).unwrap() - .write_all(js.as_bytes()).unwrap(); + File::create(&js_path) + .and_then(|mut f| f.write_all(js.as_bytes())) + .with_context(|_| format!("failed to write `{}`", js_path.display()))?; if self.typescript { let ts_path = out_dir.join(stem).with_extension("d.ts"); - File::create(&ts_path).unwrap() - .write_all(ts.as_bytes()).unwrap(); + File::create(&ts_path) + .and_then(|mut f| f.write_all(ts.as_bytes())) + .with_context(|_| format!("failed to write `{}`", ts_path.display()))?; } let wasm_path = out_dir.join(format!("{}_bg", stem)).with_extension("wasm"); @@ -166,13 +166,15 @@ impl Bindgen { if self.nodejs { let js_path = wasm_path.with_extension("js"); let shim = self.generate_node_wasm_import(&module, &wasm_path); - File::create(&js_path)?.write_all(shim.as_bytes())?; + File::create(&js_path) + .and_then(|mut f| f.write_all(shim.as_bytes())) + .with_context(|_| format!("failed to write `{}`", js_path.display()))?; } - let wasm_bytes = parity_wasm::serialize(module).map_err(|e| { - Error(format!("{:?}", e)) - })?; - File::create(&wasm_path)?.write_all(&wasm_bytes)?; + let wasm_bytes = parity_wasm::serialize(module)?; + File::create(&wasm_path) + .and_then(|mut f| f.write_all(&wasm_bytes)) + .with_context(|_| format!("failed to write `{}`", wasm_path.display()))?; Ok(()) } @@ -202,18 +204,20 @@ impl Bindgen { } } -fn extract_programs(module: &mut Module) -> Vec { +fn extract_programs(module: &mut Module) -> Result, Error> { let version = shared::version(); let mut ret = Vec::new(); + let mut to_remove = Vec::new(); - module.sections_mut().retain(|s| { + for (i, s) in module.sections().iter().enumerate() { let custom = match *s { Section::Custom(ref s) => s, - _ => return true, + _ => continue, }; if custom.name() != "__wasm_bindgen_unstable" { - return true + continue } + to_remove.push(i); let mut payload = custom.payload(); while payload.len() > 0 { @@ -227,11 +231,11 @@ fn extract_programs(module: &mut Module) -> Vec { let p: shared::ProgramOnlySchema = match serde_json::from_slice(&a) { Ok(f) => f, Err(e) => { - panic!("failed to decode what looked like wasm-bindgen data: {}", e) + bail!("failed to decode what looked like wasm-bindgen data: {}", e) } }; if p.schema_version != shared::SCHEMA_VERSION { - panic!(" + 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: @@ -258,15 +262,17 @@ to open an issue at https://github.com/alexcrichton/wasm-bindgen/issues! let p: shared::Program = match serde_json::from_slice(&a) { Ok(f) => f, Err(e) => { - panic!("failed to decode what looked like wasm-bindgen data: {}", e) + bail!("failed to decode what looked like wasm-bindgen data: {}", e) } }; ret.push(p); } + } - false - }); - return ret + for i in to_remove.into_iter().rev() { + module.sections_mut().remove(i); + } + Ok(ret) } struct MyResolver; diff --git a/crates/cli-support/src/wasm2es6js.rs b/crates/cli-support/src/wasm2es6js.rs index 11c39e11..7d92b9bd 100644 --- a/crates/cli-support/src/wasm2es6js.rs +++ b/crates/cli-support/src/wasm2es6js.rs @@ -3,8 +3,7 @@ extern crate base64; use std::collections::HashSet; use parity_wasm::elements::*; - -use super::Error; +use failure::Error; pub struct Config { base64: bool, @@ -37,11 +36,9 @@ impl Config { pub fn generate(&mut self, wasm: &[u8]) -> Result { if !self.base64 && !self.fetch_path.is_some() { - panic!("the option --base64 or --fetch is required"); + bail!("the option --base64 or --fetch is required"); } - let module = deserialize_buffer(wasm).map_err(|e| { - ::Error(format!("{:?}", e)) - })?; + let module = deserialize_buffer(wasm)?; Ok(Output { module, base64: self.base64, @@ -110,7 +107,7 @@ impl Output { return exports } - pub fn js(self) -> String { + pub fn js(self) -> Result { let mut js_imports = String::new(); let mut exports = String::new(); let mut imports = String::new(); @@ -122,13 +119,13 @@ impl Output { match *entry.external() { External::Function(_) => {} External::Table(_) => { - panic!("wasm imports a table which isn't supported yet"); + bail!("wasm imports a table which isn't supported yet"); } External::Memory(_) => { - panic!("wasm imports memory which isn't supported yet"); + bail!("wasm imports memory which isn't supported yet"); } External::Global(_) => { - panic!("wasm imports globals which aren't supported yet"); + bail!("wasm imports globals which aren't supported yet"); } } @@ -221,9 +218,9 @@ impl Output { .then(bytes => {inst})", path = path, inst = inst) ) } else { - panic!("the option --base64 or --fetch is required"); + bail!("the option --base64 or --fetch is required"); }; - format!(" + Ok(format!(" {js_imports} let wasm; {bytes} @@ -236,6 +233,6 @@ impl Output { js_imports = js_imports, exports = exports, mem_export = if export_mem { "export let memory;" } else { "" }, - ) + )) } } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index b402a270..54321b14 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,6 +14,7 @@ information see https://github.com/alexcrichton/wasm-bindgen. [dependencies] docopt = "0.8" +failure = "0.1" parity-wasm = "0.27" serde = "1.0" serde_derive = "1.0" diff --git a/crates/cli/src/bin/wasm-bindgen.rs b/crates/cli/src/bin/wasm-bindgen.rs index 695ed9c3..02974770 100644 --- a/crates/cli/src/bin/wasm-bindgen.rs +++ b/crates/cli/src/bin/wasm-bindgen.rs @@ -3,11 +3,15 @@ extern crate wasm_bindgen_cli_support; extern crate serde_derive; extern crate docopt; extern crate wasm_bindgen_shared; +#[macro_use] +extern crate failure; use std::path::PathBuf; +use std::process; use docopt::Docopt; use wasm_bindgen_cli_support::Bindgen; +use failure::Error; const USAGE: &'static str = " Generating JS bindings for a wasm file @@ -53,14 +57,25 @@ fn main() { println!("wasm-bindgen {}", wasm_bindgen_shared::version()); return; } + let err = match rmain(&args) { + Ok(()) => return, + Err(e) => e, + }; + println!("error: {}", err); + for cause in err.causes().skip(1) { + println!("\tcaused by: {}", cause); + } + process::exit(1); +} +fn rmain(args: &Args) -> Result<(), Error> { let input = match args.arg_input { - Some(s) => s, - None => panic!("input file expected"), + Some(ref s) => s, + None => bail!("input file expected"), }; let mut b = Bindgen::new(); - b.input_path(&input) + b.input_path(input) .nodejs(args.flag_nodejs) .browser(args.flag_browser) .no_modules(args.flag_no_modules) @@ -73,8 +88,8 @@ fn main() { let out_dir = match args.flag_out_dir { Some(ref p) => p, - None => panic!("the `--out-dir` argument is now required"), + None => bail!("the `--out-dir` argument is now required"), }; - b.generate(out_dir).expect("failed to generate bindings"); + b.generate(out_dir) } diff --git a/crates/cli/src/bin/wasm2es6js.rs b/crates/cli/src/bin/wasm2es6js.rs index f473cab4..bb67f072 100644 --- a/crates/cli/src/bin/wasm2es6js.rs +++ b/crates/cli/src/bin/wasm2es6js.rs @@ -3,12 +3,16 @@ extern crate serde_derive; extern crate docopt; extern crate parity_wasm; extern crate wasm_bindgen_cli_support; +#[macro_use] +extern crate failure; use std::fs::File; use std::io::{Write, Read}; use std::path::PathBuf; +use std::process; use docopt::Docopt; +use failure::{Error, ResultExt}; const USAGE: &'static str = " Converts a wasm file to an ES6 JS module @@ -42,38 +46,56 @@ fn main() { let args: Args = Docopt::new(USAGE) .and_then(|d| d.deserialize()) .unwrap_or_else(|e| e.exit()); + let err = match rmain(&args) { + Ok(()) => return, + Err(e) => e, + }; + println!("error: {}", err); + for cause in err.causes().skip(1) { + println!("\tcaused by: {}", cause); + } + process::exit(1); +} +fn rmain(args: &Args) -> Result<(), Error> { if !args.flag_base64 && !args.flag_fetch.is_some() { - panic!("unfortunately only works right now with base64 or fetch"); + bail!("unfortunately only works right now with base64 or fetch"); } let mut wasm = Vec::new(); - File::open(&args.arg_input).expect("failed to open input") - .read_to_end(&mut wasm).expect("failed to read input"); + File::open(&args.arg_input) + .and_then(|mut f| f.read_to_end(&mut wasm)) + .with_context(|_| format!("failed to read `{}`", args.arg_input.display()))?; let object = wasm_bindgen_cli_support::wasm2es6js::Config::new() .base64(args.flag_base64) - .fetch(args.flag_fetch) - .generate(&wasm) - .expect("failed to parse wasm"); + .fetch(args.flag_fetch.clone()) + .generate(&wasm)?; if args.flag_typescript { if let Some(ref p) = args.flag_output { let dst = p.with_extension("d.ts"); - File::create(dst).expect("failed to create output") - .write_all(object.typescript().as_bytes()).expect("failed to write output"); + let ts = object.typescript(); + File::create(&dst) + .and_then(|mut f| f.write_all(ts.as_bytes())) + .with_context(|_| { + format!("failed to write `{}`", dst.display()) + })?; } } - let js = object.js(); + let js = object.js()?; match args.flag_output { Some(ref p) => { - File::create(p).expect("failed to create output") - .write_all(js.as_bytes()).expect("failed to write output"); + File::create(p) + .and_then(|mut f| f.write_all(js.as_bytes())) + .with_context(|_| format!("failed to write `{}`", p.display()))?; } None => { println!("{}", js); } } + + Ok(()) }