├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── bench.rs └── fixture.wasm ├── derive ├── Cargo.toml └── src │ └── lib.rs ├── examples └── dump.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── decode.rs │ └── roundtrip.rs ├── src ├── builtins │ ├── blob.rs │ ├── boolean.rs │ ├── collections.rs │ ├── floats.rs │ ├── integers.rs │ ├── lazy.rs │ ├── mod.rs │ └── strings.rs ├── indices.rs ├── instructions │ ├── misc.rs │ ├── mod.rs │ ├── simd.rs │ └── threads.rs ├── io.rs ├── lib.rs ├── module.rs ├── sections.rs ├── types.rs └── visit.rs └── tests └── spec.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Check out the repo 15 | uses: actions/checkout@v2 16 | with: 17 | submodules: true 18 | - name: Run tests without proposals 19 | run: cargo test -- -q 20 | - name: Run tests with proposals enabled 21 | run: cargo test --features=proposals -- -q 22 | - name: Install and switch to nightly Rust 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | components: clippy 27 | toolchain: nightly 28 | override: true 29 | - name: Run `clippy check` 30 | uses: actions-rs/clippy-check@v1 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | args: --all-features --all-targets 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/testsuite"] 2 | path = tests/testsuite 3 | url = https://github.com/WebAssembly/testsuite.git 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmbin" 3 | version = "0.3.1" 4 | authors = ["Ingvar Stepanyan "] 5 | edition = "2018" 6 | license = "Apache-2.0" 7 | description = "A self-generating WebAssembly parser and serializer" 8 | repository = "https://github.com/GoogleChromeLabs/wasmbin" 9 | categories = ["wasm", "parser-implementations"] 10 | keywords = ["webassembly", "wasm", "parser", "serializer"] 11 | 12 | exclude = [ 13 | "tests/testsuite", 14 | "benches/fixture.wasm", 15 | ] 16 | 17 | [dependencies] 18 | leb128 = "0.2.4" 19 | thiserror = "1.0.25" 20 | wasmbin-derive = { version = "0.1.0", path = "derive" } 21 | custom_debug = "0.5.0" 22 | once_cell = "1.8.0" 23 | arbitrary = { version = "1.0.1", features = ["derive"] } 24 | 25 | [features] 26 | default = [] 27 | nightly = ["criterion/real_blackbox", "wasmbin-derive/nightly"] 28 | proposals = ["tail-call", "simd", "threads"] 29 | tail-call = [] 30 | simd = [] 31 | threads = [] 32 | 33 | [dev-dependencies] 34 | criterion = "0.3.4" 35 | libtest-mimic = "0.3.0" 36 | wast = "36.0.0" 37 | fehler = "1.0.0" 38 | anyhow = "1.0.41" 39 | tempfile = "3.2.0" 40 | structopt = "0.3.21" 41 | 42 | [[bench]] 43 | name = "bench" 44 | harness = false 45 | 46 | [profile.bench] 47 | debug = true 48 | 49 | [[test]] 50 | name = "spec" 51 | harness = false 52 | 53 | [workspace] 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository is archived as the author no longer works at Google. 2 | 3 | The continued development of this project has moved to [a personal fork](https://github.com/RReverser/wasmbin). 4 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 16 | use std::fs::File; 17 | use tempfile::tempfile; 18 | use wasmbin::io::DecodeError; 19 | use wasmbin::visit::{Visit, VisitError}; 20 | use wasmbin::Module; 21 | 22 | fn deep_module() -> Module { 23 | use wasmbin::builtins::Blob; 24 | use wasmbin::instructions::{Expression, Instruction}; 25 | use wasmbin::sections::FuncBody; 26 | use wasmbin::types::BlockType; 27 | 28 | let mut expr = Expression::default(); 29 | for _ in 0..100_000 { 30 | expr.push(Instruction::BlockStart(BlockType::Empty)); 31 | } 32 | for _ in 0..100_000 { 33 | expr.push(Instruction::End); 34 | } 35 | Module { 36 | sections: vec![vec![Blob::from(FuncBody { 37 | locals: Default::default(), 38 | expr, 39 | })] 40 | .into()], 41 | ..Default::default() 42 | } 43 | } 44 | 45 | fn unlazify(wasm: T) -> Result { 46 | match wasm.visit(|()| {}) { 47 | Ok(()) => Ok(wasm), 48 | Err(err) => match err { 49 | VisitError::LazyDecode(err) => Err(err), 50 | VisitError::Custom(err) => match err {}, 51 | }, 52 | } 53 | } 54 | 55 | fn bench_parse(c: &mut Criterion) { 56 | c.bench_function(concat!(stringify!($name), "::bench_parse"), |b| { 57 | b.iter(|| { 58 | let f = File::open("benches/fixture.wasm").unwrap(); 59 | unlazify(Module::decode_from(f).unwrap()) 60 | }) 61 | }); 62 | } 63 | 64 | fn bench_parse_buf(c: &mut Criterion) { 65 | c.bench_function(concat!(stringify!($name), "::bench_parse_buf"), |b| { 66 | b.iter(|| { 67 | let f = File::open("benches/fixture.wasm").unwrap(); 68 | let f = std::io::BufReader::new(f); 69 | unlazify(Module::decode_from(f).unwrap()) 70 | }) 71 | }); 72 | } 73 | 74 | fn bench_parse_vec(c: &mut Criterion) { 75 | c.bench_function(concat!(stringify!($name), "::bench_parse_vec"), |b| { 76 | let f = std::fs::read("benches/fixture.wasm").unwrap(); 77 | b.iter(|| { 78 | let f = black_box(f.as_slice()); 79 | unlazify(Module::decode_from(f).unwrap()) 80 | }) 81 | }); 82 | } 83 | 84 | fn bench_parse_deep_module(c: &mut Criterion) { 85 | c.bench_function( 86 | concat!(stringify!($name), "::bench_parse_deep_module"), 87 | |b| { 88 | let f = deep_module().encode_into(Vec::new()).unwrap(); 89 | b.iter(|| { 90 | let f = black_box(f.as_slice()); 91 | unlazify(Module::decode_from(f).unwrap()) 92 | }) 93 | }, 94 | ); 95 | } 96 | 97 | fn read_module() -> Module { 98 | let f = std::fs::read("benches/fixture.wasm").unwrap(); 99 | unlazify(Module::decode_from(f.as_slice()).unwrap()).unwrap() 100 | } 101 | 102 | fn bench_write(c: &mut Criterion) { 103 | c.bench_function(concat!(stringify!($name), "::bench_write"), |b| { 104 | let m = read_module(); 105 | b.iter(|| { 106 | let f = tempfile().unwrap(); 107 | black_box(&m).encode_into(f).unwrap() 108 | }) 109 | }); 110 | } 111 | 112 | fn bench_write_buf(c: &mut Criterion) { 113 | c.bench_function(concat!(stringify!($name), "::bench_write_buf"), |b| { 114 | let m = read_module(); 115 | b.iter(|| { 116 | let f = tempfile().unwrap(); 117 | let f = std::io::BufWriter::new(f); 118 | black_box(&m).encode_into(f).unwrap() 119 | }) 120 | }); 121 | } 122 | 123 | fn bench_write_vec(c: &mut Criterion) { 124 | c.bench_function(concat!(stringify!($name), "::bench_write_vec"), |b| { 125 | let m = read_module(); 126 | b.iter(|| black_box(&m).encode_into(Vec::new()).unwrap()) 127 | }); 128 | } 129 | 130 | fn bench_write_deep_module(c: &mut Criterion) { 131 | c.bench_function( 132 | concat!(stringify!($name), "::bench_write_deep_module"), 133 | |b| { 134 | let m = deep_module(); 135 | b.iter(|| black_box(&m).encode_into(Vec::new()).unwrap()) 136 | }, 137 | ); 138 | } 139 | 140 | criterion_group! { 141 | name = benches; 142 | config = Criterion::default().sample_size(20); 143 | targets = 144 | bench_parse, 145 | bench_parse_buf, 146 | bench_parse_vec, 147 | bench_parse_deep_module, 148 | bench_write, 149 | bench_write_buf, 150 | bench_write_vec, 151 | bench_write_deep_module, 152 | } 153 | 154 | criterion_main!(benches); 155 | -------------------------------------------------------------------------------- /benches/fixture.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/wasmbin/1c5322117c74fdbbd9ac9649b62cef70d14252a7/benches/fixture.wasm -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmbin-derive" 3 | version = "0.1.0" 4 | authors = ["Ingvar Stepanyan "] 5 | edition = "2018" 6 | license = "Apache-2.0" 7 | description = "Derive crate for the wasmbin library" 8 | repository = "https://github.com/GoogleChromeLabs/wasmbin" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | synstructure = "0.12.4" 15 | quote = "1.0.9" 16 | proc-macro2 = "1.0.27" 17 | syn = "1.0.73" 18 | thiserror = "1.0.25" 19 | 20 | [features] 21 | nightly = [] 22 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate proc_macro; 16 | 17 | use quote::{quote, ToTokens}; 18 | use std::borrow::Cow; 19 | use synstructure::{decl_derive, Structure, VariantInfo}; 20 | 21 | macro_rules! syn_throw { 22 | ($err:expr) => { 23 | return syn::Error::to_compile_error(&$err); 24 | }; 25 | } 26 | 27 | macro_rules! syn_try { 28 | ($expr:expr) => { 29 | match $expr { 30 | Ok(expr) => expr, 31 | Err(err) => syn_throw!(err), 32 | } 33 | }; 34 | } 35 | 36 | fn discriminant<'v>(v: &VariantInfo<'v>) -> syn::Result>> { 37 | v.ast() 38 | .discriminant 39 | .iter() 40 | .map(|(_, discriminant)| Ok(Cow::Borrowed(discriminant))) 41 | .chain(v.ast().attrs.iter().filter_map(|attr| match attr { 42 | syn::Attribute { 43 | style: syn::AttrStyle::Outer, 44 | path, 45 | .. 46 | } if path.is_ident("wasmbin") => { 47 | syn::custom_keyword!(discriminant); 48 | 49 | Some( 50 | attr.parse_args_with(|parser: syn::parse::ParseStream| { 51 | parser.parse::()?; 52 | parser.parse::()?; 53 | parser.parse() 54 | }) 55 | .map(Cow::Owned), 56 | ) 57 | } 58 | _ => None, 59 | })) 60 | .try_fold(None, |prev, discriminant| { 61 | let discriminant = discriminant?; 62 | if let Some(prev) = prev { 63 | let mut err = syn::Error::new_spanned( 64 | discriminant, 65 | "#[derive(Wasmbin)]: duplicate discriminant", 66 | ); 67 | err.combine(syn::Error::new_spanned( 68 | prev, 69 | "#[derive(Wasmbin)]: previous discriminant here", 70 | )); 71 | return Err(err); 72 | } 73 | Ok(Some(discriminant)) 74 | }) 75 | } 76 | 77 | fn gen_encode_discriminant(repr: &syn::Type, discriminant: &syn::Expr) -> proc_macro2::TokenStream { 78 | quote!(<#repr as Encode>::encode(&#discriminant, w)?) 79 | } 80 | 81 | fn is_newtype_like(v: &VariantInfo) -> bool { 82 | matches!(v.ast().fields, fields @ syn::Fields::Unnamed(_) if fields.len() == 1) 83 | } 84 | 85 | fn track_err_in_field( 86 | mut res: proc_macro2::TokenStream, 87 | v: &VariantInfo, 88 | field: &syn::Field, 89 | index: usize, 90 | ) -> proc_macro2::TokenStream { 91 | if !is_newtype_like(v) { 92 | let field_name = match &field.ident { 93 | Some(ident) => ident.to_string(), 94 | None => index.to_string(), 95 | }; 96 | res = quote!(#res.map_err(|err| err.in_path(PathItem::Name(#field_name)))); 97 | } 98 | res 99 | } 100 | 101 | fn track_err_in_variant( 102 | res: proc_macro2::TokenStream, 103 | v: &VariantInfo, 104 | ) -> proc_macro2::TokenStream { 105 | use std::fmt::Write; 106 | 107 | let mut variant_name = String::new(); 108 | if let Some(prefix) = v.prefix { 109 | write!(variant_name, "{}::", prefix).unwrap(); 110 | } 111 | write!(variant_name, "{}", v.ast().ident).unwrap(); 112 | 113 | quote!(#res.map_err(|err| err.in_path(PathItem::Variant(#variant_name)))) 114 | } 115 | 116 | fn catch_expr( 117 | res: proc_macro2::TokenStream, 118 | err: proc_macro2::TokenStream, 119 | ) -> proc_macro2::TokenStream { 120 | quote!( 121 | (move || -> Result<_, #err> { 122 | Ok({ #res }) 123 | })() 124 | ) 125 | } 126 | 127 | fn gen_decode(v: &VariantInfo) -> proc_macro2::TokenStream { 128 | let mut res = v.construct(|field, index| { 129 | let res = track_err_in_field(quote!(Decode::decode(r)), v, field, index); 130 | quote!(#res?) 131 | }); 132 | res = catch_expr(res, quote!(DecodeError)); 133 | res = track_err_in_variant(res, v); 134 | res 135 | } 136 | 137 | fn parse_repr(s: &Structure) -> syn::Result { 138 | s.ast() 139 | .attrs 140 | .iter() 141 | .find(|attr| attr.path.is_ident("repr")) 142 | .ok_or_else(|| { 143 | syn::Error::new_spanned( 144 | &s.ast().ident, 145 | "Wasmbin enums must have a #[repr(type)] attribute", 146 | ) 147 | })? 148 | .parse_args() 149 | } 150 | 151 | fn wasmbin_derive(s: Structure) -> proc_macro2::TokenStream { 152 | let (encode_discriminant, decode) = match s.ast().data { 153 | syn::Data::Enum(_) => { 154 | let repr = syn_try!(parse_repr(&s)); 155 | 156 | let mut encode_discriminant = quote!(); 157 | 158 | let mut decoders = quote!(); 159 | let mut decode_other = quote!({ return Ok(None) }); 160 | 161 | for v in s.variants() { 162 | let discriminant = syn_try!(discriminant(v)); 163 | 164 | match discriminant { 165 | Some(discriminant) => { 166 | let pat = v.pat(); 167 | 168 | let encode = gen_encode_discriminant(&repr, &discriminant); 169 | (quote!(#pat => #encode,)).to_tokens(&mut encode_discriminant); 170 | 171 | let decode = gen_decode(v); 172 | (quote!( 173 | #discriminant => #decode?, 174 | )) 175 | .to_tokens(&mut decoders); 176 | } 177 | None => { 178 | let fields = v.ast().fields; 179 | if fields.len() != 1 { 180 | syn_throw!(syn::Error::new_spanned( 181 | fields, 182 | "Catch-all variants without discriminant must have a single field." 183 | )); 184 | } 185 | let field = fields.iter().next().unwrap(); 186 | let construct = match &field.ident { 187 | Some(ident) => quote!({ #ident: res }), 188 | None => quote!((res)), 189 | }; 190 | let variant_name = v.ast().ident; 191 | decode_other = quote! { 192 | if let Some(res) = DecodeWithDiscriminant::maybe_decode_with_discriminant(discriminant, r)? { 193 | Self::#variant_name #construct 194 | } else #decode_other 195 | }; 196 | } 197 | } 198 | } 199 | 200 | let name = s.ast().ident.to_string(); 201 | 202 | ( 203 | quote! { 204 | match *self { 205 | #encode_discriminant 206 | _ => {} 207 | } 208 | }, 209 | quote! { 210 | gen impl DecodeWithDiscriminant for @Self { 211 | const NAME: &'static str = #name; 212 | type Discriminant = #repr; 213 | 214 | fn maybe_decode_with_discriminant(discriminant: #repr, r: &mut impl std::io::Read) -> Result, DecodeError> { 215 | Ok(Some(match discriminant { 216 | #decoders 217 | _ => #decode_other 218 | })) 219 | } 220 | } 221 | 222 | gen impl Decode for @Self { 223 | fn decode(r: &mut impl std::io::Read) -> Result { 224 | DecodeWithDiscriminant::decode_without_discriminant(r) 225 | } 226 | } 227 | }, 228 | ) 229 | } 230 | _ => { 231 | let variants = s.variants(); 232 | assert_eq!(variants.len(), 1); 233 | let v = &variants[0]; 234 | let decode = gen_decode(v); 235 | match syn_try!(discriminant(v)) { 236 | Some(discriminant) => { 237 | let name = s.ast().ident.to_string(); 238 | ( 239 | gen_encode_discriminant(&syn::parse_quote!(u8), &discriminant), 240 | quote! { 241 | gen impl DecodeWithDiscriminant for @Self { 242 | const NAME: &'static str = #name; 243 | type Discriminant = u8; 244 | 245 | fn maybe_decode_with_discriminant(discriminant: u8, r: &mut impl std::io::Read) -> Result, DecodeError> { 246 | match discriminant { 247 | #discriminant => #decode.map(Some), 248 | _ => Ok(None), 249 | } 250 | } 251 | } 252 | 253 | gen impl Decode for @Self { 254 | fn decode(r: &mut impl std::io::Read) -> Result { 255 | DecodeWithDiscriminant::decode_without_discriminant(r) 256 | } 257 | } 258 | }, 259 | ) 260 | } 261 | None => ( 262 | quote! {}, 263 | quote! { 264 | gen impl Decode for @Self { 265 | fn decode(r: &mut impl std::io::Read) -> Result { 266 | #decode 267 | } 268 | } 269 | }, 270 | ), 271 | } 272 | } 273 | }; 274 | 275 | let encode_body = s.each(|bi| { 276 | quote! { 277 | Encode::encode(#bi, w)? 278 | } 279 | }); 280 | 281 | s.gen_impl(quote! { 282 | use crate::io::{Encode, Decode, DecodeWithDiscriminant, DecodeError, PathItem}; 283 | 284 | gen impl Encode for @Self { 285 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 286 | #encode_discriminant; 287 | match *self { #encode_body } 288 | Ok(()) 289 | } 290 | } 291 | 292 | #decode 293 | }) 294 | } 295 | 296 | fn wasmbin_countable_derive(s: Structure) -> proc_macro2::TokenStream { 297 | s.gen_impl(quote! { 298 | gen impl crate::builtins::WasmbinCountable for @Self {} 299 | }) 300 | } 301 | 302 | fn wasmbin_visit_derive(mut s: Structure) -> proc_macro2::TokenStream { 303 | s.bind_with(|_| synstructure::BindStyle::Move); 304 | 305 | fn generate_visit_body(s: &Structure, method: proc_macro2::TokenStream) -> proc_macro2::TokenStream { 306 | let body = s.each_variant(|v| { 307 | let res = v.bindings().iter().enumerate().map(|(i, bi)| { 308 | let res = quote!(Visit::#method(#bi, f)); 309 | track_err_in_field(res, v, bi.ast(), i) 310 | }); 311 | let mut res = quote!(#(#res?;)*); 312 | res = catch_expr(res, quote!(VisitError)); 313 | res = track_err_in_variant(res, v); 314 | quote!(#res?) 315 | }); 316 | quote!( 317 | match self { #body } 318 | Ok(()) 319 | ) 320 | } 321 | 322 | let visit_children_body = generate_visit_body(&s, quote!(visit_child)); 323 | 324 | let visit_children_mut_body = generate_visit_body(&s, quote!(visit_child_mut)); 325 | 326 | s.gen_impl(quote! { 327 | use crate::visit::{Visit, VisitError}; 328 | use crate::io::PathItem; 329 | 330 | gen impl Visit for @Self where Self: 'static { 331 | fn visit_children<'a, VisitT: 'static, VisitE, VisitF: FnMut(&'a VisitT) -> Result<(), VisitE>>(&'a self, f: &mut VisitF) -> Result<(), VisitError> { 332 | #visit_children_body 333 | } 334 | 335 | fn visit_children_mut Result<(), VisitE>>(&mut self, f: &mut VisitF) -> Result<(), VisitError> { 336 | #visit_children_mut_body 337 | } 338 | } 339 | }) 340 | } 341 | 342 | #[proc_macro_attribute] 343 | pub fn wasmbin_discriminants( 344 | _attr: proc_macro::TokenStream, 345 | input: proc_macro::TokenStream, 346 | ) -> proc_macro::TokenStream { 347 | let mut input: syn::DeriveInput = syn::parse(input).unwrap(); 348 | let e = match &mut input.data { 349 | syn::Data::Enum(e) => e, 350 | _ => panic!("This attribute can only be used on enums"), 351 | }; 352 | let mut seen_non_units = false; 353 | for v in &mut e.variants { 354 | match v.fields { 355 | syn::Fields::Unit => {} 356 | _ => seen_non_units = true, 357 | } 358 | #[cfg(not(feature = "nightly"))] 359 | { 360 | if let Some((_, discriminant)) = v.discriminant.take() { 361 | v.attrs 362 | .push(syn::parse_quote!(#[wasmbin(discriminant = #discriminant)])); 363 | } 364 | } 365 | } 366 | assert!( 367 | seen_non_units, 368 | "Attribute shouldn't be used on C-like enums" 369 | ); 370 | input.into_token_stream().into() 371 | } 372 | 373 | decl_derive!([Wasmbin, attributes(wasmbin)] => wasmbin_derive); 374 | decl_derive!([WasmbinCountable] => wasmbin_countable_derive); 375 | decl_derive!([Visit] => wasmbin_visit_derive); 376 | -------------------------------------------------------------------------------- /examples/dump.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::fs::File; 16 | use std::io::{BufReader, Seek, SeekFrom}; 17 | use structopt::StructOpt; 18 | use wasmbin::io::DecodeError; 19 | use wasmbin::sections::{Kind, Section}; 20 | use wasmbin::visit::{Visit, VisitError}; 21 | use wasmbin::Module; 22 | 23 | #[derive(StructOpt)] 24 | enum DumpSection { 25 | All, 26 | Custom { name: String }, 27 | Type, 28 | Import, 29 | Function, 30 | Table, 31 | Memory, 32 | Global, 33 | Export, 34 | Start, 35 | Element, 36 | DataCount, 37 | Code, 38 | Data, 39 | } 40 | 41 | #[derive(StructOpt)] 42 | struct DumpOpts { 43 | filename: String, 44 | #[structopt(long)] 45 | include_raw: bool, 46 | #[structopt(flatten)] 47 | section: DumpSection, 48 | } 49 | 50 | fn unlazify_with_opt(wasm: &mut T, include_raw: bool) -> Result<(), DecodeError> { 51 | let res = if include_raw { 52 | wasm.visit(|()| {}) 53 | } else { 54 | wasm.visit_mut(|()| {}) 55 | }; 56 | match res { 57 | Ok(()) => Ok(()), 58 | Err(err) => match err { 59 | VisitError::LazyDecode(err) => Err(err), 60 | VisitError::Custom(err) => match err {}, 61 | }, 62 | } 63 | } 64 | 65 | fn main() { 66 | let opts = DumpOpts::from_args(); 67 | let f = File::open(opts.filename).unwrap(); 68 | let mut f = BufReader::new(f); 69 | let mut m = Module::decode_from(&mut f).unwrap_or_else(|err| { 70 | panic!( 71 | "Parsing error at offset 0x{:08X}: {}", 72 | f.seek(SeekFrom::Current(0)).unwrap(), 73 | err 74 | ) 75 | }); 76 | let filter: Box bool> = match opts.section { 77 | DumpSection::All => Box::new(|_s: &Section| true) as _, 78 | DumpSection::Custom { name } => Box::new(move |s: &Section| { 79 | let other_name = match s { 80 | Section::Custom(s) => match s.try_contents() { 81 | Ok(section) => Some(section.name()), 82 | Err(err) => { 83 | eprintln!("Warning: could not parse a custom section. {}", err); 84 | None 85 | } 86 | }, 87 | _ => None, 88 | }; 89 | Some(name.as_str()) == other_name 90 | }), 91 | DumpSection::Type => Box::new(|s| s.kind() == Kind::Type), 92 | DumpSection::Import => Box::new(|s| s.kind() == Kind::Import), 93 | DumpSection::Function => Box::new(|s| s.kind() == Kind::Function), 94 | DumpSection::Table => Box::new(|s| s.kind() == Kind::Table), 95 | DumpSection::Memory => Box::new(|s| s.kind() == Kind::Memory), 96 | DumpSection::Global => Box::new(|s| s.kind() == Kind::Global), 97 | DumpSection::Export => Box::new(|s| s.kind() == Kind::Export), 98 | DumpSection::Start => Box::new(|s| s.kind() == Kind::Start), 99 | DumpSection::Element => Box::new(|s| s.kind() == Kind::Element), 100 | DumpSection::DataCount => Box::new(|s| s.kind() == Kind::DataCount), 101 | DumpSection::Code => Box::new(|s| s.kind() == Kind::Code), 102 | DumpSection::Data => Box::new(|s| s.kind() == Kind::Data), 103 | }; 104 | let mut count = 0; 105 | for s in m.sections.iter_mut().filter(|s| filter(s)) { 106 | count += 1; 107 | unlazify_with_opt(s, opts.include_raw).unwrap(); 108 | println!("{:#?}", s); 109 | } 110 | println!("Found {} sections.", count); 111 | } 112 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "wasmbin-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = "0.4.2" 14 | 15 | [dependencies.wasmbin] 16 | path = ".." 17 | default-features = false 18 | 19 | # Prevent this from interfering with workspaces 20 | [workspace] 21 | members = ["."] 22 | 23 | [[bin]] 24 | name = "decode" 25 | path = "fuzz_targets/decode.rs" 26 | 27 | [[bin]] 28 | name = "roundtrip" 29 | path = "fuzz_targets/roundtrip.rs" 30 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/decode.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![no_main] 16 | use libfuzzer_sys::fuzz_target; 17 | 18 | use wasmbin::Module; 19 | 20 | fuzz_target!(|data: &[u8]| { 21 | // Just check that we don't crash anywhere trying to read the data. 22 | let _ = Module::decode_from(data); 23 | }); 24 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![no_main] 16 | use libfuzzer_sys::fuzz_target; 17 | 18 | use wasmbin::io::DecodeError; 19 | use wasmbin::visit::{Visit, VisitError}; 20 | use wasmbin::Module; 21 | 22 | fn unlazify(wasm: T) -> Result { 23 | match wasm.visit(|()| {}) { 24 | Ok(()) => Ok(wasm), 25 | Err(err) => match err { 26 | VisitError::LazyDecode(err) => Err(err), 27 | VisitError::Custom(err) => match err {}, 28 | }, 29 | } 30 | } 31 | 32 | fuzz_target!(|module: Module| { 33 | // We're using Vec as I/O destination, so this should never fail, except 34 | // if the module itself is malformed. 35 | // In that case, bail out. 36 | let encoded = match module.encode_into(Vec::new()) { 37 | Ok(encoded) => encoded, 38 | Err(_) => return, 39 | }; 40 | // Check that we can re-decoded encoded data back. 41 | let decoded = Module::decode_from(encoded.as_slice()) 42 | .and_then(unlazify) 43 | .unwrap(); 44 | // Ensure that re-decoded module is equivalent to the original. 45 | assert_eq!(module, decoded); 46 | // Check that encoding again results in a deterministic output. 47 | let encoded2 = decoded.encode_into(Vec::new()).unwrap(); 48 | assert_eq!(encoded, encoded2); 49 | }); 50 | -------------------------------------------------------------------------------- /src/builtins/blob.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::builtins::{Lazy, WasmbinCountable}; 16 | use crate::io::{Decode, DecodeError, DecodeErrorKind, Encode}; 17 | use crate::visit::Visit; 18 | use arbitrary::Arbitrary; 19 | 20 | #[derive(Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 21 | pub struct RawBlob> { 22 | pub contents: T, 23 | } 24 | 25 | impl> Encode for RawBlob { 26 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 27 | let bytes = self.contents.as_ref(); 28 | bytes.len().encode(w)?; 29 | bytes.encode(w) 30 | } 31 | } 32 | 33 | impl Decode for RawBlob { 34 | fn decode(r: &mut impl std::io::Read) -> Result { 35 | let size = u32::decode(r)?; 36 | let mut taken = std::io::Read::take(r, size.into()); 37 | let contents = T::decode(&mut taken)?; 38 | if taken.limit() != 0 { 39 | return Err(DecodeErrorKind::UnrecognizedData.into()); 40 | } 41 | Ok(RawBlob { contents }) 42 | } 43 | } 44 | 45 | impl> AsRef<[u8]> for RawBlob { 46 | fn as_ref(&self) -> &[u8] { 47 | self.contents.as_ref() 48 | } 49 | } 50 | 51 | #[derive(Default, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 52 | pub struct Blob { 53 | contents: Lazy, 54 | } 55 | 56 | impl Blob { 57 | pub fn try_contents(&self) -> Result<&T, DecodeError> { 58 | self.contents.try_contents() 59 | } 60 | 61 | pub fn try_contents_mut(&mut self) -> Result<&mut T, DecodeError> { 62 | self.contents.try_contents_mut() 63 | } 64 | 65 | pub fn try_into_contents(self) -> Result { 66 | self.contents.try_into_contents() 67 | } 68 | } 69 | 70 | impl std::fmt::Debug for Blob { 71 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 72 | f.write_str("Blob(")?; 73 | self.contents.fmt(f)?; 74 | f.write_str(")") 75 | } 76 | } 77 | 78 | impl Encode for Blob { 79 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 80 | let value = match self.contents.try_as_raw() { 81 | Ok(raw) => return RawBlob { contents: raw }.encode(w), 82 | Err(value) => value, 83 | }; 84 | let mut buf; 85 | buf = Vec::new(); 86 | value.encode(&mut buf)?; 87 | RawBlob { contents: buf }.encode(w) 88 | } 89 | } 90 | 91 | impl Decode for Blob { 92 | fn decode(r: &mut impl std::io::Read) -> Result { 93 | let contents: Lazy = RawBlob::decode(r)?.contents; 94 | Ok(Self { contents }) 95 | } 96 | } 97 | 98 | impl WasmbinCountable for Blob {} 99 | 100 | impl From for Blob { 101 | fn from(value: T) -> Self { 102 | Blob { 103 | contents: value.into(), 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/builtins/boolean.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::io::DecodeErrorKind; 16 | use crate::visit::Visit; 17 | 18 | encode_decode_as!(bool, { 19 | false <=> 0_u8, 20 | true <=> 1_u8, 21 | }, |discriminant| { 22 | Err(DecodeErrorKind::UnsupportedDiscriminant { ty: "bool", discriminant: discriminant.into() }.into()) 23 | }); 24 | 25 | impl Visit for bool {} 26 | -------------------------------------------------------------------------------- /src/builtins/collections.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::io::{Decode, DecodeError, Encode, PathItem}; 16 | 17 | pub use wasmbin_derive::WasmbinCountable; 18 | pub trait WasmbinCountable {} 19 | 20 | impl Encode for [T] { 21 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 22 | self.len().encode(w)?; 23 | for item in self { 24 | item.encode(w)?; 25 | } 26 | Ok(()) 27 | } 28 | } 29 | 30 | impl Encode for Vec 31 | where 32 | [T]: Encode, 33 | { 34 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 35 | self.as_slice().encode(w) 36 | } 37 | } 38 | 39 | impl Decode for Vec { 40 | fn decode(r: &mut impl std::io::Read) -> Result { 41 | let count = usize::decode(r)?; 42 | (0..count) 43 | .map(|i| T::decode(r).map_err(move |err| err.in_path(PathItem::Index(i)))) 44 | .collect() 45 | } 46 | } 47 | 48 | impl_visit_for_iter!(Vec); 49 | 50 | impl crate::visit::Visit for Option { 51 | fn visit_children<'a, VisitT: 'static, E, F: FnMut(&'a VisitT) -> Result<(), E>>( 52 | &'a self, 53 | f: &mut F, 54 | ) -> Result<(), crate::visit::VisitError> { 55 | if let Some(v) = self { 56 | v.visit_child(f)?; 57 | } 58 | Ok(()) 59 | } 60 | 61 | fn visit_children_mut Result<(), E>>( 62 | &mut self, 63 | f: &mut F, 64 | ) -> Result<(), crate::visit::VisitError> { 65 | if let Some(v) = self { 66 | v.visit_child_mut(f)?; 67 | } 68 | Ok(()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/builtins/floats.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::io::{Decode, DecodeError, Encode, Wasmbin}; 16 | use crate::visit::Visit; 17 | use arbitrary::Arbitrary; 18 | 19 | /// A wrapper around floats that treats `NaN`s as equal. 20 | /// 21 | /// This is useful in instruction context, where we don't care 22 | /// about general floating number rules. 23 | #[derive(Wasmbin, Debug, Arbitrary, Clone, Visit)] 24 | pub struct FloatConst { 25 | pub value: F, 26 | } 27 | 28 | impl Eq for FloatConst where Self: PartialEq {} 29 | 30 | macro_rules! def_float { 31 | ($ty:ident) => { 32 | impl Encode for $ty { 33 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 34 | self.to_le_bytes().encode(w) 35 | } 36 | } 37 | 38 | impl Decode for $ty { 39 | fn decode(r: &mut impl std::io::Read) -> Result { 40 | Decode::decode(r).map($ty::from_le_bytes) 41 | } 42 | } 43 | 44 | impl Visit for $ty {} 45 | 46 | impl PartialEq for FloatConst<$ty> { 47 | fn eq(&self, other: &Self) -> bool { 48 | self.value == other.value || self.value.is_nan() && other.value.is_nan() 49 | } 50 | } 51 | 52 | impl std::hash::Hash for FloatConst<$ty> { 53 | fn hash(&self, h: &mut H) { 54 | h.write(&self.value.to_ne_bytes()) 55 | } 56 | } 57 | }; 58 | } 59 | 60 | def_float!(f32); 61 | def_float!(f64); 62 | -------------------------------------------------------------------------------- /src/builtins/integers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::io::{Decode, DecodeError, DecodeErrorKind, Encode}; 16 | use crate::visit::Visit; 17 | use std::convert::TryFrom; 18 | 19 | macro_rules! def_byte_array { 20 | ($count:literal) => { 21 | impl Encode for [u8; $count] { 22 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 23 | w.write_all(self) 24 | } 25 | } 26 | 27 | impl Decode for [u8; $count] { 28 | fn decode(r: &mut impl std::io::Read) -> Result { 29 | let mut dest = [0_u8; $count]; 30 | r.read_exact(&mut dest)?; 31 | Ok(dest) 32 | } 33 | } 34 | }; 35 | } 36 | 37 | def_byte_array!(4); 38 | def_byte_array!(8); 39 | def_byte_array!(16); 40 | 41 | impl Encode for u8 { 42 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 43 | std::slice::from_ref(self).encode(w) 44 | } 45 | } 46 | 47 | impl Encode for [u8] { 48 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 49 | w.write_all(self) 50 | } 51 | } 52 | 53 | impl Decode for u8 { 54 | fn decode(r: &mut impl std::io::Read) -> Result { 55 | let mut dest = 0; 56 | r.read_exact(std::slice::from_mut(&mut dest))?; 57 | Ok(dest) 58 | } 59 | } 60 | 61 | impl Decode for Option { 62 | fn decode(r: &mut impl std::io::Read) -> Result { 63 | let mut dest = 0; 64 | loop { 65 | return match r.read(std::slice::from_mut(&mut dest)) { 66 | Ok(0) => Ok(None), 67 | Ok(_) => Ok(Some(dest)), 68 | Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue, 69 | Err(err) => Err(DecodeErrorKind::Io(err).into()), 70 | }; 71 | } 72 | } 73 | } 74 | 75 | impl Decode for Vec { 76 | fn decode(r: &mut impl std::io::Read) -> Result { 77 | let mut dest = Vec::new(); 78 | r.read_to_end(&mut dest)?; 79 | Ok(dest) 80 | } 81 | } 82 | 83 | impl Visit for u8 {} 84 | 85 | macro_rules! def_integer { 86 | ($ty:ident, $leb128_method:ident) => { 87 | impl Encode for $ty { 88 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 89 | leb128::write::$leb128_method(w, (*self).into()).map(|_| ()) 90 | } 91 | } 92 | 93 | impl Decode for $ty { 94 | fn decode(r: &mut impl std::io::Read) -> Result { 95 | const LIMIT: u64 = (std::mem::size_of::<$ty>() * 8 / 7) as u64 + 1; 96 | 97 | let mut r = std::io::Read::take(r, LIMIT); 98 | let as_64 = leb128::read::$leb128_method(&mut r)?; 99 | let res = Self::try_from(as_64)?; 100 | 101 | Ok(res) 102 | } 103 | } 104 | 105 | impl Visit for $ty {} 106 | }; 107 | } 108 | 109 | def_integer!(u32, unsigned); 110 | def_integer!(i32, signed); 111 | def_integer!(u64, unsigned); 112 | def_integer!(i64, signed); 113 | 114 | impl Encode for usize { 115 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 116 | match u32::try_from(*self) { 117 | Ok(v) => v.encode(w), 118 | Err(err) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, err)), 119 | } 120 | } 121 | } 122 | 123 | impl Decode for usize { 124 | fn decode(r: &mut impl std::io::Read) -> Result { 125 | Ok(usize::try_from(u32::decode(r)?)?) 126 | } 127 | } 128 | 129 | impl Visit for usize {} 130 | -------------------------------------------------------------------------------- /src/builtins/lazy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::builtins::WasmbinCountable; 16 | use crate::io::{Decode, DecodeError, DecodeErrorKind, Encode}; 17 | use crate::visit::{Visit, VisitError}; 18 | use arbitrary::Arbitrary; 19 | use custom_debug::Debug as CustomDebug; 20 | use once_cell::sync::OnceCell; 21 | use std::hash::Hash; 22 | 23 | #[derive(CustomDebug, Clone)] 24 | enum LazyStatus { 25 | FromInput { 26 | #[debug(with = "custom_debug::hexbuf_str")] 27 | raw: Vec, 28 | parsed: OnceCell, 29 | }, 30 | Output { 31 | value: T, 32 | }, 33 | } 34 | 35 | #[derive(Clone)] 36 | pub struct Lazy { 37 | status: LazyStatus, 38 | } 39 | 40 | impl Lazy { 41 | pub fn from_raw(raw: Vec) -> Self { 42 | Lazy { 43 | status: LazyStatus::FromInput { 44 | raw, 45 | parsed: OnceCell::new(), 46 | }, 47 | } 48 | } 49 | 50 | pub fn try_as_raw(&self) -> Result<&[u8], &T> { 51 | match &self.status { 52 | LazyStatus::FromInput { raw, .. } => Ok(raw), 53 | LazyStatus::Output { value } => Err(value), 54 | } 55 | } 56 | } 57 | 58 | impl From for Lazy { 59 | fn from(value: T) -> Self { 60 | Lazy { 61 | status: LazyStatus::Output { value }, 62 | } 63 | } 64 | } 65 | 66 | impl Default for Lazy { 67 | fn default() -> Self { 68 | T::default().into() 69 | } 70 | } 71 | 72 | impl Encode for Lazy { 73 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 74 | match &self.status { 75 | LazyStatus::FromInput { raw, .. } => raw.encode(w), 76 | LazyStatus::Output { value } => value.encode(w), 77 | } 78 | } 79 | } 80 | 81 | impl Decode for Lazy { 82 | fn decode(r: &mut impl std::io::Read) -> Result { 83 | Vec::decode(r).map(Self::from_raw) 84 | } 85 | } 86 | 87 | impl std::fmt::Debug for Lazy { 88 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 89 | self.status.fmt(f) 90 | } 91 | } 92 | 93 | fn decode_raw(mut raw: &[u8]) -> Result { 94 | let value = T::decode(&mut raw)?; 95 | if !raw.is_empty() { 96 | return Err(DecodeErrorKind::UnrecognizedData.into()); 97 | } 98 | Ok(value) 99 | } 100 | 101 | impl Lazy { 102 | pub fn try_contents(&self) -> Result<&T, DecodeError> { 103 | match &self.status { 104 | LazyStatus::FromInput { raw, parsed } => parsed.get_or_try_init(|| decode_raw(raw)), 105 | LazyStatus::Output { value } => Ok(value), 106 | } 107 | } 108 | 109 | pub fn try_contents_mut(&mut self) -> Result<&mut T, DecodeError> { 110 | if let LazyStatus::FromInput { raw, parsed } = &mut self.status { 111 | // We can't trust input and output to match once we obtained a mutable reference, 112 | // so get the value and change the status to just Output. 113 | let parsed = std::mem::replace(parsed, OnceCell::new()); 114 | self.status = LazyStatus::Output { 115 | value: match parsed.into_inner() { 116 | Some(value) => value, 117 | None => decode_raw(raw)?, 118 | }, 119 | }; 120 | } 121 | if let LazyStatus::Output { value } = &mut self.status { 122 | return Ok(value); 123 | } 124 | unsafe { std::hint::unreachable_unchecked() } 125 | } 126 | 127 | pub fn try_into_contents(self) -> Result { 128 | match self.status { 129 | LazyStatus::FromInput { raw, parsed } => match parsed.into_inner() { 130 | Some(value) => Ok(value), 131 | None => decode_raw(&raw), 132 | }, 133 | LazyStatus::Output { value } => Ok(value), 134 | } 135 | } 136 | } 137 | 138 | impl PartialEq for Lazy { 139 | fn eq(&self, other: &Self) -> bool { 140 | if let (LazyStatus::FromInput { raw: raw1, .. }, LazyStatus::FromInput { raw: raw2, .. }) = 141 | (&self.status, &other.status) 142 | { 143 | if raw1 == raw2 { 144 | return true; 145 | } 146 | } 147 | if let (Ok(value1), Ok(value2)) = (self.try_contents(), other.try_contents()) { 148 | return value1 == value2; 149 | } 150 | false 151 | } 152 | } 153 | 154 | impl Eq for Lazy {} 155 | 156 | impl Hash for Lazy { 157 | fn hash(&self, state: &mut H) { 158 | self.try_contents().ok().hash(state); 159 | } 160 | } 161 | 162 | impl<'a, T: Arbitrary<'a>> Arbitrary<'a> for Lazy { 163 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 164 | T::arbitrary(u).map(Self::from) 165 | } 166 | 167 | fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result { 168 | T::arbitrary_take_rest(u).map(Self::from) 169 | } 170 | 171 | fn size_hint(depth: usize) -> (usize, Option) { 172 | T::size_hint(depth) 173 | } 174 | } 175 | 176 | impl WasmbinCountable for Lazy {} 177 | 178 | impl Visit for Lazy { 179 | fn visit_children<'a, VisitT: 'static, E, F: FnMut(&'a VisitT) -> Result<(), E>>( 180 | &'a self, 181 | f: &mut F, 182 | ) -> Result<(), VisitError> { 183 | match self.try_contents() { 184 | Ok(contents) => contents.visit_child(f), 185 | Err(err) => Err(VisitError::LazyDecode(err)), 186 | } 187 | } 188 | 189 | fn visit_children_mut Result<(), E>>( 190 | &mut self, 191 | f: &mut F, 192 | ) -> Result<(), VisitError> { 193 | match self.try_contents_mut() { 194 | Ok(contents) => contents.visit_child_mut(f), 195 | Err(err) => Err(VisitError::LazyDecode(err)), 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/builtins/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod blob; 16 | mod boolean; 17 | mod collections; 18 | mod floats; 19 | mod integers; 20 | mod lazy; 21 | mod strings; 22 | 23 | pub use blob::{Blob, RawBlob}; 24 | pub use collections::WasmbinCountable; 25 | pub use floats::FloatConst; 26 | pub use lazy::Lazy; 27 | -------------------------------------------------------------------------------- /src/builtins/strings.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::RawBlob; 16 | use crate::io::{Decode, DecodeError, Encode}; 17 | use crate::visit::Visit; 18 | 19 | impl Encode for str { 20 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 21 | RawBlob { contents: self }.encode(w) 22 | } 23 | } 24 | 25 | impl Encode for String { 26 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 27 | self.as_str().encode(w) 28 | } 29 | } 30 | 31 | impl Decode for String { 32 | fn decode(r: &mut impl std::io::Read) -> Result { 33 | Ok(String::from_utf8(RawBlob::decode(r)?.contents)?) 34 | } 35 | } 36 | 37 | impl Visit for String {} 38 | -------------------------------------------------------------------------------- /src/indices.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::builtins::WasmbinCountable; 16 | use crate::io::Wasmbin; 17 | use crate::visit::Visit; 18 | use arbitrary::Arbitrary; 19 | 20 | macro_rules! newtype_id { 21 | ($name:ident) => { 22 | #[derive(PartialEq, Eq, Clone, Copy, Wasmbin, WasmbinCountable, Arbitrary, Hash, Visit)] 23 | #[repr(transparent)] 24 | pub struct $name { 25 | pub index: u32, 26 | } 27 | 28 | impl From for $name { 29 | fn from(index: u32) -> Self { 30 | Self { index } 31 | } 32 | } 33 | 34 | impl From<$name> for u32 { 35 | fn from(id: $name) -> u32 { 36 | id.index 37 | } 38 | } 39 | 40 | impl std::fmt::Debug for $name { 41 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 42 | write!( 43 | f, 44 | "{}#{}", 45 | &stringify!($name)[..stringify!($name).len() - "Id".len()], 46 | self.index 47 | ) 48 | } 49 | } 50 | }; 51 | } 52 | 53 | newtype_id!(DataId); 54 | newtype_id!(ElemId); 55 | newtype_id!(FuncId); 56 | newtype_id!(GlobalId); 57 | newtype_id!(LabelId); 58 | newtype_id!(LocalId); 59 | newtype_id!(MemId); 60 | newtype_id!(TableId); 61 | newtype_id!(TypeId); 62 | -------------------------------------------------------------------------------- /src/instructions/misc.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::indices::{DataId, ElemId, MemId, TableId}; 16 | use crate::io::Wasmbin; 17 | use crate::visit::Visit; 18 | use arbitrary::Arbitrary; 19 | 20 | #[crate::wasmbin_discriminants] 21 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 22 | #[repr(u32)] 23 | pub enum Misc { 24 | I32TruncSatF32S = 0x00, 25 | I32TruncSatF32U = 0x01, 26 | I32TruncSatF64S = 0x02, 27 | I32TruncSatF64U = 0x03, 28 | I64TruncSatF32S = 0x04, 29 | I64TruncSatF32U = 0x05, 30 | I64TruncSatF64S = 0x06, 31 | I64TruncSatF64U = 0x07, 32 | MemoryInit { data: DataId, mem: MemId } = 0x08, 33 | DataDrop(DataId) = 0x09, 34 | MemoryCopy { dest: MemId, src: MemId } = 0x0A, 35 | MemoryFill(MemId) = 0x0B, 36 | TableInit { elem: ElemId, table: TableId } = 0x0C, 37 | ElemDrop(ElemId) = 0x0D, 38 | TableCopy { dest: TableId, src: TableId } = 0x0E, 39 | TableGrow(TableId) = 0x0F, 40 | TableSize(TableId) = 0x10, 41 | TableFill(TableId) = 0x11, 42 | } 43 | -------------------------------------------------------------------------------- /src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::builtins::FloatConst; 16 | use crate::indices::{FuncId, GlobalId, LabelId, LocalId, MemId, TableId, TypeId}; 17 | use crate::io::{Decode, DecodeError, DecodeWithDiscriminant, Encode, PathItem, Wasmbin}; 18 | use crate::types::{BlockType, RefType, ValueType}; 19 | use crate::visit::Visit; 20 | use crate::wasmbin_discriminants; 21 | use arbitrary::Arbitrary; 22 | use thiserror::Error; 23 | 24 | const OP_CODE_BLOCK_START: u8 = 0x02; 25 | const OP_CODE_LOOP_START: u8 = 0x03; 26 | const OP_CODE_IF_START: u8 = 0x04; 27 | const OP_CODE_END: u8 = 0x0B; 28 | 29 | #[derive(Debug, Error)] 30 | #[error("Mismatched block depth")] 31 | struct DepthError; 32 | 33 | impl From for std::io::Error { 34 | fn from(err: DepthError) -> Self { 35 | Self::new(std::io::ErrorKind::InvalidData, err) 36 | } 37 | } 38 | 39 | #[derive(Default)] 40 | struct DepthTracker { 41 | depth: u32, 42 | } 43 | 44 | impl DepthTracker { 45 | pub fn inc(&mut self) { 46 | self.depth += 1; 47 | } 48 | 49 | // Returns a bool indicating whether to continue, or an error state. 50 | pub fn try_dec(&mut self) -> Result<(), DepthError> { 51 | self.depth = self.depth.checked_sub(1).ok_or(DepthError)?; 52 | Ok(()) 53 | } 54 | 55 | pub fn assert_end(self) -> Result<(), DepthError> { 56 | match self.depth { 57 | 0 => Ok(()), 58 | _ => Err(DepthError), 59 | } 60 | } 61 | } 62 | 63 | impl Encode for [Instruction] { 64 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 65 | let mut depth_tracker = DepthTracker::default(); 66 | for instr in self { 67 | match instr { 68 | Instruction::BlockStart(_) 69 | | Instruction::LoopStart(_) 70 | | Instruction::IfStart(_) => { 71 | depth_tracker.inc(); 72 | } 73 | Instruction::End => { 74 | depth_tracker.try_dec()?; 75 | } 76 | _ => {} 77 | } 78 | instr.encode(w)?; 79 | } 80 | depth_tracker.assert_end()?; 81 | OP_CODE_END.encode(w) 82 | } 83 | } 84 | 85 | impl Decode for Vec { 86 | fn decode(r: &mut impl std::io::Read) -> Result { 87 | let mut res = Vec::new(); 88 | let mut depth_tracker = DepthTracker::default(); 89 | loop { 90 | let op_code = u8::decode(r)?; 91 | match op_code { 92 | OP_CODE_BLOCK_START | OP_CODE_LOOP_START | OP_CODE_IF_START => { 93 | depth_tracker.inc(); 94 | } 95 | OP_CODE_END => { 96 | if depth_tracker.try_dec().is_err() { 97 | break; 98 | } 99 | } 100 | _ => {} 101 | } 102 | let i = res.len(); 103 | res.push( 104 | Instruction::decode_with_discriminant(op_code, r) 105 | .map_err(move |err| err.in_path(PathItem::Index(i)))?, 106 | ); 107 | } 108 | Ok(res) 109 | } 110 | } 111 | 112 | pub type Expression = Vec; 113 | 114 | impl crate::builtins::WasmbinCountable for Expression {} 115 | 116 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 117 | pub struct MemArg { 118 | pub align: u32, 119 | pub offset: u32, 120 | } 121 | 122 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 123 | pub struct CallIndirect { 124 | ty: TypeId, 125 | table: TableId, 126 | } 127 | 128 | #[wasmbin_discriminants] 129 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 130 | #[repr(u8)] 131 | pub enum Instruction { 132 | Unreachable = 0x00, 133 | Nop = 0x01, 134 | BlockStart(BlockType) = OP_CODE_BLOCK_START, 135 | LoopStart(BlockType) = OP_CODE_LOOP_START, 136 | IfStart(BlockType) = OP_CODE_IF_START, 137 | IfElse = 0x05, 138 | End = OP_CODE_END, 139 | Br(LabelId) = 0x0C, 140 | BrIf(LabelId) = 0x0D, 141 | BrTable { 142 | branches: Vec, 143 | otherwise: LabelId, 144 | } = 0x0E, 145 | Return = 0x0F, 146 | Call(FuncId) = 0x10, 147 | CallIndirect(CallIndirect) = 0x11, 148 | #[cfg(feature = "tail-call")] 149 | ReturnCall(FuncId) = 0x12, 150 | #[cfg(feature = "tail-call")] 151 | ReturnCallIndirect(CallIndirect) = 0x13, 152 | Drop = 0x1A, 153 | Select = 0x1B, 154 | SelectWithTypes(Vec) = 0x1C, 155 | LocalGet(LocalId) = 0x20, 156 | LocalSet(LocalId) = 0x21, 157 | LocalTee(LocalId) = 0x22, 158 | GlobalGet(GlobalId) = 0x23, 159 | GlobalSet(GlobalId) = 0x24, 160 | TableGet(TableId) = 0x25, 161 | TableSet(TableId) = 0x26, 162 | I32Load(MemArg) = 0x28, 163 | I64Load(MemArg) = 0x29, 164 | F32Load(MemArg) = 0x2A, 165 | F64Load(MemArg) = 0x2B, 166 | I32Load8S(MemArg) = 0x2C, 167 | I32Load8U(MemArg) = 0x2D, 168 | I32Load16S(MemArg) = 0x2E, 169 | I32Load16U(MemArg) = 0x2F, 170 | I64Load8S(MemArg) = 0x30, 171 | I64Load8U(MemArg) = 0x31, 172 | I64Load16S(MemArg) = 0x32, 173 | I64Load16U(MemArg) = 0x33, 174 | I64Load32S(MemArg) = 0x34, 175 | I64Load32U(MemArg) = 0x35, 176 | I32Store(MemArg) = 0x36, 177 | I64Store(MemArg) = 0x37, 178 | F32Store(MemArg) = 0x38, 179 | F64Store(MemArg) = 0x39, 180 | I32Store8(MemArg) = 0x3A, 181 | I32Store16(MemArg) = 0x3B, 182 | I64Store8(MemArg) = 0x3C, 183 | I64Store16(MemArg) = 0x3D, 184 | I64Store32(MemArg) = 0x3E, 185 | MemorySize(MemId) = 0x3F, 186 | MemoryGrow(MemId) = 0x40, 187 | I32Const(i32) = 0x41, 188 | I64Const(i64) = 0x42, 189 | F32Const(FloatConst) = 0x43, 190 | F64Const(FloatConst) = 0x44, 191 | I32Eqz = 0x45, 192 | I32Eq = 0x46, 193 | I32Ne = 0x47, 194 | I32LtS = 0x48, 195 | I32LtU = 0x49, 196 | I32GtS = 0x4A, 197 | I32GtU = 0x4B, 198 | I32LeS = 0x4C, 199 | I32LeU = 0x4D, 200 | I32GeS = 0x4E, 201 | I32GeU = 0x4F, 202 | I64Eqz = 0x50, 203 | I64Eq = 0x51, 204 | I64Ne = 0x52, 205 | I64LtS = 0x53, 206 | I64LtU = 0x54, 207 | I64GtS = 0x55, 208 | I64GtU = 0x56, 209 | I64LeS = 0x57, 210 | I64LeU = 0x58, 211 | I64GeS = 0x59, 212 | I64GeU = 0x5A, 213 | F32Eq = 0x5B, 214 | F32Ne = 0x5C, 215 | F32Lt = 0x5D, 216 | F32Gt = 0x5E, 217 | F32Le = 0x5F, 218 | F32Ge = 0x60, 219 | F64Eq = 0x61, 220 | F64Ne = 0x62, 221 | F64Lt = 0x63, 222 | F64Gt = 0x64, 223 | F64Le = 0x65, 224 | F64Ge = 0x66, 225 | I32Clz = 0x67, 226 | I32Ctz = 0x68, 227 | I32PopCnt = 0x69, 228 | I32Add = 0x6A, 229 | I32Sub = 0x6B, 230 | I32Mul = 0x6C, 231 | I32DivS = 0x6D, 232 | I32DivU = 0x6E, 233 | I32RemS = 0x6F, 234 | I32RemU = 0x70, 235 | I32And = 0x71, 236 | I32Or = 0x72, 237 | I32Xor = 0x73, 238 | I32Shl = 0x74, 239 | I32ShrS = 0x75, 240 | I32ShrU = 0x76, 241 | I32RotL = 0x77, 242 | I32RotR = 0x78, 243 | I64Clz = 0x79, 244 | I64Ctz = 0x7A, 245 | I64PopCnt = 0x7B, 246 | I64Add = 0x7C, 247 | I64Sub = 0x7D, 248 | I64Mul = 0x7E, 249 | I64DivS = 0x7F, 250 | I64DivU = 0x80, 251 | I64RemS = 0x81, 252 | I64RemU = 0x82, 253 | I64And = 0x83, 254 | I64Or = 0x84, 255 | I64Xor = 0x85, 256 | I64Shl = 0x86, 257 | I64ShrS = 0x87, 258 | I64ShrU = 0x88, 259 | I64RotL = 0x89, 260 | I64RotR = 0x8A, 261 | F32Abs = 0x8B, 262 | F32Neg = 0x8C, 263 | F32Ceil = 0x8D, 264 | F32Floor = 0x8E, 265 | F32Trunc = 0x8F, 266 | F32Nearest = 0x90, 267 | F32Sqrt = 0x91, 268 | F32Add = 0x92, 269 | F32Sub = 0x93, 270 | F32Mul = 0x94, 271 | F32Div = 0x95, 272 | F32Min = 0x96, 273 | F32Max = 0x97, 274 | F32CopySign = 0x98, 275 | F64Abs = 0x99, 276 | F64Neg = 0x9A, 277 | F64Ceil = 0x9B, 278 | F64Floor = 0x9C, 279 | F64Trunc = 0x9D, 280 | F64Nearest = 0x9E, 281 | F64Sqrt = 0x9F, 282 | F64Add = 0xA0, 283 | F64Sub = 0xA1, 284 | F64Mul = 0xA2, 285 | F64Div = 0xA3, 286 | F64Min = 0xA4, 287 | F64Max = 0xA5, 288 | F64CopySign = 0xA6, 289 | I32WrapI64 = 0xA7, 290 | I32TruncF32S = 0xA8, 291 | I32TruncF332U = 0xA9, 292 | I32TruncF64S = 0xAA, 293 | I32TruncF64U = 0xAB, 294 | I64ExtendI32S = 0xAC, 295 | I64ExtendI32U = 0xAD, 296 | I64TruncF32S = 0xAE, 297 | I64TruncF32U = 0xAF, 298 | I64TruncF64S = 0xB0, 299 | I64TruncF64U = 0xB1, 300 | F32ConvertI32S = 0xB2, 301 | F32ConvertI32U = 0xB3, 302 | F32ConvertI64S = 0xB4, 303 | F32ConvertI64U = 0xB5, 304 | F32DemoteF64 = 0xB6, 305 | F64ConvertI32S = 0xB7, 306 | F64ConvertI32U = 0xB8, 307 | F64ConvertI64S = 0xB9, 308 | F64ConvertI64U = 0xBA, 309 | F64PromoteF32 = 0xBB, 310 | I32ReinterpretF32 = 0xBC, 311 | I64ReinterpretF64 = 0xBD, 312 | F32ReinterpretI32 = 0xBE, 313 | F64ReinterpretI64 = 0xBF, 314 | I32Extend8S = 0xC0, 315 | I32Extend16S = 0xC1, 316 | I64Extend8S = 0xC2, 317 | I64Extend16S = 0xC3, 318 | I64Extend32S = 0xC4, 319 | RefNull(RefType) = 0xD0, 320 | RefIsNull = 0xD1, 321 | RefFunc(FuncId) = 0xD2, 322 | Misc(Misc) = 0xFC, 323 | #[cfg(feature = "simd")] 324 | SIMD(SIMD) = 0xFD, 325 | #[cfg(feature = "threads")] 326 | Atomic(Atomic) = 0xFE, 327 | } 328 | 329 | mod misc; 330 | 331 | pub use misc::Misc; 332 | 333 | #[cfg(feature = "simd")] 334 | pub mod simd; 335 | 336 | #[cfg(feature = "simd")] 337 | pub use simd::SIMD; 338 | 339 | #[cfg(feature = "threads")] 340 | pub mod threads; 341 | 342 | #[cfg(feature = "threads")] 343 | pub use threads::Atomic; 344 | -------------------------------------------------------------------------------- /src/instructions/simd.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::MemArg; 16 | use crate::io::{Decode, DecodeError, DecodeErrorKind, Encode, Wasmbin}; 17 | use crate::visit::Visit; 18 | use crate::wasmbin_discriminants; 19 | use arbitrary::Arbitrary; 20 | 21 | macro_rules! def_lane_idx { 22 | ($name:ident, $num:literal) => { 23 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Visit)] 24 | #[repr(transparent)] 25 | pub struct $name(u8); 26 | 27 | impl Encode for $name { 28 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 29 | self.0.encode(w) 30 | } 31 | } 32 | 33 | impl Decode for $name { 34 | fn decode(r: &mut impl std::io::Read) -> Result { 35 | let value = u8::decode(r)?; 36 | if value >= $num { 37 | return Err(DecodeErrorKind::UnsupportedDiscriminant { 38 | ty: stringify!($name), 39 | discriminant: value.into(), 40 | } 41 | .into()); 42 | } 43 | Ok(Self(value)) 44 | } 45 | } 46 | 47 | impl<'a> Arbitrary<'a> for $name { 48 | #[allow(clippy::range_minus_one)] 49 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 50 | u.int_in_range(0..=($num - 1)).map(Self) 51 | } 52 | } 53 | }; 54 | } 55 | 56 | def_lane_idx!(LaneIdx2, 2); 57 | def_lane_idx!(LaneIdx4, 4); 58 | def_lane_idx!(LaneIdx8, 8); 59 | def_lane_idx!(LaneIdx16, 16); 60 | def_lane_idx!(LaneIdx32, 32); 61 | 62 | impl Encode for [LaneIdx32; 16] { 63 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 64 | unsafe { &*(self as *const [LaneIdx32; 16] as *const [u8; 16]) }.encode(w) 65 | } 66 | } 67 | 68 | impl Decode for [LaneIdx32; 16] { 69 | fn decode(r: &mut impl std::io::Read) -> Result { 70 | let bytes = <[u8; 16]>::decode(r)?; 71 | for &b in &bytes { 72 | if b >= 32 { 73 | return Err(DecodeErrorKind::UnsupportedDiscriminant { 74 | ty: "LaneIdx32", 75 | discriminant: b.into(), 76 | } 77 | .into()); 78 | } 79 | } 80 | Ok(unsafe { std::mem::transmute::<[u8; 16], [LaneIdx32; 16]>(bytes) }) 81 | } 82 | } 83 | 84 | impl_visit_for_iter!([u8; 16]); 85 | impl_visit_for_iter!([LaneIdx32; 16]); 86 | 87 | #[wasmbin_discriminants] 88 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 89 | #[repr(u32)] 90 | pub enum SIMD { 91 | V128Load(MemArg) = 0x00, 92 | V128Load8x8S(MemArg) = 0x01, 93 | V128Load8x8U(MemArg) = 0x02, 94 | V128Load16x4S(MemArg) = 0x03, 95 | V128Load16x4U(MemArg) = 0x04, 96 | V128Load32x2S(MemArg) = 0x05, 97 | V128Load32x2U(MemArg) = 0x06, 98 | V128Load8Splat(MemArg) = 0x07, 99 | V128Load16Splat(MemArg) = 0x08, 100 | V128Load32Splat(MemArg) = 0x09, 101 | V128Load64Splat(MemArg) = 0x0A, 102 | V128Store(MemArg) = 0x0B, 103 | V128Const([u8; 16]) = 0x0C, 104 | I8x16Shuffle([LaneIdx32; 16]) = 0x0D, 105 | I8x16Swizzle = 0x0E, 106 | I8x16Splat = 0x0F, 107 | I16x8Splat = 0x10, 108 | I32x4Splat = 0x11, 109 | I64x2Splat = 0x12, 110 | F32x4Splat = 0x13, 111 | F64x2Splat = 0x14, 112 | I8x16ExtractLaneS(LaneIdx16) = 0x15, 113 | I8x16ExtractLaneU(LaneIdx16) = 0x16, 114 | I8x16ReplaceLane(LaneIdx16) = 0x17, 115 | I16x8ExtractLaneS(LaneIdx8) = 0x18, 116 | I16x8ExtractLaneU(LaneIdx8) = 0x19, 117 | I16x8ReplaceLane(LaneIdx8) = 0x1A, 118 | I32x4ExtractLane(LaneIdx4) = 0x1B, 119 | I32x4ReplaceLane(LaneIdx4) = 0x1C, 120 | I64x2ExtractLane(LaneIdx2) = 0x1D, 121 | I64x2ReplaceLane(LaneIdx2) = 0x1E, 122 | F32x4ExtractLane(LaneIdx4) = 0x1F, 123 | F32x4ReplaceLane(LaneIdx4) = 0x20, 124 | F64x2ExtractLane(LaneIdx2) = 0x21, 125 | F64x2ReplaceLane(LaneIdx2) = 0x22, 126 | I8x16Eq = 0x23, 127 | I8x16Ne = 0x24, 128 | I8x16LtS = 0x25, 129 | I8x16LtU = 0x26, 130 | I8x16GtS = 0x27, 131 | I8x16GtU = 0x28, 132 | I8x16LeS = 0x29, 133 | I8x16LeU = 0x2A, 134 | I8x16GeS = 0x2B, 135 | I8x16GeU = 0x2C, 136 | I16x8Eq = 0x2D, 137 | I16x8Ne = 0x2E, 138 | I16x8LtS = 0x2F, 139 | I16x8LtU = 0x30, 140 | I16x8GtS = 0x31, 141 | I16x8GtU = 0x32, 142 | I16x8LeS = 0x33, 143 | I16x8LeU = 0x34, 144 | I16x8GeS = 0x35, 145 | I16x8GeU = 0x36, 146 | I32x4Eq = 0x37, 147 | I32x4Ne = 0x38, 148 | I32x4LtS = 0x39, 149 | I32x4LtU = 0x3A, 150 | I32x4GtS = 0x3B, 151 | I32x4GtU = 0x3C, 152 | I32x4LeS = 0x3D, 153 | I32x4LeU = 0x3E, 154 | I32x4GeS = 0x3F, 155 | I32x4GeU = 0x40, 156 | F32x4Eq = 0x41, 157 | F32x4Ne = 0x42, 158 | F32x4Lt = 0x43, 159 | F32x4Gt = 0x44, 160 | F32x4Le = 0x45, 161 | F32x4Ge = 0x46, 162 | F64x2Eq = 0x47, 163 | F64x2Ne = 0x48, 164 | F64x2Lt = 0x49, 165 | F64x2Gt = 0x4A, 166 | F64x2Le = 0x4B, 167 | F64x2Ge = 0x4C, 168 | V128Not = 0x4D, 169 | V128And = 0x4E, 170 | V128Andnot = 0x4F, 171 | V128Or = 0x50, 172 | V128Xor = 0x51, 173 | V128Bitselect = 0x52, 174 | I8x16Abs = 0x60, 175 | I8x16Neg = 0x61, 176 | I8x16AllTrue = 0x63, 177 | I8x16Bitmask = 0x64, 178 | I8x16NarrowI16x8S = 0x65, 179 | I8x16NarrowI16x8U = 0x66, 180 | I8x16Shl = 0x6B, 181 | I8x16ShrS = 0x6C, 182 | I8x16ShrU = 0x6D, 183 | I8x16Add = 0x6E, 184 | I8x16AddSatS = 0x6F, 185 | I8x16AddSatU = 0x70, 186 | I8x16Sub = 0x71, 187 | I8x16SubSatS = 0x72, 188 | I8x16SubSatU = 0x73, 189 | I8x16MinS = 0x76, 190 | I8x16MinU = 0x77, 191 | I8x16MaxS = 0x78, 192 | I8x16MaxU = 0x79, 193 | I8x16AvgrU = 0x7B, 194 | I16x8Abs = 0x80, 195 | I16x8Neg = 0x81, 196 | I16x8AllTrue = 0x83, 197 | I16x8Bitmask = 0x84, 198 | I16x8NarrowI32x4S = 0x85, 199 | I16x8NarrowI32x4U = 0x86, 200 | I16x8ExtendLowI8x16S = 0x87, 201 | I16x8ExtendHighI8x16S = 0x88, 202 | I16x8ExtendLowI8x16U = 0x89, 203 | I16x8ExtendHighI8x16U = 0x8A, 204 | I16x8Shl = 0x8B, 205 | I16x8ShrS = 0x8C, 206 | I16x8ShrU = 0x8D, 207 | I16x8Add = 0x8E, 208 | I16x8AddSatS = 0x8F, 209 | I16x8AddSatU = 0x90, 210 | I16x8Sub = 0x91, 211 | I16x8SubSatS = 0x92, 212 | I16x8SubSatU = 0x93, 213 | I16x8Mul = 0x95, 214 | I16x8MinS = 0x96, 215 | I16x8MinU = 0x97, 216 | I16x8MaxS = 0x98, 217 | I16x8MaxU = 0x99, 218 | I16x8AvgrU = 0x9B, 219 | I32x4Abs = 0xA0, 220 | I32x4Neg = 0xA1, 221 | I32x4AllTrue = 0xA3, 222 | I32x4Bitmask = 0xA4, 223 | I32x4ExtendLowI16x8S = 0xA7, 224 | I32x4ExtendHighI16x8S = 0xA8, 225 | I32x4ExtendLowI16x8U = 0xA9, 226 | I32x4ExtendHighI16x8U = 0xAA, 227 | I32x4Shl = 0xAB, 228 | I32x4ShrS = 0xAC, 229 | I32x4ShrU = 0xAD, 230 | I32x4Add = 0xAE, 231 | I32x4Sub = 0xB1, 232 | I32x4Mul = 0xB5, 233 | I32x4MinS = 0xB6, 234 | I32x4MinU = 0xB7, 235 | I32x4MaxS = 0xB8, 236 | I32x4MaxU = 0xB9, 237 | I32x4DotI16x8S = 0xBA, 238 | I64x2Abs = 0xC0, 239 | I64x2Neg = 0xC1, 240 | I64x2Bitmask = 0xC4, 241 | I64x2ExtendLowI32x4S = 0xC7, 242 | I64x2ExtendHighI32x4S = 0xC8, 243 | I64x2ExtendLowI32x4U = 0xC9, 244 | I64x2ExtendHighI32x4U = 0xCA, 245 | I64x2Shl = 0xCB, 246 | I64x2ShrS = 0xCC, 247 | I64x2ShrU = 0xCD, 248 | I64x2Add = 0xCE, 249 | I64x2Sub = 0xD1, 250 | I64x2Mul = 0xD5, 251 | F32x4Ceil = 0x67, 252 | F32x4Floor = 0x68, 253 | F32x4Trunc = 0x69, 254 | F32x4Nearest = 0x6A, 255 | F64x2Ceil = 0x74, 256 | F64x2Floor = 0x75, 257 | F64x2Trunc = 0x7A, 258 | F64x2Nearest = 0x94, 259 | F32x4Abs = 0xE0, 260 | F32x4Neg = 0xE1, 261 | F32x4Sqrt = 0xE3, 262 | F32x4Add = 0xE4, 263 | F32x4Sub = 0xE5, 264 | F32x4Mul = 0xE6, 265 | F32x4Div = 0xE7, 266 | F32x4Min = 0xE8, 267 | F32x4Max = 0xE9, 268 | F32x4Pmin = 0xEA, 269 | F32x4Pmax = 0xEB, 270 | F64x2Abs = 0xEC, 271 | F64x2Neg = 0xED, 272 | F64x2Sqrt = 0xEF, 273 | F64x2Add = 0xF0, 274 | F64x2Sub = 0xF1, 275 | F64x2Mul = 0xF2, 276 | F64x2Div = 0xF3, 277 | F64x2Min = 0xF4, 278 | F64x2Max = 0xF5, 279 | F64x2Pmin = 0xF6, 280 | F64x2Pmax = 0xF7, 281 | I32x4TruncSatF32x4S = 0xF8, 282 | I32x4TruncSatF32x4U = 0xF9, 283 | F32x4ConvertI32x4S = 0xFA, 284 | F32x4ConvertI32x4U = 0xFB, 285 | V128Load32Zero(MemArg) = 0x5C, 286 | V128Load64Zero(MemArg) = 0x5D, 287 | I16x8ExtmulLowI8x16S = 0x9C, 288 | I16x8ExtmulHighI8x16S = 0x9D, 289 | I16x8ExtmulLowI8x16U = 0x9E, 290 | I16x8ExtmulHighI8x16U = 0x9F, 291 | I32x4ExtmulLowI16x8S = 0xBC, 292 | I32x4ExtmulHighI16x8S = 0xBD, 293 | I32x4ExtmulLowI16x8U = 0xBE, 294 | I32x4ExtmulHighI16x8U = 0xBF, 295 | I64x2ExtmulLowI32x4S = 0xDC, 296 | I64x2ExtmulHighI32x4S = 0xDD, 297 | I64x2ExtmulLowI32x4U = 0xDE, 298 | I64x2ExtmulHighI32x4U = 0xDF, 299 | I16x8Q15mulrSatS = 0x82, 300 | V128AnyTrue = 0x53, 301 | V128Load8Lane(MemArg, LaneIdx16) = 0x54, 302 | V128Load16Lane(MemArg, LaneIdx8) = 0x55, 303 | V128Load32Lane(MemArg, LaneIdx4) = 0x56, 304 | V128Load64Lane(MemArg, LaneIdx2) = 0x57, 305 | V128Store8Lane(MemArg, LaneIdx16) = 0x58, 306 | V128Store16Lane(MemArg, LaneIdx8) = 0x59, 307 | V128Store32Lane(MemArg, LaneIdx4) = 0x5A, 308 | V128Store64Lane(MemArg, LaneIdx2) = 0x5B, 309 | I64x2Eq = 0xD6, 310 | I64x2Ne = 0xD7, 311 | I64x2LtS = 0xD8, 312 | I64x2GtS = 0xD9, 313 | I64x2LeS = 0xDA, 314 | I64x2GeS = 0xDB, 315 | I64x2AllTrue = 0xC3, 316 | F64x2ConvertLowI32x4S = 0xFE, 317 | F64x2ConvertLowI32x4U = 0xFF, 318 | I32x4TruncSatF64x2SZero = 0xFC, 319 | I32x4TruncSatF64x2UZero = 0xFD, 320 | F32x4DemoteF64x2Zero = 0x5E, 321 | F64x2PromoteLowF32x4 = 0x5F, 322 | I8x16Popcnt = 0x62, 323 | I16x8ExtaddPairwiseI8x16S = 0x7C, 324 | I16x8ExtaddPairwiseI8x16U = 0x7D, 325 | I32x4ExtaddPairwiseI16x8S = 0x7E, 326 | I32x4ExtaddPairwiseI16x8U = 0x7F, 327 | } 328 | -------------------------------------------------------------------------------- /src/instructions/threads.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::MemArg; 16 | use crate::io::{DecodeErrorKind, Wasmbin}; 17 | use crate::visit::Visit; 18 | use crate::wasmbin_discriminants; 19 | use arbitrary::Arbitrary; 20 | 21 | macro_rules! def_mem_arg { 22 | ($name:ident, $num:literal) => { 23 | #[derive(Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 24 | #[repr(transparent)] 25 | pub struct $name { 26 | pub offset: u32, 27 | } 28 | 29 | impl $name { 30 | pub const ALIGN: u32 = $num; 31 | } 32 | 33 | impl From<$name> for MemArg { 34 | fn from(arg: $name) -> MemArg { 35 | MemArg { 36 | align: $num, 37 | offset: arg.offset, 38 | } 39 | } 40 | } 41 | 42 | encode_decode_as!($name, { 43 | ($name { offset }) <=> (MemArg { align: $num, offset }), 44 | }, |arg| { 45 | Err(DecodeErrorKind::UnsupportedDiscriminant { 46 | ty: stringify!($name), 47 | discriminant: arg.offset.into(), 48 | }.into()) 49 | }); 50 | }; 51 | } 52 | 53 | def_mem_arg!(MemArg8, 0x00); 54 | def_mem_arg!(MemArg16, 0x01); 55 | def_mem_arg!(MemArg32, 0x02); 56 | def_mem_arg!(MemArg64, 0x03); 57 | 58 | #[wasmbin_discriminants] 59 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 60 | #[repr(u8)] 61 | pub enum Atomic { 62 | Wake(MemArg32) = 0x00, 63 | I32Wait(MemArg32) = 0x01, 64 | I64Wait(MemArg64) = 0x02, 65 | I32Load(MemArg32) = 0x10, 66 | I64Load(MemArg64) = 0x11, 67 | I32Load8U(MemArg8) = 0x12, 68 | I32Load16U(MemArg16) = 0x13, 69 | I64Load8U(MemArg8) = 0x14, 70 | I64Load16U(MemArg16) = 0x15, 71 | I64Load32U(MemArg32) = 0x16, 72 | I32Store(MemArg32) = 0x17, 73 | I64Store(MemArg64) = 0x18, 74 | I32Store8(MemArg8) = 0x19, 75 | I32Store16(MemArg16) = 0x1A, 76 | I64Store8(MemArg8) = 0x1B, 77 | I64Store16(MemArg16) = 0x1C, 78 | I64Store32(MemArg32) = 0x1D, 79 | I32RmwAdd(MemArg32) = 0x1E, 80 | I64RmwAdd(MemArg64) = 0x1F, 81 | I32Rmw8AddU(MemArg8) = 0x20, 82 | I32Rmw16AddU(MemArg16) = 0x21, 83 | I64Rmw8AddU(MemArg8) = 0x22, 84 | I64Rmw16AddU(MemArg16) = 0x23, 85 | I64Rmw32AddU(MemArg32) = 0x24, 86 | I32RmwSub(MemArg32) = 0x25, 87 | I64RmwSub(MemArg64) = 0x26, 88 | I32Rmw8SubU(MemArg8) = 0x27, 89 | I32Rmw16SubU(MemArg16) = 0x28, 90 | I64Rmw8SubU(MemArg8) = 0x29, 91 | I64Rmw16SubU(MemArg16) = 0x2A, 92 | I64Rmw32SubU(MemArg32) = 0x2B, 93 | I32RmwAnd(MemArg32) = 0x2C, 94 | I64RmwAnd(MemArg64) = 0x2D, 95 | I32Rmw8AndU(MemArg8) = 0x2E, 96 | I32Rmw16AndU(MemArg16) = 0x2F, 97 | I64Rmw8AndU(MemArg8) = 0x30, 98 | I64Rmw16AndU(MemArg16) = 0x31, 99 | I64Rmw32AndU(MemArg32) = 0x32, 100 | I32RmwOr(MemArg32) = 0x33, 101 | I64RmwOr(MemArg64) = 0x34, 102 | I32Rmw8OrU(MemArg8) = 0x35, 103 | I32Rmw16OrU(MemArg16) = 0x36, 104 | I64Rmw8OrU(MemArg8) = 0x37, 105 | I64Rmw16OrU(MemArg16) = 0x38, 106 | I64Rmw32OrU(MemArg32) = 0x39, 107 | I32RmwXor(MemArg32) = 0x3A, 108 | I64RmwXor(MemArg64) = 0x3B, 109 | I32Rmw8XorU(MemArg8) = 0x3C, 110 | I32Rmw16XorU(MemArg16) = 0x3D, 111 | I64Rmw8XorU(MemArg8) = 0x3E, 112 | I64Rmw16XorU(MemArg16) = 0x3F, 113 | I64Rmw32XorU(MemArg32) = 0x40, 114 | I32RmwXchg(MemArg32) = 0x41, 115 | I64RmwXchg(MemArg64) = 0x42, 116 | I32Rmw8XchgU(MemArg8) = 0x43, 117 | I32Rmw16XchgU(MemArg16) = 0x44, 118 | I64Rmw8XchgU(MemArg8) = 0x45, 119 | I64Rmw16XchgU(MemArg16) = 0x46, 120 | I64Rmw32XchgU(MemArg32) = 0x47, 121 | I32RmwCmpXchg(MemArg32) = 0x48, 122 | I64RmwCmpXchg(MemArg64) = 0x49, 123 | I32Rmw8CmpXchgU(MemArg8) = 0x4A, 124 | I32Rmw16CmpXchgU(MemArg16) = 0x4B, 125 | I64Rmw8CmpXchgU(MemArg8) = 0x4C, 126 | I64Rmw16CmpXchgU(MemArg16) = 0x4D, 127 | I64Rmw32CmpXchgU(MemArg32) = 0x4E, 128 | } 129 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::sections::SectionOrderError; 16 | use thiserror::Error; 17 | pub use wasmbin_derive::Wasmbin; 18 | 19 | #[derive(Error, Debug)] 20 | pub enum DecodeErrorKind { 21 | #[error("{0}")] 22 | Io(#[from] std::io::Error), 23 | 24 | #[error("{0}")] 25 | Leb128(#[from] leb128::read::Error), 26 | 27 | #[error("{0}")] 28 | Utf8(#[from] std::string::FromUtf8Error), 29 | 30 | #[error("Could not recognise discriminant 0x{discriminant:X} for type {ty}")] 31 | UnsupportedDiscriminant { 32 | ty: &'static str, 33 | discriminant: i128, 34 | }, 35 | 36 | #[error("Invalid module magic signature [{actual:02X?}]")] 37 | InvalidMagic { actual: [u8; 8] }, 38 | 39 | #[error("Unrecognized data")] 40 | UnrecognizedData, 41 | 42 | #[error("{0}")] 43 | SectionOutOfOrder(#[from] SectionOrderError), 44 | } 45 | 46 | #[derive(Debug)] 47 | pub(crate) enum PathItem { 48 | Name(&'static str), 49 | Index(usize), 50 | Variant(&'static str), 51 | } 52 | 53 | #[derive(Error, Debug)] 54 | pub struct DecodeError { 55 | path: Vec, 56 | #[source] 57 | pub kind: DecodeErrorKind, 58 | } 59 | 60 | impl DecodeError { 61 | pub(crate) fn in_path(mut self, item: PathItem) -> Self { 62 | self.path.push(item); 63 | self 64 | } 65 | } 66 | 67 | impl> From for DecodeError { 68 | fn from(err: E) -> DecodeError { 69 | DecodeError { 70 | path: vec![], 71 | kind: err.into(), 72 | } 73 | } 74 | } 75 | 76 | impl std::fmt::Display for DecodeError { 77 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 78 | f.write_str("(root)")?; 79 | for item in self.path.iter().rev() { 80 | match *item { 81 | PathItem::Name(name) => write!(f, ".{}", name), 82 | PathItem::Index(index) => write!(f, "[{}]", index), 83 | PathItem::Variant(variant) => write!(f, ":<{}>", variant), 84 | }?; 85 | } 86 | write!(f, ": {}", self.kind) 87 | } 88 | } 89 | 90 | impl From for DecodeErrorKind { 91 | fn from(_err: std::num::TryFromIntError) -> Self { 92 | DecodeErrorKind::Leb128(leb128::read::Error::Overflow) 93 | } 94 | } 95 | 96 | impl From for DecodeErrorKind { 97 | fn from(err: std::convert::Infallible) -> Self { 98 | match err {} 99 | } 100 | } 101 | 102 | pub trait Encode { 103 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()>; 104 | } 105 | 106 | pub trait Decode: Sized { 107 | fn decode(r: &mut impl std::io::Read) -> Result; 108 | } 109 | 110 | macro_rules! encode_decode_as { 111 | ($ty:ty, { 112 | $($lhs:tt <=> $rhs:tt,)* 113 | } $(, |$other:pat| $other_handler:expr)?) => { 114 | impl crate::io::Encode for $ty { 115 | #[allow(unused_parens)] 116 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 117 | match *self { 118 | $($lhs => $rhs,)* 119 | }.encode(w) 120 | } 121 | } 122 | 123 | impl crate::io::Decode for $ty { 124 | #[allow(unused_parens)] 125 | fn decode(r: &mut impl std::io::Read) -> Result { 126 | Ok(match crate::io::Decode::decode(r)? { 127 | $($rhs => $lhs,)* 128 | $($other => return $other_handler)? 129 | }) 130 | } 131 | } 132 | }; 133 | } 134 | 135 | pub trait DecodeWithDiscriminant: Decode { 136 | const NAME: &'static str; 137 | type Discriminant: Decode + Copy + Into; 138 | 139 | fn maybe_decode_with_discriminant( 140 | discriminant: Self::Discriminant, 141 | r: &mut impl std::io::Read, 142 | ) -> Result, DecodeError>; 143 | 144 | fn decode_with_discriminant( 145 | discriminant: Self::Discriminant, 146 | r: &mut impl std::io::Read, 147 | ) -> Result { 148 | Self::maybe_decode_with_discriminant(discriminant, r)?.ok_or_else(|| { 149 | DecodeErrorKind::UnsupportedDiscriminant { 150 | ty: Self::NAME, 151 | discriminant: discriminant.into(), 152 | } 153 | .into() 154 | }) 155 | } 156 | 157 | fn decode_without_discriminant(r: &mut impl std::io::Read) -> Result { 158 | Self::decode_with_discriminant(Self::Discriminant::decode(r)?, r) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![cfg_attr(feature = "nightly", feature(arbitrary_enum_discriminant, never_type))] 16 | #![warn(clippy::all, clippy::pedantic)] 17 | #![allow( 18 | clippy::missing_errors_doc, 19 | clippy::match_bool, 20 | clippy::must_use_candidate, 21 | clippy::module_name_repetitions 22 | )] 23 | 24 | use wasmbin_derive::wasmbin_discriminants; 25 | 26 | #[macro_use] 27 | pub mod io; 28 | #[macro_use] 29 | pub mod visit; 30 | 31 | pub mod builtins; 32 | pub mod indices; 33 | pub mod instructions; 34 | pub mod module; 35 | pub mod sections; 36 | pub mod types; 37 | 38 | pub use module::Module; 39 | -------------------------------------------------------------------------------- /src/module.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::builtins::Blob; 16 | use crate::io::{Decode, DecodeError, DecodeErrorKind, Encode, Wasmbin}; 17 | use crate::sections::{Section, StdPayload}; 18 | use crate::visit::Visit; 19 | use arbitrary::Arbitrary; 20 | use std::cmp::Ordering; 21 | 22 | const MAGIC_AND_VERSION: [u8; 8] = [b'\0', b'a', b's', b'm', 0x01, 0x00, 0x00, 0x00]; 23 | 24 | #[derive(Debug, Default, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 25 | pub struct MagicAndVersion; 26 | 27 | encode_decode_as!(MagicAndVersion, { 28 | MagicAndVersion <=> MAGIC_AND_VERSION, 29 | }, |actual| { 30 | Err(DecodeErrorKind::InvalidMagic { actual }.into()) 31 | }); 32 | 33 | #[derive(Wasmbin, Debug, Default, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 34 | pub struct Module { 35 | #[doc(hidden)] 36 | pub magic_and_version: MagicAndVersion, 37 | pub sections: Vec
, 38 | } 39 | 40 | impl Module { 41 | pub fn decode_from(mut r: impl std::io::Read) -> Result { 42 | Self::decode(&mut r) 43 | } 44 | 45 | pub fn encode_into(&self, mut w: W) -> std::io::Result { 46 | self.encode(&mut w)?; 47 | Ok(w) 48 | } 49 | 50 | pub fn find_std_section(&self) -> Option<&Blob> { 51 | self.sections.iter().find_map(Section::try_as) 52 | } 53 | 54 | pub fn find_std_section_mut(&mut self) -> Option<&mut Blob> { 55 | self.sections.iter_mut().find_map(Section::try_as_mut) 56 | } 57 | 58 | pub fn find_or_insert_std_section( 59 | &mut self, 60 | insert_callback: impl FnOnce() -> T, 61 | ) -> &mut Blob { 62 | let mut index = self.sections.len(); 63 | let mut insert = true; 64 | for (i, section) in self.sections.iter_mut().enumerate() { 65 | match section.kind().cmp(&T::KIND) { 66 | Ordering::Less => continue, 67 | Ordering::Equal => { 68 | // We can't just `return` here due to a bug in rustc: 69 | // https://github.com/rust-lang/rust/issues/70255 70 | insert = false; 71 | } 72 | Ordering::Greater => {} 73 | } 74 | index = i; 75 | break; 76 | } 77 | if insert { 78 | self.sections.insert(index, insert_callback().into()); 79 | } 80 | unsafe { self.sections.get_unchecked_mut(index) } 81 | .try_as_mut() 82 | .unwrap_or_else(|| unsafe { std::hint::unreachable_unchecked() }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/sections.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::builtins::Lazy; 16 | use crate::builtins::WasmbinCountable; 17 | use crate::builtins::{Blob, RawBlob}; 18 | use crate::indices::{FuncId, GlobalId, LocalId, MemId, TableId, TypeId}; 19 | use crate::instructions::Expression; 20 | use crate::io::{ 21 | Decode, DecodeError, DecodeWithDiscriminant, Encode, PathItem, Wasmbin, 22 | }; 23 | use crate::types::{FuncType, GlobalType, MemType, RefType, TableType, ValueType}; 24 | use crate::visit::Visit; 25 | use crate::wasmbin_discriminants; 26 | use arbitrary::Arbitrary; 27 | use custom_debug::Debug as CustomDebug; 28 | use std::convert::TryFrom; 29 | use thiserror::Error; 30 | 31 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 32 | pub struct ModuleNameSubSection { 33 | pub name: String, 34 | } 35 | 36 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 37 | pub struct NameAssoc { 38 | pub index: I, 39 | pub value: V, 40 | } 41 | 42 | impl WasmbinCountable for NameAssoc {} 43 | 44 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 45 | pub struct NameMap { 46 | pub items: Vec>, 47 | } 48 | 49 | #[wasmbin_discriminants] 50 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 51 | #[repr(u8)] 52 | pub enum NameSubSection { 53 | Module(Blob) = 0, 54 | Func(Blob>) = 1, 55 | Local(Blob>>) = 2, 56 | } 57 | 58 | impl Encode for [NameSubSection] { 59 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 60 | for sub in self { 61 | sub.encode(w)?; 62 | } 63 | Ok(()) 64 | } 65 | } 66 | 67 | impl Decode for Vec { 68 | fn decode(r: &mut impl std::io::Read) -> Result { 69 | let mut sub = Vec::new(); 70 | while let Some(disc) = Option::decode(r)? { 71 | let i = sub.len(); 72 | sub.push( 73 | NameSubSection::decode_with_discriminant(disc, r) 74 | .map_err(move |err| err.in_path(PathItem::Index(i)))?, 75 | ); 76 | } 77 | Ok(sub) 78 | } 79 | } 80 | 81 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 82 | pub struct ProducerField { 83 | pub name: String, 84 | pub values: Vec, 85 | } 86 | 87 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 88 | pub struct ProducerVersionedName { 89 | pub name: String, 90 | pub version: String, 91 | } 92 | 93 | #[derive(Wasmbin, CustomDebug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 94 | pub struct RawCustomSection { 95 | pub name: String, 96 | 97 | #[debug(with = "custom_debug::hexbuf_str")] 98 | pub data: Vec, 99 | } 100 | 101 | macro_rules! define_custom_sections { 102 | ($($name:ident($ty:ty) = $disc:literal,)*) => { 103 | #[derive(Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 104 | pub enum CustomSection { 105 | $($name(Lazy<$ty>),)* 106 | Other(RawCustomSection), 107 | } 108 | 109 | impl CustomSection { 110 | pub fn name(&self) -> &str { 111 | match self { 112 | $(Self::$name(_) => $disc,)* 113 | Self::Other(raw) => raw.name.as_str(), 114 | } 115 | } 116 | } 117 | 118 | impl Encode for CustomSection { 119 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 120 | match self { 121 | $(CustomSection::$name(data) => { 122 | $disc.encode(w)?; 123 | data.encode(w) 124 | })* 125 | CustomSection::Other(raw) => raw.encode(w) 126 | } 127 | } 128 | } 129 | 130 | impl Decode for CustomSection { 131 | fn decode(r: &mut impl std::io::Read) -> Result { 132 | let raw = RawCustomSection::decode(r)?; 133 | Ok(match raw.name.as_str() { 134 | $($disc => CustomSection::$name(Lazy::from_raw(raw.data)),)* 135 | _ => CustomSection::Other(raw) 136 | }) 137 | } 138 | } 139 | }; 140 | } 141 | 142 | define_custom_sections! { 143 | Name(Vec) = "name", 144 | Producers(Vec) = "producers", 145 | // https://github.com/WebAssembly/tool-conventions/blob/08bacbed/Debugging.md#external-dwarf 146 | ExternalDebugInfo(String) = "external_debug_info", 147 | // https://github.com/WebAssembly/tool-conventions/blob/08bacbed/Debugging.md#source-maps 148 | SourceMappingUrl(String) = "sourceMappingURL", 149 | } 150 | 151 | #[wasmbin_discriminants] 152 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 153 | #[repr(u8)] 154 | pub enum ImportDesc { 155 | Func(TypeId) = 0x00, 156 | Table(TableType) = 0x01, 157 | Mem(MemType) = 0x02, 158 | Global(GlobalType) = 0x03, 159 | } 160 | 161 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 162 | pub struct ImportPath { 163 | pub module: String, 164 | pub name: String, 165 | } 166 | 167 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 168 | pub struct Import { 169 | pub path: ImportPath, 170 | pub desc: ImportDesc, 171 | } 172 | 173 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 174 | pub struct Global { 175 | pub ty: GlobalType, 176 | pub init: Expression, 177 | } 178 | 179 | #[wasmbin_discriminants] 180 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 181 | #[repr(u8)] 182 | pub enum ExportDesc { 183 | Func(FuncId) = 0x00, 184 | Table(TableId) = 0x01, 185 | Mem(MemId) = 0x02, 186 | Global(GlobalId) = 0x03, 187 | } 188 | 189 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 190 | pub struct Export { 191 | pub name: String, 192 | pub desc: ExportDesc, 193 | } 194 | 195 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 196 | #[repr(u8)] 197 | pub enum ElemKind { 198 | FuncRef = 0x00, 199 | } 200 | 201 | #[wasmbin_discriminants] 202 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 203 | #[repr(u8)] 204 | pub enum Element { 205 | ActiveWithFuncs { 206 | offset: Expression, 207 | funcs: Vec, 208 | } = 0x00, 209 | PassiveWithFuncs { 210 | kind: ElemKind, 211 | funcs: Vec, 212 | } = 0x01, 213 | ActiveWithTableAndFuncs { 214 | table: TableId, 215 | offset: Expression, 216 | kind: ElemKind, 217 | funcs: Vec, 218 | } = 0x02, 219 | DeclarativeWithFuncs { 220 | kind: ElemKind, 221 | funcs: Vec, 222 | } = 0x03, 223 | ActiveWithExprs { 224 | offset: Expression, 225 | exprs: Vec, 226 | } = 0x04, 227 | PassiveWithExprs { 228 | ty: RefType, 229 | exprs: Vec, 230 | } = 0x05, 231 | ActiveWithTableAndExprs { 232 | table: TableId, 233 | offset: Expression, 234 | ty: RefType, 235 | exprs: Vec, 236 | } = 0x06, 237 | DeclarativeWithExprs { 238 | ty: RefType, 239 | exprs: Vec, 240 | } = 0x07, 241 | } 242 | 243 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 244 | pub struct Locals { 245 | pub repeat: u32, 246 | pub ty: ValueType, 247 | } 248 | 249 | #[derive( 250 | Wasmbin, WasmbinCountable, Debug, Default, Arbitrary, PartialEq, Eq, Hash, Clone, Visit, 251 | )] 252 | pub struct FuncBody { 253 | pub locals: Vec, 254 | pub expr: Expression, 255 | } 256 | 257 | #[wasmbin_discriminants] 258 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 259 | #[repr(u8)] 260 | pub enum DataInit { 261 | Active { offset: Expression } = 0x00, 262 | Passive = 0x01, 263 | ActiveWithMemory { memory: MemId, offset: Expression } = 0x02, 264 | } 265 | 266 | #[derive(Wasmbin, WasmbinCountable, CustomDebug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 267 | pub struct Data { 268 | pub init: DataInit, 269 | #[debug(with = "custom_debug::hexbuf_str")] 270 | pub blob: RawBlob, 271 | } 272 | 273 | pub trait Payload: Encode + Decode + Into
{ 274 | const KIND: Kind; 275 | 276 | fn try_from_ref(section: &Section) -> Option<&Blob>; 277 | fn try_from_mut(section: &mut Section) -> Option<&mut Blob>; 278 | fn try_from(section: Section) -> Result, Section>; 279 | } 280 | 281 | pub trait StdPayload: Payload {} 282 | 283 | macro_rules! define_sections { 284 | ($($(# $attr:tt)? $name:ident($ty:ty) = $disc:literal,)*) => { 285 | pub mod payload { 286 | $($(# $attr)? pub type $name = $ty;)* 287 | } 288 | 289 | #[wasmbin_discriminants] 290 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 291 | #[repr(u8)] 292 | pub enum Section { 293 | $($(# $attr)? $name(Blob) = $disc,)* 294 | } 295 | 296 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] 297 | #[repr(u8)] 298 | pub enum Kind { 299 | $($(# $attr)? $name = $disc,)* 300 | } 301 | 302 | impl TryFrom for Kind { 303 | type Error = u8; 304 | 305 | fn try_from(discriminant: u8) -> Result { 306 | Ok(match discriminant { 307 | $($(# $attr)? $disc => Kind::$name,)* 308 | _ => return Err(discriminant), 309 | }) 310 | } 311 | } 312 | 313 | impl Ord for Kind { 314 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 315 | // Some new sections might have larger discriminants, 316 | // but be ordered logically between those will smaller 317 | // discriminants. 318 | // 319 | // To compare their Kinds in a defined order, we need an 320 | // intermediate enum without discriminants. 321 | #[derive(PartialEq, Eq, PartialOrd, Ord)] 322 | #[repr(u8)] 323 | enum OrderedRepr { 324 | $($(# $attr)? $name,)* 325 | } 326 | 327 | impl From for OrderedRepr { 328 | fn from(kind: Kind) -> Self { 329 | match kind { 330 | $($(# $attr)? Kind::$name => Self::$name,)* 331 | } 332 | } 333 | } 334 | 335 | OrderedRepr::from(*self).cmp(&OrderedRepr::from(*other)) 336 | } 337 | } 338 | 339 | impl PartialOrd for Kind { 340 | fn partial_cmp(&self, other: &Self) -> Option { 341 | Some(self.cmp(other)) 342 | } 343 | } 344 | 345 | $($(# $attr)? const _: () = { 346 | impl From> for Section { 347 | fn from(value: Blob) -> Self { 348 | Section::$name(value) 349 | } 350 | } 351 | 352 | impl From for Section { 353 | fn from(value: payload::$name) -> Self { 354 | Section::$name(Blob::from(value)) 355 | } 356 | } 357 | 358 | impl Payload for payload::$name { 359 | const KIND: Kind = Kind::$name; 360 | 361 | fn try_from_ref(section: &Section) -> Option<&Blob> { 362 | match section { 363 | Section::$name(res) => Some(res), 364 | _ => None, 365 | } 366 | } 367 | 368 | fn try_from_mut(section: &mut Section) -> Option<&mut Blob> { 369 | match section { 370 | Section::$name(res) => Some(res), 371 | _ => None, 372 | } 373 | } 374 | 375 | fn try_from(section: Section) -> Result, Section> { 376 | match section { 377 | Section::$name(res) => Ok(res), 378 | _ => Err(section), 379 | } 380 | } 381 | } 382 | };)* 383 | 384 | impl Section { 385 | pub fn kind(&self) -> Kind { 386 | match self { 387 | $($(# $attr)? Section::$name(_) => Kind::$name,)* 388 | } 389 | } 390 | 391 | pub fn try_as(&self) -> Option<&Blob> { 392 | T::try_from_ref(self) 393 | } 394 | 395 | pub fn try_as_mut(&mut self) -> Option<&mut Blob> { 396 | T::try_from_mut(self) 397 | } 398 | } 399 | 400 | define_sections!(@std $($(# $attr)? $name)*); 401 | }; 402 | 403 | (@std $ignore_custom:ident $($(# $attr:tt)? $name:ident)*) => { 404 | $($(# $attr)? impl StdPayload for payload::$name {})* 405 | }; 406 | } 407 | 408 | define_sections! { 409 | Custom(super::CustomSection) = 0, 410 | Type(Vec) = 1, 411 | Import(Vec) = 2, 412 | Function(Vec) = 3, 413 | Table(Vec) = 4, 414 | Memory(Vec) = 5, 415 | Global(Vec) = 6, 416 | Export(Vec) = 7, 417 | Start(super::FuncId) = 8, 418 | Element(Vec) = 9, 419 | DataCount(u32) = 12, 420 | Code(Vec>) = 10, 421 | Data(Vec) = 11, 422 | } 423 | 424 | #[derive(Debug, Error)] 425 | #[error("Section out of order: {current:?} after {prev:?}")] 426 | pub struct SectionOrderError { 427 | pub current: Kind, 428 | pub prev: Kind, 429 | } 430 | 431 | impl From for std::io::Error { 432 | fn from(err: SectionOrderError) -> Self { 433 | Self::new(std::io::ErrorKind::InvalidData, err) 434 | } 435 | } 436 | 437 | struct SectionOrderTracker { 438 | last_kind: Kind, 439 | } 440 | 441 | impl Default for SectionOrderTracker { 442 | fn default() -> Self { 443 | Self { 444 | last_kind: Kind::Custom, 445 | } 446 | } 447 | } 448 | 449 | impl SectionOrderTracker { 450 | pub fn try_add(&mut self, section: &Section) -> Result<(), SectionOrderError> { 451 | match section.kind() { 452 | Kind::Custom => {} 453 | kind if kind > self.last_kind => { 454 | self.last_kind = kind; 455 | } 456 | kind => { 457 | return Err(SectionOrderError { 458 | prev: self.last_kind, 459 | current: kind, 460 | }); 461 | } 462 | } 463 | Ok(()) 464 | } 465 | } 466 | 467 | impl Encode for [Section] { 468 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 469 | let mut section_order_tracker = SectionOrderTracker::default(); 470 | for section in self { 471 | section_order_tracker.try_add(section)?; 472 | section.encode(w)?; 473 | } 474 | Ok(()) 475 | } 476 | } 477 | 478 | impl Decode for Vec
{ 479 | fn decode(r: &mut impl std::io::Read) -> Result { 480 | let mut sections = Vec::new(); 481 | let mut section_order_tracker = SectionOrderTracker::default(); 482 | while let Some(disc) = Option::decode(r)? { 483 | let i = sections.len(); 484 | (|| -> Result<(), DecodeError> { 485 | let section = Section::decode_with_discriminant(disc, r)?; 486 | section_order_tracker.try_add(§ion)?; 487 | sections.push(section); 488 | Ok(()) 489 | })() 490 | .map_err(move |err| err.in_path(PathItem::Index(i)))?; 491 | } 492 | Ok(sections) 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::builtins::WasmbinCountable; 16 | use crate::indices::TypeId; 17 | use crate::io::{Decode, DecodeError, DecodeWithDiscriminant, Encode, PathItem, Wasmbin}; 18 | use crate::visit::Visit; 19 | use crate::wasmbin_discriminants; 20 | use arbitrary::Arbitrary; 21 | use std::convert::TryFrom; 22 | use std::fmt::{self, Debug, Formatter}; 23 | 24 | const OP_CODE_EMPTY_BLOCK: u8 = 0x40; 25 | 26 | #[wasmbin_discriminants] 27 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 28 | #[repr(u8)] 29 | pub enum ValueType { 30 | #[cfg(feature = "simd")] 31 | V128 = 0x7B, 32 | F64 = 0x7C, 33 | F32 = 0x7D, 34 | I64 = 0x7E, 35 | I32 = 0x7F, 36 | Ref(RefType), 37 | } 38 | 39 | #[derive(Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 40 | #[repr(u8)] 41 | pub enum BlockType { 42 | Empty, 43 | Value(ValueType), 44 | MultiValue(TypeId), 45 | } 46 | 47 | impl Encode for BlockType { 48 | fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { 49 | match self { 50 | BlockType::Empty => OP_CODE_EMPTY_BLOCK.encode(w), 51 | BlockType::Value(ty) => ty.encode(w), 52 | BlockType::MultiValue(id) => i64::from(id.index).encode(w), 53 | } 54 | } 55 | } 56 | 57 | impl Decode for BlockType { 58 | fn decode(r: &mut impl std::io::Read) -> Result { 59 | let discriminant = u8::decode(r)?; 60 | if discriminant == OP_CODE_EMPTY_BLOCK { 61 | return Ok(BlockType::Empty); 62 | } 63 | if let Some(ty) = ValueType::maybe_decode_with_discriminant(discriminant, r) 64 | .map_err(|err| err.in_path(PathItem::Variant("BlockType::Value")))? 65 | { 66 | return Ok(BlockType::Value(ty)); 67 | } 68 | let index = (move || -> Result<_, DecodeError> { 69 | // We have already read one byte that could've been either a 70 | // discriminant or a part of an s33 LEB128 specially used for 71 | // type indices. 72 | // 73 | // To recover the LEB128 sequence, we need to chain it back. 74 | let buf = [discriminant]; 75 | let mut r = std::io::Read::chain(&buf[..], r); 76 | let as_i64 = i64::decode(&mut r)?; 77 | // These indices are encoded as positive signed integers. 78 | // Convert them to unsigned integers and error out if they're out of range. 79 | let index = u32::try_from(as_i64)?; 80 | Ok(index) 81 | })() 82 | .map_err(|err| err.in_path(PathItem::Variant("BlockType::MultiValue")))?; 83 | Ok(BlockType::MultiValue(TypeId { index })) 84 | } 85 | } 86 | 87 | #[derive(Wasmbin, WasmbinCountable, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 88 | #[wasmbin(discriminant = 0x60)] 89 | pub struct FuncType { 90 | pub params: Vec, 91 | pub results: Vec, 92 | } 93 | 94 | impl Debug for FuncType { 95 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 96 | fn encode_types(types: &[ValueType], f: &mut Formatter) -> fmt::Result { 97 | f.write_str("(")?; 98 | for (i, ty) in types.iter().enumerate() { 99 | if i != 0 { 100 | f.write_str(", ")?; 101 | } 102 | ty.fmt(f)?; 103 | } 104 | f.write_str(")") 105 | } 106 | 107 | encode_types(&self.params, f)?; 108 | f.write_str(" -> ")?; 109 | encode_types(&self.results, f) 110 | } 111 | } 112 | 113 | #[derive(Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 114 | pub struct Limits { 115 | pub min: u32, 116 | pub max: Option, 117 | } 118 | 119 | impl Debug for Limits { 120 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 121 | write!(f, "{}..", self.min)?; 122 | if let Some(max) = self.max { 123 | write!(f, "={}", max)?; 124 | } 125 | Ok(()) 126 | } 127 | } 128 | 129 | #[wasmbin_discriminants] 130 | #[derive(Wasmbin)] 131 | #[repr(u8)] 132 | enum LimitsRepr { 133 | Min { min: u32 } = 0x00, 134 | MinMax { min: u32, max: u32 } = 0x01, 135 | } 136 | 137 | encode_decode_as!(Limits, { 138 | (Limits { min, max: None }) <=> (LimitsRepr::Min { min }), 139 | (Limits { min, max: Some(max) }) <=> (LimitsRepr::MinMax { min, max }), 140 | }); 141 | 142 | #[cfg(feature = "threads")] 143 | #[wasmbin_discriminants] 144 | #[derive(Wasmbin)] 145 | #[repr(u8)] 146 | enum MemTypeRepr { 147 | Unshared(LimitsRepr), 148 | SharedMin { min: u32 } = 0x02, 149 | SharedMinMax { min: u32, max: u32 } = 0x03, 150 | } 151 | 152 | #[cfg_attr(not(feature = "threads"), derive(Wasmbin))] 153 | #[derive(WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 154 | pub struct MemType { 155 | #[cfg(feature = "threads")] 156 | pub is_shared: bool, 157 | pub limits: Limits, 158 | } 159 | 160 | #[cfg(feature = "threads")] 161 | encode_decode_as!(MemType, { 162 | (MemType { is_shared: false, limits: Limits { min, max: None } }) <=> (MemTypeRepr::Unshared(LimitsRepr::Min { min })), 163 | (MemType { is_shared: false, limits: Limits { min, max: Some(max) } }) <=> (MemTypeRepr::Unshared(LimitsRepr::MinMax { min, max })), 164 | (MemType { is_shared: true, limits: Limits { min, max: None } }) <=> (MemTypeRepr::SharedMin { min }), 165 | (MemType { is_shared: true, limits: Limits { min, max: Some(max) } }) <=> (MemTypeRepr::SharedMinMax { min, max }), 166 | }); 167 | 168 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 169 | #[repr(u8)] 170 | pub enum RefType { 171 | Func = 0x70, 172 | Extern = 0x6F, 173 | } 174 | 175 | #[derive(Wasmbin, WasmbinCountable, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 176 | pub struct TableType { 177 | pub elem_type: RefType, 178 | pub limits: Limits, 179 | } 180 | 181 | #[derive(Wasmbin, Debug, Arbitrary, PartialEq, Eq, Hash, Clone, Visit)] 182 | pub struct GlobalType { 183 | pub value_type: ValueType, 184 | pub mutable: bool, 185 | } 186 | -------------------------------------------------------------------------------- /src/visit.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::io::{DecodeError, PathItem}; 16 | 17 | pub enum VisitError { 18 | LazyDecode(DecodeError), 19 | Custom(E), 20 | } 21 | 22 | impl std::fmt::Display for VisitError { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | match self { 25 | VisitError::LazyDecode(err) => err.fmt(f), 26 | VisitError::Custom(err) => err.fmt(f), 27 | } 28 | } 29 | } 30 | 31 | impl std::fmt::Debug for VisitError { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | match self { 34 | VisitError::LazyDecode(err) => err.fmt(f), 35 | VisitError::Custom(err) => err.fmt(f), 36 | } 37 | } 38 | } 39 | 40 | impl std::error::Error for VisitError {} 41 | 42 | impl VisitError { 43 | pub(crate) fn in_path(self, item: PathItem) -> Self { 44 | #[allow(clippy::match_wildcard_for_single_variants)] 45 | match self { 46 | VisitError::LazyDecode(err) => VisitError::LazyDecode(err.in_path(item)), 47 | err => err, 48 | } 49 | } 50 | } 51 | 52 | #[cfg(feature = "nightly")] 53 | pub type NeverError = !; 54 | 55 | #[cfg(not(feature = "nightly"))] 56 | #[allow(clippy::empty_enum)] 57 | #[derive(Debug, thiserror::Error)] 58 | pub enum NeverError {} 59 | 60 | impl From> for DecodeError { 61 | fn from(err: VisitError) -> Self { 62 | match err { 63 | VisitError::Custom(err) => match err {}, 64 | VisitError::LazyDecode(err) => err, 65 | } 66 | } 67 | } 68 | 69 | pub trait VisitResult { 70 | type Error; 71 | 72 | fn as_result(self) -> Result<(), Self::Error>; 73 | } 74 | 75 | impl VisitResult for () { 76 | type Error = NeverError; 77 | 78 | fn as_result(self) -> Result<(), Self::Error> { 79 | Ok(()) 80 | } 81 | } 82 | 83 | impl VisitResult for bool { 84 | type Error = (); 85 | 86 | fn as_result(self) -> Result<(), Self::Error> { 87 | match self { 88 | true => Ok(()), 89 | false => Err(()), 90 | } 91 | } 92 | } 93 | 94 | impl VisitResult for Result<(), E> { 95 | type Error = E; 96 | 97 | fn as_result(self) -> Result<(), Self::Error> { 98 | self 99 | } 100 | } 101 | 102 | pub use wasmbin_derive::Visit; 103 | pub trait Visit: 'static + Sized { 104 | fn visit<'a, T: 'static, R: VisitResult, F: FnMut(&'a T) -> R>( 105 | &'a self, 106 | mut f: F, 107 | ) -> Result<(), VisitError> { 108 | self.visit_child(&mut move |item| f(item).as_result()) 109 | } 110 | 111 | fn visit_mut R>( 112 | &mut self, 113 | mut f: F, 114 | ) -> Result<(), VisitError> { 115 | self.visit_child_mut(&mut move |item| f(item).as_result()) 116 | } 117 | 118 | fn visit_child<'a, T: 'static, E, F: FnMut(&'a T) -> Result<(), E>>( 119 | &'a self, 120 | f: &mut F, 121 | ) -> Result<(), VisitError> { 122 | if let Some(v) = ::downcast_ref(self) { 123 | f(v).map_err(VisitError::Custom)?; 124 | } 125 | self.visit_children(f) 126 | } 127 | 128 | fn visit_child_mut Result<(), E>>( 129 | &mut self, 130 | f: &mut F, 131 | ) -> Result<(), VisitError> { 132 | if let Some(v) = ::downcast_mut(self) { 133 | f(v).map_err(VisitError::Custom)?; 134 | } 135 | self.visit_children_mut(f) 136 | } 137 | 138 | fn visit_children<'a, T: 'static, E, F: FnMut(&'a T) -> Result<(), E>>( 139 | &'a self, 140 | _f: &mut F, 141 | ) -> Result<(), VisitError> { 142 | Ok(()) 143 | } 144 | 145 | fn visit_children_mut Result<(), E>>( 146 | &mut self, 147 | _f: &mut F, 148 | ) -> Result<(), VisitError> { 149 | Ok(()) 150 | } 151 | } 152 | 153 | macro_rules! impl_visit_for_iter { 154 | ($ty:tt $(<$param:ident>)?) => { 155 | impl$(<$param: crate::visit::Visit>)? crate::visit::Visit for $ty $(<$param>)? { 156 | fn visit_children<'a, VisitT: 'static, E, F: FnMut(&'a VisitT) -> Result<(), E>>( 157 | &'a self, 158 | f: &mut F, 159 | ) -> Result<(), crate::visit::VisitError> { 160 | for (i, v) in self.iter().enumerate() { 161 | v.visit_child(f).map_err(move |err| err.in_path(crate::io::PathItem::Index(i)))?; 162 | } 163 | Ok(()) 164 | } 165 | 166 | fn visit_children_mut Result<(), E>>( 167 | &mut self, 168 | f: &mut F, 169 | ) -> Result<(), crate::visit::VisitError> { 170 | for (i, v) in self.iter_mut().enumerate() { 171 | v.visit_child_mut(f).map_err(move |err| err.in_path(crate::io::PathItem::Index(i)))?; 172 | } 173 | Ok(()) 174 | } 175 | } 176 | }; 177 | } 178 | -------------------------------------------------------------------------------- /tests/spec.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use anyhow::{bail, Context, Error}; 16 | use fehler::throws; 17 | use libtest_mimic::{run_tests, Arguments, Outcome, Test}; 18 | use std::fs::{read_dir, read_to_string}; 19 | use std::path::Path; 20 | use wasmbin::{ 21 | io::DecodeError, 22 | visit::{Visit, VisitError}, 23 | Module, 24 | }; 25 | use wast::parser::{parse, ParseBuffer}; 26 | use wast::Wast; 27 | 28 | const IGNORED_ERRORS: &[&str] = &[ 29 | // We don't perform cross-section analysis. 30 | "function and code section have inconsistent lengths", 31 | "data count section required", 32 | "data count and data section have inconsistent lengths", 33 | // We allow non-zero table and memory IDs already. 34 | "zero flag expected", 35 | // We don't perform full function analysis either. 36 | "too many locals", 37 | ]; 38 | 39 | const IGNORED_MODULES: &[&[u8]] = &[ 40 | // These are outdated tests in WebAssembly/testsuite itself. 41 | // It needs updating, but see https://github.com/WebAssembly/testsuite/pull/39#issuecomment-863496809. 42 | &[ 43 | 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x05, 0x03, 0x01, 0x00, 0x00, 0x0B, 0x07, 44 | 0x01, 0x80, 0x00, 0x41, 0x00, 0x0B, 0x00, 45 | ], 46 | &[ 47 | 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x04, 0x04, 0x01, 0x70, 0x00, 0x00, 0x09, 48 | 0x07, 0x01, 0x80, 0x00, 0x41, 0x00, 0x0B, 0x00, 49 | ], 50 | &[ 51 | 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x05, 0x03, 0x01, 0x00, 0x00, 0x0B, 0x06, 52 | 0x01, 0x01, 0x41, 0x00, 0x0B, 0x00, 53 | ], 54 | &[ 55 | 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x06, 0x01, 0x01, 0x41, 0x00, 0x0B, 56 | 0x00, 57 | ], 58 | // Upstream malformed test that becomes well-formed if threads are enabled. 59 | #[cfg(feature = "threads")] 60 | &[ 61 | 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x05, 0x03, 0x01, 0x02, 0x00, 62 | ], 63 | ]; 64 | 65 | struct WasmTest { 66 | module: Vec, 67 | expect_result: Result<(), String>, 68 | } 69 | 70 | #[throws] 71 | fn read_tests_from_file(path: &Path, dest: &mut Vec>) { 72 | let src = read_to_string(path)?; 73 | let set_err_path_text = |mut err: wast::Error| { 74 | err.set_path(path); 75 | err.set_text(&src); 76 | err 77 | }; 78 | let buf = ParseBuffer::new(&src).map_err(set_err_path_text)?; 79 | let wast = parse::(&buf).map_err(set_err_path_text)?; 80 | for directive in wast.directives { 81 | let (span, mut module, expect_result) = match directive { 82 | // Expect errors for assert_malformed on binary or AST modules. 83 | wast::WastDirective::AssertMalformed { 84 | span, 85 | module: wast::QuoteModule::Module(module), 86 | message, 87 | } 88 | // Unlike other AssertInvalid, this is actually something we 89 | // check at the parsing time, because it's part of our 90 | // typesystem and doesn't require cross-function or 91 | // cross-section checks. 92 | // 93 | // See https://github.com/WebAssembly/simd/issues/256 for accepted 94 | // but pending suggestion to change proposal to match this as well. 95 | | wast::WastDirective::AssertInvalid { 96 | span, 97 | module, 98 | message: message @ "invalid lane index", 99 | } => (span, module, Err(message.to_owned())), 100 | // Expect successful parsing for regular AST modules. 101 | wast::WastDirective::Module(module) => (module.span, module, Ok(())), 102 | // Counter-intuitively, expect successful parsing for modules that are supposed 103 | // to error out at runtime or linking stage, too. 104 | wast::WastDirective::AssertInvalid { span, module, .. } 105 | | wast::WastDirective::AssertUnlinkable { span, module, .. } => (span, module, Ok(())), 106 | _ => { 107 | // Skipping interpreted 108 | continue; 109 | } 110 | }; 111 | let (line, col) = span.linecol_in(&src); 112 | let module = module.encode()?; 113 | dest.push(Test { 114 | name: format!("{}:{}:{}", path.display(), line + 1, col + 1), 115 | kind: String::default(), 116 | is_ignored: IGNORED_MODULES.contains(&module.as_slice()) 117 | || match &expect_result { 118 | Ok(()) => false, 119 | Err(err) => IGNORED_ERRORS.contains(&err.as_str()), 120 | }, 121 | is_bench: false, 122 | data: WasmTest { 123 | module, 124 | expect_result, 125 | }, 126 | }); 127 | } 128 | } 129 | 130 | #[throws] 131 | fn read_tests_from_dir(path: &Path, dest: &mut Vec>) { 132 | for file in read_dir(path)? { 133 | let path = file?.path(); 134 | if path.extension().map_or(false, |ext| ext == "wast") { 135 | read_tests_from_file(&path, dest)?; 136 | } 137 | } 138 | } 139 | 140 | #[throws] 141 | fn read_all_tests(path: &Path) -> Vec> { 142 | let mut tests = Vec::new(); 143 | let proposals_dir = path.join("proposals"); 144 | 145 | macro_rules! read_proposal_tests { 146 | ($name:literal) => { 147 | if cfg!(feature = $name) { 148 | read_tests_from_dir(&proposals_dir.join($name), &mut tests).context($name)? 149 | } 150 | }; 151 | } 152 | 153 | read_proposal_tests!("tail-call"); 154 | read_proposal_tests!("simd"); 155 | read_proposal_tests!("threads"); 156 | 157 | read_tests_from_dir(path, &mut tests)?; 158 | 159 | if tests.is_empty() { 160 | bail!("Couldn't find any tests. Did you run `git submodule update --init`?"); 161 | } 162 | 163 | tests 164 | } 165 | 166 | fn unlazify(mut wasm: T) -> Result { 167 | match wasm.visit_mut(|()| {}) { 168 | Ok(()) => Ok(wasm), 169 | Err(err) => match err { 170 | VisitError::LazyDecode(err) => Err(err), 171 | VisitError::Custom(err) => match err {}, 172 | }, 173 | } 174 | } 175 | 176 | #[throws] 177 | fn run_test(test: &WasmTest) { 178 | let module = match (Module::decode_from(&mut test.module.as_slice()).and_then(unlazify), &test.expect_result) { 179 | (Ok(ref module), Err(err)) => bail!("Expected an invalid module definition with an error: {}\nParsed part: {:02X?}\nGot module: {:02X?}", err, test.module, module), 180 | (Err(err), Ok(())) => bail!( 181 | "Expected a valid module definition, but got an error\nModule: {:02X?}\nError: {:#}", 182 | test.module, 183 | err 184 | ), 185 | (Ok(module), Ok(())) => module, 186 | (Err(_), Err(_)) => return, 187 | }; 188 | let out = module.encode_into(Vec::new())?; 189 | if out != test.module { 190 | // In the rare case that binary representation doesn't match, it 191 | // might be because the test uses longer LEB128 form than 192 | // required. Verify that at least decoding it back produces the 193 | // same module. 194 | let module2 = Module::decode_from(out.as_slice())?; 195 | if module != module2 { 196 | bail!( 197 | "Roundtrip mismatch. Old: {:#?}\nNew: {:#?}", 198 | module, 199 | module2 200 | ); 201 | } 202 | } 203 | } 204 | 205 | #[throws] 206 | fn main() { 207 | let tests = read_all_tests(&Path::new("tests").join("testsuite"))?; 208 | 209 | run_tests(&Arguments::from_args(), tests, |test| { 210 | match run_test(&test.data) { 211 | Ok(()) => Outcome::Passed, 212 | Err(err) => Outcome::Failed { 213 | msg: Some(err.to_string()), 214 | }, 215 | } 216 | }) 217 | .exit_if_failed(); 218 | } 219 | --------------------------------------------------------------------------------