diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index fee7a42f..fe595ed9 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -19,6 +19,5 @@ lazy_static = "1.0.0" log = "0.4" proc-macro2 = "0.4.8" quote = '0.6' -serde_json = "1.0" -syn = { version = '0.15', features = ['full', 'visit'] } +syn = { version = '0.15', features = ['full'] } wasm-bindgen-shared = { path = "../shared", version = "=0.2.25" } diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index b4d58cf3..ff6f3db1 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -1,9 +1,8 @@ +use Diagnostic; use proc_macro2::{Ident, Span}; use shared; use syn; -use Diagnostic; - /// An abstract syntax tree representing a rust program. Contains /// extra information for joining up this rust code with javascript. #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq))] @@ -260,36 +259,6 @@ pub struct DictionaryField { pub ty: syn::Type, } -impl Program { - pub(crate) fn shared(&self) -> Result { - let mut errors = Vec::new(); - let mut imports = Vec::new(); - for import in self.imports.iter() { - match import.shared() { - Ok(i) => imports.push(i), - Err(e) => errors.push(e), - } - } - Diagnostic::from_vec(errors)?; - Ok(shared::Program { - exports: self.exports.iter().map(|a| a.shared()).collect(), - structs: self.structs.iter().map(|a| a.shared()).collect(), - enums: self.enums.iter().map(|a| a.shared()).collect(), - imports, - version: shared::version(), - schema_version: shared::SCHEMA_VERSION.to_string(), - }) - } -} - -impl Function { - fn shared(&self) -> shared::Function { - shared::Function { - name: self.name.to_string(), - } - } -} - impl Export { /// Mangles a rust -> javascript export, so that the created Ident will be unique over function /// name and class name, if the function belongs to a javascript class. @@ -314,51 +283,6 @@ impl Export { None => shared::free_function_export_name(&fn_name), } } - - fn shared(&self) -> shared::Export { - let (method, consumed) = match self.method_self { - Some(MethodSelf::ByValue) => (true, true), - Some(_) => (true, false), - None => (false, false), - }; - shared::Export { - class: self.class.as_ref().map(|s| s.to_string()), - method, - consumed, - is_constructor: self.is_constructor, - function: self.function.shared(), - comments: self.comments.clone(), - } - } -} - -impl Enum { - fn shared(&self) -> shared::Enum { - shared::Enum { - name: self.name.to_string(), - variants: self.variants.iter().map(|v| v.shared()).collect(), - comments: self.comments.clone(), - } - } -} - -impl Variant { - fn shared(&self) -> shared::EnumVariant { - shared::EnumVariant { - name: self.name.to_string(), - value: self.value, - } - } -} - -impl Import { - fn shared(&self) -> Result { - Ok(shared::Import { - module: self.module.clone(), - js_namespace: self.js_namespace.as_ref().map(|s| s.to_string()), - kind: self.kind.shared()?, - }) - } } impl ImportKind { @@ -371,27 +295,18 @@ impl ImportKind { ImportKind::Enum(_) => false, } } - - fn shared(&self) -> Result { - Ok(match *self { - ImportKind::Function(ref f) => shared::ImportKind::Function(f.shared()?), - ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()), - ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()), - ImportKind::Enum(ref f) => shared::ImportKind::Enum(f.shared()), - }) - } } impl ImportFunction { /// If the rust object has a `fn xxx(&self) -> MyType` method, get the name for a getter in /// javascript (in this case `xxx`, so you can write `val = obj.xxx`) - fn infer_getter_property(&self) -> String { - self.function.name.to_string() + pub fn infer_getter_property(&self) -> &str { + &self.function.name } /// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name /// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`) - fn infer_setter_property(&self) -> Result { + pub fn infer_setter_property(&self) -> Result { let name = self.function.name.to_string(); // if `#[wasm_bindgen(js_name = "...")]` is used then that explicitly @@ -410,102 +325,4 @@ impl ImportFunction { } Ok(name[4..].to_string()) } - - fn shared(&self) -> Result { - let shared_operation = |operation: &Operation| -> Result<_, Diagnostic> { - let is_static = operation.is_static; - let kind = match &operation.kind { - OperationKind::Regular => shared::OperationKind::Regular, - OperationKind::Getter(g) => { - let g = g.as_ref().map(|g| g.to_string()); - shared::OperationKind::Getter(g.unwrap_or_else(|| self.infer_getter_property())) - } - OperationKind::Setter(s) => { - let s = s.as_ref().map(|s| s.to_string()); - shared::OperationKind::Setter(match s { - Some(s) => s, - None => self.infer_setter_property()?, - }) - } - OperationKind::IndexingGetter => shared::OperationKind::IndexingGetter, - OperationKind::IndexingSetter => shared::OperationKind::IndexingSetter, - OperationKind::IndexingDeleter => shared::OperationKind::IndexingDeleter, - }; - Ok(shared::Operation { is_static, kind }) - }; - - let method = match self.kind { - ImportFunctionKind::Method { - ref class, - ref kind, - .. - } => { - let kind = match kind { - MethodKind::Constructor => shared::MethodKind::Constructor, - MethodKind::Operation(op) => { - shared::MethodKind::Operation(shared_operation(op)?) - } - }; - Some(shared::MethodData { - class: class.clone(), - kind, - }) - } - ImportFunctionKind::Normal => None, - }; - - Ok(shared::ImportFunction { - shim: self.shim.to_string(), - catch: self.catch, - variadic: self.variadic, - method, - structural: self.structural, - function: self.function.shared(), - }) - } -} - -impl ImportStatic { - fn shared(&self) -> shared::ImportStatic { - shared::ImportStatic { - name: self.js_name.to_string(), - shim: self.shim.to_string(), - } - } -} - -impl ImportType { - fn shared(&self) -> shared::ImportType { - shared::ImportType { - name: self.js_name.clone(), - instanceof_shim: self.instanceof_shim.clone(), - vendor_prefixes: self.vendor_prefixes.iter().map(|s| s.to_string()).collect(), - } - } -} - -impl ImportEnum { - fn shared(&self) -> shared::ImportEnum { - shared::ImportEnum {} - } -} - -impl Struct { - fn shared(&self) -> shared::Struct { - shared::Struct { - name: self.name.to_string(), - fields: self.fields.iter().map(|s| s.shared()).collect(), - comments: self.comments.clone(), - } - } -} - -impl StructField { - fn shared(&self) -> shared::StructField { - shared::StructField { - name: self.name.to_string(), - readonly: self.readonly, - comments: self.comments.clone(), - } - } } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 18143cb1..c9d1f63c 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -1,14 +1,14 @@ use std::collections::HashSet; -use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; use std::sync::Mutex; +use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::ToTokens; -use serde_json; use shared; use syn; use ast; +use encode; use util::ShortHash; use Diagnostic; @@ -87,20 +87,20 @@ impl TryToTokens for ast::Program { ); let generated_static_name = Ident::new(&generated_static_name, Span::call_site()); - let description = serde_json::to_string(&self.shared()?).unwrap(); + // See comments in `crates/cli-support/src/lib.rs` about what this + // `schema_version` is. + let prefix_json = format!(r#"{{"schema_version":"{}","version":"{}"}}"#, + shared::SCHEMA_VERSION, + shared::version()); + let mut bytes = Vec::new(); + bytes.push((prefix_json.len() >> 0) as u8); + bytes.push((prefix_json.len() >> 8) as u8); + bytes.push((prefix_json.len() >> 16) as u8); + bytes.push((prefix_json.len() >> 24) as u8); + bytes.extend_from_slice(prefix_json.as_bytes()); + bytes.extend_from_slice(&encode::encode(self)?); - // Each JSON blob is prepended with the length of the JSON blob so when - // all these sections are concatenated in the final wasm file we know - // how to extract all the JSON pieces, so insert the byte length here. - // The value is little-endian. - let generated_static_length = description.len() + 4; - let mut bytes = vec![ - (description.len() >> 0) as u8, - (description.len() >> 8) as u8, - (description.len() >> 16) as u8, - (description.len() >> 24) as u8, - ]; - bytes.extend_from_slice(description.as_bytes()); + let generated_static_length = bytes.len(); let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site()); (quote! { diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs new file mode 100644 index 00000000..04d92436 --- /dev/null +++ b/crates/backend/src/encode.rs @@ -0,0 +1,368 @@ +use std::cell::RefCell; +use std::collections::HashMap; + +use proc_macro2::{Ident, Span}; + +use Diagnostic; +use ast; + +pub fn encode(program: &ast::Program) -> Result, Diagnostic> { + let mut e = Encoder::new(); + let i = Interner::new(); + shared_program(program, &i)?.encode(&mut e); + Ok(e.finish()) +} + +struct Interner { + map: RefCell>, +} + +impl Interner { + fn new() -> Interner { + Interner { map: RefCell::new(HashMap::new()) } + } + + fn intern(&self, s: &Ident) -> &str { + let mut map = self.map.borrow_mut(); + if let Some(s) = map.get(s) { + return unsafe { &*(&**s as *const str) } + } + map.insert(s.clone(), s.to_string()); + unsafe { &*(&*map[s] as *const str) } + } + + fn intern_str(&self, s: &str) -> &str { + self.intern(&Ident::new(s, Span::call_site())) + } +} + +fn shared_program<'a>(prog: &'a ast::Program, intern: &'a Interner) + -> Result, Diagnostic> +{ + Ok(Program { + exports: prog.exports.iter().map(|a| shared_export(a, intern)).collect(), + structs: prog.structs.iter().map(|a| shared_struct(a, intern)).collect(), + enums: prog.enums.iter().map(|a| shared_enum(a, intern)).collect(), + imports: prog.imports.iter() + .map(|a| shared_import(a, intern)) + .collect::, _>>()?, + // version: shared::version(), + // schema_version: shared::SCHEMA_VERSION.to_string(), + }) +} + +fn shared_export<'a>(export: &'a ast::Export, intern: &'a Interner) -> Export<'a> { + let (method, consumed) = match export.method_self { + Some(ast::MethodSelf::ByValue) => (true, true), + Some(_) => (true, false), + None => (false, false), + }; + Export { + class: export.class.as_ref().map(|s| intern.intern(s)), + method, + consumed, + is_constructor: export.is_constructor, + function: shared_function(&export.function, intern), + comments: export.comments.iter().map(|s| &**s).collect(), + } +} + +fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> { + Function { + name: &func.name, + } +} + +fn shared_enum<'a>(e: &'a ast::Enum, intern: &'a Interner) -> Enum<'a> { + Enum { + name: intern.intern(&e.name), + variants: e.variants.iter().map(|v| shared_variant(v, intern)).collect(), + comments: e.comments.iter().map(|s| &**s).collect(), + } +} + +fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<'a> { + EnumVariant { + name: intern.intern(&v.name), + value: v.value, + } +} + +fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) + -> Result, Diagnostic> +{ + Ok(Import { + module: i.module.as_ref().map(|s| &**s), + js_namespace: i.js_namespace.as_ref().map(|s| intern.intern(s)), + kind: shared_import_kind(&i.kind, intern)?, + }) +} + +fn shared_import_kind<'a>(i: &'a ast::ImportKind, intern: &'a Interner) + -> Result, Diagnostic> +{ + Ok(match i { + ast::ImportKind::Function(f) => ImportKind::Function(shared_import_function(f, intern)?), + ast::ImportKind::Static(f) => ImportKind::Static(shared_import_static(f, intern)), + ast::ImportKind::Type(f) => ImportKind::Type(shared_import_type(f, intern)), + ast::ImportKind::Enum(f) => ImportKind::Enum(shared_import_enum(f, intern)), + }) +} + +fn shared_import_function<'a>(i: &'a ast::ImportFunction, intern: &'a Interner) + -> Result, Diagnostic> +{ + let method = match &i.kind { + ast::ImportFunctionKind::Method { class, kind, .. } => { + let kind = match kind { + ast::MethodKind::Constructor => MethodKind::Constructor, + ast::MethodKind::Operation(ast::Operation { is_static, kind }) => { + let is_static = *is_static; + let kind = match kind { + ast::OperationKind::Regular => OperationKind::Regular, + ast::OperationKind::Getter(g) => { + let g = g.as_ref().map(|g| intern.intern(g)); + OperationKind::Getter( + g.unwrap_or_else(|| i.infer_getter_property()), + ) + } + ast::OperationKind::Setter(s) => { + let s = s.as_ref().map(|s| intern.intern(s)); + OperationKind::Setter(match s { + Some(s) => s, + None => intern.intern_str(&i.infer_setter_property()?), + }) + } + ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter, + ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter, + ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter, + }; + MethodKind::Operation(Operation { is_static, kind }) + } + }; + Some(MethodData { + class, + kind, + }) + } + ast::ImportFunctionKind::Normal => None, + }; + + Ok(ImportFunction { + shim: intern.intern(&i.shim), + catch: i.catch, + method, + structural: i.structural, + function: shared_function(&i.function, intern), + variadic: i.variadic, + }) +} + +fn shared_import_static<'a>(i: &'a ast::ImportStatic, intern: &'a Interner) + -> ImportStatic<'a> +{ + ImportStatic { + name: &i.js_name, + shim: intern.intern(&i.shim), + } +} + +fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) + -> ImportType<'a> +{ + ImportType { + name: &i.js_name, + instanceof_shim: &i.instanceof_shim, + vendor_prefixes: i.vendor_prefixes.iter() + .map(|x| intern.intern(x)) + .collect(), + } +} + +fn shared_import_enum<'a>(_i: &'a ast::ImportEnum, _intern: &'a Interner) + -> ImportEnum +{ + ImportEnum {} +} + +fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> { + Struct { + name: intern.intern(&s.name), + fields: s.fields.iter().map(|s| shared_struct_field(s, intern)).collect(), + comments: s.comments.iter().map(|s| &**s).collect(), + } +} + +fn shared_struct_field<'a>(s: &'a ast::StructField, intern: &'a Interner) + -> StructField<'a> +{ + StructField { + name: intern.intern(&s.name), + readonly: s.readonly, + comments: s.comments.iter().map(|s| &**s).collect(), + } +} + +trait Encode { + fn encode(&self, dst: &mut Encoder); +} + +struct Encoder { + dst: Vec, +} + +impl Encoder { + fn new() -> Encoder { + Encoder { + dst: vec![0, 0, 0, 0], + } + } + + fn finish(mut self) -> Vec { + let len = self.dst.len() - 4; + self.dst[0] = (len >> 0) as u8; + self.dst[1] = (len >> 8) as u8; + self.dst[2] = (len >> 16) as u8; + self.dst[3] = (len >> 24) as u8; + self.dst + } + + fn byte(&mut self, byte: u8) { + self.dst.push(byte); + } +} + +impl Encode for bool { + fn encode(&self, dst: &mut Encoder) { + dst.byte(*self as u8); + } +} + +impl Encode for u32 { + fn encode(&self, dst: &mut Encoder) { + let mut val = *self; + while (val >> 7) != 0 { + dst.byte((val as u8) | 0x80); + val >>= 7; + } + assert_eq!(val >> 7, 0); + dst.byte(val as u8); + } +} + +impl Encode for usize { + fn encode(&self, dst: &mut Encoder) { + assert!(*self <= u32::max_value() as usize); + (*self as u32).encode(dst); + } +} + +impl<'a> Encode for &'a [u8] { + fn encode(&self, dst: &mut Encoder) { + self.len().encode(dst); + dst.dst.extend_from_slice(*self); + } +} + +impl<'a> Encode for &'a str { + fn encode(&self, dst: &mut Encoder) { + self.as_bytes().encode(dst); + } +} + +impl Encode for Vec { + fn encode(&self, dst: &mut Encoder) { + self.len().encode(dst); + for item in self { + item.encode(dst); + } + } +} + +impl Encode for Option { + fn encode(&self, dst: &mut Encoder) { + match self { + None => dst.byte(0), + Some(val) => { + dst.byte(1); + val.encode(dst) + } + } + } +} + +macro_rules! encode_struct { + ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => { + struct $name $($lt)* { + $($field: $ty,)* + } + + impl $($lt)* Encode for $name $($lt)* { + fn encode(&self, _dst: &mut Encoder) { + $(self.$field.encode(_dst);)* + } + } + } +} + +macro_rules! encode_enum { + ($name:ident ($($lt:tt)*) $($fields:tt)*) => ( + enum $name $($lt)* { $($fields)* } + + impl$($lt)* Encode for $name $($lt)* { + fn encode(&self, dst: &mut Encoder) { + use self::$name::*; + encode_enum!(@arms self dst (0) () $($fields)*) + } + } + ); + + (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*)) => ( + encode_enum!(@expr match $me { $($arms)* }) + ); + + (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident, $($rest:tt)*) => ( + encode_enum!( + @arms + $me + $dst + ($cnt+1) + ($($arms)* $name => $dst.byte($cnt),) + $($rest)* + ) + ); + + (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident($t:ty), $($rest:tt)*) => ( + encode_enum!( + @arms + $me + $dst + ($cnt+1) + ($($arms)* $name(val) => { $dst.byte($cnt); val.encode($dst) }) + $($rest)* + ) + ); + + (@expr $e:expr) => ($e); +} + +macro_rules! encode_api { + () => (); + (struct $name:ident<'a> { $($fields:tt)* } $($rest:tt)*) => ( + encode_struct!($name (<'a>) $($fields)*); + encode_api!($($rest)*); + ); + (struct $name:ident { $($fields:tt)* } $($rest:tt)*) => ( + encode_struct!($name () $($fields)*); + encode_api!($($rest)*); + ); + (enum $name:ident<'a> { $($variants:tt)* } $($rest:tt)*) => ( + encode_enum!($name (<'a>) $($variants)*); + encode_api!($($rest)*); + ); + (enum $name:ident { $($variants:tt)* } $($rest:tt)*) => ( + encode_enum!($name () $($variants)*); + encode_api!($($rest)*); + ); +} +shared_api!(encode_api); diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index bcc9d51e..50388188 100755 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -5,16 +5,16 @@ )] #![doc(html_root_url = "https://docs.rs/wasm-bindgen-backend/0.2")] -#[macro_use] -extern crate lazy_static; #[macro_use] extern crate log; extern crate proc_macro2; #[macro_use] extern crate quote; -extern crate serde_json; extern crate syn; +#[macro_use] +extern crate lazy_static; +#[macro_use] extern crate wasm_bindgen_shared as shared; pub use codegen::TryToTokens; @@ -25,5 +25,6 @@ mod error; pub mod ast; mod codegen; +mod encode; pub mod defined; pub mod util; diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index bf62f871..249b9caf 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -14,8 +14,6 @@ Shared support for the wasm-bindgen-cli package, an internal dependency base64 = "0.9" failure = "0.1.2" parity-wasm = "0.34" -serde = "1.0" -serde_json = "1.0" tempfile = "3.0" wasm-bindgen-shared = { path = "../shared", version = '=0.2.25' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.25' } diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs new file mode 100644 index 00000000..9d4fdc03 --- /dev/null +++ b/crates/cli-support/src/decode.rs @@ -0,0 +1,147 @@ +use std::str; + +pub trait Decode<'src>: Sized { + fn decode(data: &mut &'src [u8]) -> Self; + + fn decode_all(mut data: &'src [u8]) -> Self { + let ret = Self::decode(&mut data); + assert!(data.len() == 0); + return ret + } +} + +fn get<'a>(b: &mut &'a [u8]) -> u8 { + let r = b[0]; + *b = &b[1..]; + return r +} + +impl<'src> Decode<'src> for bool { + fn decode(data: &mut &'src [u8]) -> Self { + get(data) != 0 + } +} + +impl<'src> Decode<'src> for u32 { + fn decode(data: &mut &'src [u8]) -> Self { + let mut cur = 0; + let mut offset = 0; + loop { + let byte = get(data); + cur |= ((byte & 0x7f) as u32) << offset; + if byte & 0x80 == 0 { + break cur + } + offset += 7; + } + } +} + +impl<'src> Decode<'src> for &'src str { + fn decode(data: &mut &'src [u8]) -> &'src str { + let n = u32::decode(data); + let (a, b) = data.split_at(n as usize); + *data = b; + str::from_utf8(a).unwrap() + } +} + +impl<'src, T: Decode<'src>> Decode<'src> for Vec { + fn decode(data: &mut &'src [u8]) -> Self { + let n = u32::decode(data); + let mut v = Vec::with_capacity(n as usize); + for _ in 0..n { + v.push(Decode::decode(data)); + } + v + } +} + +impl<'src, T: Decode<'src>> Decode<'src> for Option { + fn decode(data: &mut &'src [u8]) -> Self { + match get(data) { + 0 => None, + 1 => Some(Decode::decode(data)), + _ => unreachable!(), + } + } +} + +macro_rules! decode_struct { + ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => { + pub struct $name <$($lt)*> { + $(pub $field: $ty,)* + } + + impl <'a> Decode<'a> for $name <$($lt)*> { + fn decode(_data: &mut &'a [u8]) -> Self { + $name { + $($field: Decode::decode(_data),)* + } + } + } + } +} + +macro_rules! decode_enum { + ($name:ident ($($lt:tt)*) $($fields:tt)*) => ( + pub enum $name <$($lt)*> { $($fields)* } + + impl <'a> Decode<'a> for $name <$($lt)*> { + fn decode(data: &mut &'a [u8]) -> Self { + use self::$name::*; + decode_enum!(@arms data dst (0) () $($fields)*) + } + } + ); + + (@arms $data:ident $dst:ident ($cnt:expr) ($($arms:tt)*)) => ( + decode_enum!(@expr match get($data) { $($arms)* _ => unreachable!() }) + ); + + (@arms $data:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident, $($rest:tt)*) => ( + decode_enum!( + @arms + $data + $dst + ($cnt+1) + ($($arms)* n if n == $cnt => $name, ) + $($rest)* + ) + ); + + (@arms $data:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident($t:ty), $($rest:tt)*) => ( + decode_enum!( + @arms + $data + $dst + ($cnt+1) + ($($arms)* n if n == $cnt => $name(Decode::decode($data)), ) + $($rest)* + ) + ); + + (@expr $e:expr) => ($e); +} + +macro_rules! decode_api { + () => (); + (struct $name:ident<'a> { $($fields:tt)* } $($rest:tt)*) => ( + decode_struct!($name ('a) $($fields)*); + decode_api!($($rest)*); + ); + (struct $name:ident { $($fields:tt)* } $($rest:tt)*) => ( + decode_struct!($name () $($fields)*); + decode_api!($($rest)*); + ); + (enum $name:ident<'a> { $($variants:tt)* } $($rest:tt)*) => ( + decode_enum!($name ('a) $($variants)*); + decode_api!($($rest)*); + ); + (enum $name:ident { $($variants:tt)* } $($rest:tt)*) => ( + decode_enum!($name () $($variants)*); + decode_api!($($rest)*); + ); +} + +shared_api!(decode_api); diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 0f75b427..f2a31793 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::mem; +use decode; use failure::{Error, ResultExt}; use parity_wasm::elements::*; use shared; @@ -25,8 +26,8 @@ pub struct Context<'a> { pub typescript: String, pub exposed_globals: HashSet<&'static str>, pub required_internal_exports: HashSet<&'static str>, - pub imported_functions: HashSet, - pub imported_statics: HashSet, + pub imported_functions: HashSet<&'a str>, + pub imported_statics: HashSet<&'a str>, pub config: &'a Bindgen, pub module: &'a mut Module, @@ -37,7 +38,7 @@ pub struct Context<'a> { /// from, `None` being the global module. The second key is a map of /// identifiers we've already imported from the module to what they're /// called locally. - pub imported_names: HashMap, HashMap>, + pub imported_names: HashMap, HashMap>, /// A set of all imported identifiers to the number of times they've been /// imported, used to generate new identifiers. @@ -59,9 +60,9 @@ pub struct ExportedClass { } pub struct SubContext<'a, 'b: 'a> { - pub program: &'a shared::Program, + pub program: &'b decode::Program<'b>, pub cx: &'a mut Context<'b>, - pub vendor_prefixes: HashMap>, + pub vendor_prefixes: HashMap<&'b str, Vec<&'b str>>, } const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"]; @@ -1690,7 +1691,7 @@ impl<'a, 'b> SubContext<'a, 'b> { })?; } for f in self.program.imports.iter() { - if let shared::ImportKind::Type(ty) = &f.kind { + if let decode::ImportKind::Type(ty) = &f.kind { self.register_vendor_prefix(ty); } } @@ -1707,7 +1708,7 @@ impl<'a, 'b> SubContext<'a, 'b> { Ok(()) } - fn generate_export(&mut self, export: &shared::Export) -> Result<(), Error> { + fn generate_export(&mut self, export: &decode::Export<'b>) -> Result<(), Error> { if let Some(ref class) = export.class { return self.generate_export_for_class(class, export); } @@ -1734,8 +1735,8 @@ impl<'a, 'b> SubContext<'a, 'b> { fn generate_export_for_class( &mut self, - class_name: &str, - export: &shared::Export, + class_name: &'b str, + export: &decode::Export, ) -> Result<(), Error> { let wasm_name = shared::struct_function_export_name(class_name, &export.function.name); @@ -1785,9 +1786,9 @@ impl<'a, 'b> SubContext<'a, 'b> { Ok(()) } - fn generate_import(&mut self, import: &shared::Import) -> Result<(), Error> { + fn generate_import(&mut self, import: &decode::Import<'b>) -> Result<(), Error> { match import.kind { - shared::ImportKind::Function(ref f) => { + decode::ImportKind::Function(ref f) => { self.generate_import_function(import, f).with_context(|_| { format!( "failed to generate bindings for JS import `{}`", @@ -1795,29 +1796,29 @@ impl<'a, 'b> SubContext<'a, 'b> { ) })?; } - shared::ImportKind::Static(ref s) => { + decode::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(ref ty) => { + decode::ImportKind::Type(ref ty) => { self.generate_import_type(import, ty).with_context(|_| { format!("failed to generate bindings for JS import `{}`", ty.name,) })?; } - shared::ImportKind::Enum(_) => {} + decode::ImportKind::Enum(_) => {} } Ok(()) } fn generate_import_static( &mut self, - info: &shared::Import, - import: &shared::ImportStatic, + info: &decode::Import<'b>, + import: &decode::ImportStatic<'b>, ) -> Result<(), Error> { // The same static can be imported in multiple locations, so only // generate bindings once for it. - if !self.cx.imported_statics.insert(import.shim.clone()) { + if !self.cx.imported_statics.insert(import.shim) { return Ok(()); } @@ -1841,8 +1842,8 @@ impl<'a, 'b> SubContext<'a, 'b> { fn generate_import_function( &mut self, - info: &shared::Import, - import: &shared::ImportFunction, + info: &decode::Import<'b>, + import: &decode::ImportFunction<'b>, ) -> Result<(), Error> { if !self.cx.wasm_import_needed(&import.shim) { return Ok(()); @@ -1850,7 +1851,7 @@ impl<'a, 'b> SubContext<'a, 'b> { // It's possible for the same function to be imported in two locations, // but we only want to generate one. - if !self.cx.imported_functions.insert(import.shim.clone()) { + if !self.cx.imported_functions.insert(import.shim) { return Ok(()); } @@ -1872,8 +1873,8 @@ impl<'a, 'b> SubContext<'a, 'b> { fn generated_import_target( &mut self, - info: &shared::Import, - import: &shared::ImportFunction, + info: &decode::Import<'b>, + import: &decode::ImportFunction, descriptor: &Descriptor, ) -> Result { let method_data = match &import.method { @@ -1896,14 +1897,14 @@ impl<'a, 'b> SubContext<'a, 'b> { let class = self.import_name(info, &method_data.class)?; let op = match &method_data.kind { - shared::MethodKind::Constructor => return Ok(format!("new {}", class)), - shared::MethodKind::Operation(op) => op, + decode::MethodKind::Constructor => return Ok(format!("new {}", class)), + decode::MethodKind::Operation(op) => op, }; let target = if import.structural { let location = if op.is_static { &class } else { "this" }; match &op.kind { - shared::OperationKind::Regular => { + decode::OperationKind::Regular => { let nargs = descriptor.unwrap_function().arguments.len(); let mut s = format!("function("); for i in 0..nargs - 1 { @@ -1924,31 +1925,31 @@ impl<'a, 'b> SubContext<'a, 'b> { s.push_str(");\n}"); s } - shared::OperationKind::Getter(g) => format!( + decode::OperationKind::Getter(g) => format!( "function() {{ return {}.{}; }}", location, g ), - shared::OperationKind::Setter(s) => format!( + decode::OperationKind::Setter(s) => format!( "function(y) {{ {}.{} = y; }}", location, s ), - shared::OperationKind::IndexingGetter => format!( + decode::OperationKind::IndexingGetter => format!( "function(y) {{ return {}[y]; }}", location ), - shared::OperationKind::IndexingSetter => format!( + decode::OperationKind::IndexingSetter => format!( "function(y, z) {{ {}[y] = z; }}", location ), - shared::OperationKind::IndexingDeleter => format!( + decode::OperationKind::IndexingDeleter => format!( "function(y) {{ delete {}[y]; }}", @@ -1960,30 +1961,30 @@ impl<'a, 'b> SubContext<'a, 'b> { class, if op.is_static { "" } else { ".prototype" }); let (mut target, name) = match &op.kind { - shared::OperationKind::Regular => { + decode::OperationKind::Regular => { (format!("{}.{}", target, import.function.name), &import.function.name) } - shared::OperationKind::Getter(g) => { + decode::OperationKind::Getter(g) => { self.cx.expose_get_inherited_descriptor(); (format!( "GetOwnOrInheritedPropertyDescriptor({}, '{}').get", target, g, ), g) } - shared::OperationKind::Setter(s) => { + decode::OperationKind::Setter(s) => { self.cx.expose_get_inherited_descriptor(); (format!( "GetOwnOrInheritedPropertyDescriptor({}, '{}').set", target, s, ), s) } - shared::OperationKind::IndexingGetter => { + decode::OperationKind::IndexingGetter => { panic!("indexing getter should be structural") } - shared::OperationKind::IndexingSetter => { + decode::OperationKind::IndexingSetter => { panic!("indexing setter should be structural") } - shared::OperationKind::IndexingDeleter => { + decode::OperationKind::IndexingDeleter => { panic!("indexing deleter should be structural") } }; @@ -2009,8 +2010,8 @@ impl<'a, 'b> SubContext<'a, 'b> { fn generate_import_type( &mut self, - info: &shared::Import, - import: &shared::ImportType, + info: &decode::Import<'b>, + import: &decode::ImportType, ) -> Result<(), Error> { if !self.cx.wasm_import_needed(&import.instanceof_shim) { return Ok(()); @@ -2029,7 +2030,7 @@ impl<'a, 'b> SubContext<'a, 'b> { Ok(()) } - fn generate_enum(&mut self, enum_: &shared::Enum) { + fn generate_enum(&mut self, enum_: &decode::Enum) { let mut variants = String::new(); for variant in enum_.variants.iter() { @@ -2052,7 +2053,7 @@ impl<'a, 'b> SubContext<'a, 'b> { self.cx.typescript.push_str("}\n"); } - fn generate_struct(&mut self, struct_: &shared::Struct) -> Result<(), Error> { + fn generate_struct(&mut self, struct_: &decode::Struct) -> Result<(), Error> { let mut dst = String::new(); let mut ts_dst = String::new(); for field in struct_.fields.iter() { @@ -2098,7 +2099,7 @@ impl<'a, 'b> SubContext<'a, 'b> { let class = self .cx .exported_classes - .entry(struct_.name.clone()) + .entry(struct_.name.to_string()) .or_insert_with(Default::default); class.comments = format_doc_comments(&struct_.comments, None); class.contents.push_str(&dst); @@ -2110,18 +2111,18 @@ impl<'a, 'b> SubContext<'a, 'b> { fn register_vendor_prefix( &mut self, - info: &shared::ImportType, + info: &decode::ImportType<'b>, ) { if info.vendor_prefixes.len() == 0 { return } self.vendor_prefixes - .entry(info.name.to_string()) + .entry(info.name) .or_insert(Vec::new()) .extend(info.vendor_prefixes.iter().cloned()); } - fn import_name(&mut self, import: &shared::Import, item: &str) -> Result { + fn import_name(&mut self, import: &decode::Import<'b>, item: &str) -> Result { // First up, imports don't work at all in `--no-modules` mode as we're // not sure how to import them. if self.cx.config.no_modules { @@ -2180,7 +2181,7 @@ impl<'a, 'b> SubContext<'a, 'b> { let identifier = self .cx .imported_names - .entry(import.module.clone()) + .entry(import.module) .or_insert_with(Default::default) .entry(name_to_import.to_string()) .or_insert_with(|| { @@ -2207,7 +2208,7 @@ impl<'a, 'b> SubContext<'a, 'b> { switch(imports_post, &name, "", vendor_prefixes); imports_post.push_str(";\n"); - fn switch(dst: &mut String, name: &str, prefix: &str, left: &[String]) { + fn switch(dst: &mut String, name: &str, prefix: &str, left: &[&str]) { if left.len() == 0 { dst.push_str(prefix); return dst.push_str(name); @@ -2254,7 +2255,7 @@ fn generate_identifier(name: &str, used_names: &mut HashMap) -> S } } -fn format_doc_comments(comments: &Vec, js_doc_comments: Option) -> String { +fn format_doc_comments(comments: &[&str], js_doc_comments: Option) -> String { let body: String = comments .iter() .map(|c| format!("*{}\n", c.trim_matches('"'))) diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 6b9fe696..bb7fddaf 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -1,7 +1,7 @@ #![doc(html_root_url = "https://docs.rs/wasm-bindgen-cli-support/0.2")] extern crate parity_wasm; -extern crate serde_json; +#[macro_use] extern crate wasm_bindgen_shared as shared; extern crate wasm_bindgen_gc; #[macro_use] @@ -13,10 +13,12 @@ use std::env; use std::fs; use std::mem; use std::path::{Path, PathBuf}; +use std::str; use failure::{Error, ResultExt}; use parity_wasm::elements::*; +mod decode; mod descriptor; mod js; pub mod wasm2es6js; @@ -137,7 +139,8 @@ impl Bindgen { (module, stem) } }; - let programs = extract_programs(&mut module) + let mut program_storage = Vec::new(); + let programs = extract_programs(&mut module, &mut program_storage) .with_context(|_| "failed to extract wasm-bindgen custom sections")?; // Here we're actually instantiating the module we've parsed above for @@ -289,35 +292,50 @@ impl Bindgen { } } -fn extract_programs(module: &mut Module) -> Result, Error> { - let version = shared::version(); - let mut ret = Vec::new(); +fn extract_programs<'a>( + module: &mut Module, + program_storage: &'a mut Vec>, +) -> Result>, Error> { + let my_version = shared::version(); let mut to_remove = Vec::new(); + assert!(program_storage.is_empty()); - for (i, s) in module.sections().iter().enumerate() { - let custom = match *s { - Section::Custom(ref s) => s, + for (i, s) in module.sections_mut().iter_mut().enumerate() { + let custom = match s { + Section::Custom(s) => s, _ => continue, }; if custom.name() != "__wasm_bindgen_unstable" { continue; } to_remove.push(i); + program_storage.push(mem::replace(custom.payload_mut(), Vec::new())); + } - let mut payload = custom.payload(); - while payload.len() > 0 { - let len = ((payload[0] as usize) << 0) - | ((payload[1] as usize) << 8) - | ((payload[2] as usize) << 16) - | ((payload[3] as usize) << 24); - let (a, b) = payload[4..].split_at(len as usize); - payload = b; + for i in to_remove.into_iter().rev() { + module.sections_mut().remove(i); + } - let p: shared::ProgramOnlySchema = match serde_json::from_slice(&a) { - Ok(f) => f, - Err(e) => bail!("failed to decode what looked like wasm-bindgen data: {}", e), - }; - if p.schema_version != shared::SCHEMA_VERSION { + 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!( " @@ -341,24 +359,67 @@ or you can update the binary with 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! ", - p.version, - version + their_version, + my_version, ); } - let p: shared::Program = match serde_json::from_slice(&a) { - Ok(f) => f, - Err(e) => bail!("failed to decode what looked like wasm-bindgen data: {}", e), - }; - ret.push(p); + let next = get_remaining(&mut payload).unwrap(); + ret.push(::decode_all(next)); } } - - for i in to_remove.into_iter().rev() { - module.sections_mut().remove(i); - } 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!(), + }; + 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 == 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 reset_indentation(s: &str) -> String { let mut indent: u32 = 0; let mut dst = String::new(); diff --git a/crates/macro-support/Cargo.toml b/crates/macro-support/Cargo.toml index 0bd1a951..0a578786 100644 --- a/crates/macro-support/Cargo.toml +++ b/crates/macro-support/Cargo.toml @@ -15,7 +15,7 @@ spans = ["wasm-bindgen-backend/spans"] extra-traits = ["syn/extra-traits"] [dependencies] -syn = { version = '0.15.0', features = ['full'] } +syn = { version = '0.15.0', features = ['visit'] } quote = '0.6' proc-macro2 = "0.4.9" wasm-bindgen-backend = { path = "../backend", version = "=0.2.25" } diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index e3f27a4c..7bb7ef72 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -10,7 +10,3 @@ description = """ Shared support between wasm-bindgen and wasm-bindgen cli, an internal dependency. """ - -[dependencies] -serde_derive = "1" -serde = "1" diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 05140dd9..8fc8ad7e 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -1,139 +1,118 @@ #![doc(html_root_url = "https://docs.rs/wasm-bindgen-shared/0.2")] -#[macro_use] -extern crate serde_derive; - // The schema is so unstable right now we just force it to change whenever this // package's version changes, which happens on all publishes. pub const SCHEMA_VERSION: &str = env!("CARGO_PKG_VERSION"); -#[derive(Deserialize)] -pub struct ProgramOnlySchema { - pub schema_version: String, - pub version: String, +#[macro_export] +macro_rules! shared_api { + ($mac:ident) => ($mac! { +struct Program<'a> { + exports: Vec>, + enums: Vec>, + imports: Vec>, + structs: Vec>, + // version: &'a str, + // schema_version: &'a str, } -#[derive(Deserialize, Serialize)] -pub struct Program { - pub exports: Vec, - pub enums: Vec, - pub imports: Vec, - pub structs: Vec, - pub version: String, - pub schema_version: String, +struct Import<'a> { + module: Option<&'a str>, + js_namespace: Option<&'a str>, + kind: ImportKind<'a>, } -#[derive(Deserialize, Serialize)] -pub struct Import { - pub module: Option, - pub js_namespace: Option, - pub kind: ImportKind, -} - -#[derive(Deserialize, Serialize)] -#[serde(tag = "kind", rename_all = "lowercase")] -pub enum ImportKind { - Function(ImportFunction), - Static(ImportStatic), - Type(ImportType), +enum ImportKind<'a> { + Function(ImportFunction<'a>), + Static(ImportStatic<'a>), + Type(ImportType<'a>), Enum(ImportEnum), } -#[derive(Deserialize, Serialize)] -pub struct ImportFunction { - pub shim: String, - pub catch: bool, - pub variadic: bool, - pub method: Option, - pub structural: bool, - pub function: Function, +struct ImportFunction<'a> { + shim: &'a str, + catch: bool, + variadic: bool, + method: Option>, + structural: bool, + function: Function<'a>, } -#[derive(Deserialize, Serialize)] -pub struct MethodData { - pub class: String, - pub kind: MethodKind, +struct MethodData<'a> { + class: &'a str, + kind: MethodKind<'a>, } -#[derive(Deserialize, Serialize)] -pub enum MethodKind { +enum MethodKind<'a> { Constructor, - Operation(Operation), + Operation(Operation<'a>), } -#[derive(Deserialize, Serialize)] -pub struct Operation { - pub is_static: bool, - pub kind: OperationKind, +struct Operation<'a> { + is_static: bool, + kind: OperationKind<'a>, } -#[derive(Deserialize, Serialize)] -pub enum OperationKind { +enum OperationKind<'a> { Regular, - Getter(String), - Setter(String), + Getter(&'a str), + Setter(&'a str), IndexingGetter, IndexingSetter, IndexingDeleter, } -#[derive(Deserialize, Serialize)] -pub struct ImportStatic { - pub name: String, - pub shim: String, +struct ImportStatic<'a> { + name: &'a str, + shim: &'a str, } -#[derive(Deserialize, Serialize)] -pub struct ImportType { - pub name: String, - pub instanceof_shim: String, - pub vendor_prefixes: Vec, +struct ImportType<'a> { + name: &'a str, + instanceof_shim: &'a str, + vendor_prefixes: Vec<&'a str>, } -#[derive(Deserialize, Serialize)] -pub struct ImportEnum {} +struct ImportEnum {} -#[derive(Deserialize, Serialize)] -pub struct Export { - pub class: Option, - pub method: bool, - pub consumed: bool, - pub is_constructor: bool, - pub function: Function, - pub comments: Vec, +struct Export<'a> { + class: Option<&'a str>, + method: bool, + consumed: bool, + is_constructor: bool, + function: Function<'a>, + comments: Vec<&'a str>, } -#[derive(Deserialize, Serialize)] -pub struct Enum { - pub name: String, - pub variants: Vec, - pub comments: Vec, +struct Enum<'a> { + name: &'a str, + variants: Vec>, + comments: Vec<&'a str>, } -#[derive(Deserialize, Serialize)] -pub struct EnumVariant { - pub name: String, - pub value: u32, +struct EnumVariant<'a> { + name: &'a str, + value: u32, } -#[derive(Deserialize, Serialize)] -pub struct Function { - pub name: String, +struct Function<'a> { + name: &'a str, } -#[derive(Deserialize, Serialize)] -pub struct Struct { - pub name: String, - pub fields: Vec, - pub comments: Vec, +struct Struct<'a> { + name: &'a str, + fields: Vec>, + comments: Vec<&'a str>, } -#[derive(Deserialize, Serialize)] -pub struct StructField { - pub name: String, - pub readonly: bool, - pub comments: Vec, +struct StructField<'a> { + name: &'a str, + readonly: bool, + comments: Vec<&'a str>, } +}) // end of mac case + +} // end of mac definition pub fn new_function(struct_name: &str) -> String { let mut name = format!("__wbg_");