| 1 | use anyhow::Result; |
| 2 | use indexmap::{map::Entry, IndexMap}; |
| 3 | use serde_derive::Serialize; |
| 4 | use wasm_encoder::Encode; |
| 5 | use wasmparser::{BinaryReader, KnownCustom, Parser, ProducersSectionReader}; |
| 6 | |
| 7 | use crate::{rewrite_wasm, AddMetadata}; |
| 8 | /// A representation of a WebAssembly producers section. |
| 9 | /// |
| 10 | /// Spec: <https://github.com/WebAssembly/tool-conventions/blob/main/ProducersSection.md> |
| 11 | #[derive (Debug, Serialize)] |
| 12 | pub struct Producers( |
| 13 | #[serde(serialize_with = "indexmap::map::serde_seq::serialize" )] |
| 14 | IndexMap<String, IndexMap<String, String>>, |
| 15 | ); |
| 16 | |
| 17 | impl Default for Producers { |
| 18 | fn default() -> Self { |
| 19 | Self::empty() |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | impl Producers { |
| 24 | /// Creates an empty producers section |
| 25 | pub fn empty() -> Self { |
| 26 | Producers(IndexMap::new()) |
| 27 | } |
| 28 | |
| 29 | /// Indicates if section is empty |
| 30 | pub fn is_empty(&self) -> bool { |
| 31 | self.0.is_empty() |
| 32 | } |
| 33 | |
| 34 | /// Read the producers section from a Wasm binary. Supports both core |
| 35 | /// Modules and Components. In the component case, only returns the |
| 36 | /// producers section in the outer component, ignoring all interior |
| 37 | /// components and modules. |
| 38 | pub fn from_wasm(bytes: &[u8]) -> Result<Option<Self>> { |
| 39 | let mut depth = 0; |
| 40 | for payload in Parser::new(0).parse_all(bytes) { |
| 41 | let payload = payload?; |
| 42 | use wasmparser::Payload::*; |
| 43 | match payload { |
| 44 | ModuleSection { .. } | ComponentSection { .. } => depth += 1, |
| 45 | End { .. } => depth -= 1, |
| 46 | CustomSection(c) if depth == 0 => { |
| 47 | if let KnownCustom::Producers(_) = c.as_known() { |
| 48 | let producers = Self::from_bytes(c.data(), c.data_offset())?; |
| 49 | return Ok(Some(producers)); |
| 50 | } |
| 51 | } |
| 52 | _ => {} |
| 53 | } |
| 54 | } |
| 55 | Ok(None) |
| 56 | } |
| 57 | /// Read the producers section from a Wasm binary. |
| 58 | pub fn from_bytes(bytes: &[u8], offset: usize) -> Result<Self> { |
| 59 | let reader = BinaryReader::new(bytes, offset); |
| 60 | let section = ProducersSectionReader::new(reader)?; |
| 61 | let mut fields = IndexMap::new(); |
| 62 | for field in section.into_iter() { |
| 63 | let field = field?; |
| 64 | let mut values = IndexMap::new(); |
| 65 | for value in field.values.into_iter() { |
| 66 | let value = value?; |
| 67 | values.insert(value.name.to_owned(), value.version.to_owned()); |
| 68 | } |
| 69 | fields.insert(field.name.to_owned(), values); |
| 70 | } |
| 71 | Ok(Producers(fields)) |
| 72 | } |
| 73 | /// Add a name & version value to a field. |
| 74 | /// |
| 75 | /// The spec says expected field names are "language", "processed-by", and "sdk". |
| 76 | /// The version value should be left blank for languages. |
| 77 | pub fn add(&mut self, field: &str, name: &str, version: &str) { |
| 78 | match self.0.entry(field.to_string()) { |
| 79 | Entry::Occupied(e) => { |
| 80 | e.into_mut().insert(name.to_owned(), version.to_owned()); |
| 81 | } |
| 82 | Entry::Vacant(e) => { |
| 83 | let mut m = IndexMap::new(); |
| 84 | m.insert(name.to_owned(), version.to_owned()); |
| 85 | e.insert(m); |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | /// Add all values found in another `Producers` section. Values in `other` take |
| 91 | /// precedence. |
| 92 | pub fn merge(&mut self, other: &Self) { |
| 93 | for (field, values) in other.iter() { |
| 94 | for (name, version) in values.iter() { |
| 95 | self.add(field, name, version); |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | /// Get the contents of a field |
| 101 | pub fn get<'a>(&'a self, field: &str) -> Option<ProducersField<'a>> { |
| 102 | self.0.get(&field.to_owned()).map(ProducersField) |
| 103 | } |
| 104 | |
| 105 | /// Iterate through all fields |
| 106 | pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, ProducersField<'a>)> + 'a { |
| 107 | self.0 |
| 108 | .iter() |
| 109 | .map(|(name, field)| (name, ProducersField(field))) |
| 110 | } |
| 111 | |
| 112 | /// Construct the fields specified by [`AddMetadata`] |
| 113 | pub(crate) fn from_meta(add: &AddMetadata) -> Self { |
| 114 | let mut s = Self::empty(); |
| 115 | for (lang, version) in add.language.iter() { |
| 116 | s.add("language" , &lang, &version); |
| 117 | } |
| 118 | for (name, version) in add.processed_by.iter() { |
| 119 | s.add("processed-by" , &name, &version); |
| 120 | } |
| 121 | for (name, version) in add.sdk.iter() { |
| 122 | s.add("sdk" , &name, &version); |
| 123 | } |
| 124 | s |
| 125 | } |
| 126 | |
| 127 | /// Serialize into [`wasm_encoder::ProducersSection`]. |
| 128 | pub(crate) fn section(&self) -> wasm_encoder::ProducersSection { |
| 129 | let mut section = wasm_encoder::ProducersSection::new(); |
| 130 | for (fieldname, fieldvalues) in self.0.iter() { |
| 131 | let mut field = wasm_encoder::ProducersField::new(); |
| 132 | for (name, version) in fieldvalues { |
| 133 | field.value(&name, &version); |
| 134 | } |
| 135 | section.field(&fieldname, &field); |
| 136 | } |
| 137 | section |
| 138 | } |
| 139 | |
| 140 | /// Serialize into the raw bytes of a wasm custom section. |
| 141 | pub fn raw_custom_section(&self) -> Vec<u8> { |
| 142 | let mut ret = Vec::new(); |
| 143 | self.section().encode(&mut ret); |
| 144 | ret |
| 145 | } |
| 146 | |
| 147 | /// Merge into an existing wasm module. Rewrites the module with this producers section |
| 148 | /// merged into its existing one, or adds this producers section if none is present. |
| 149 | pub fn add_to_wasm(&self, input: &[u8]) -> Result<Vec<u8>> { |
| 150 | rewrite_wasm( |
| 151 | &None, self, &None, &None, &None, &None, &None, &None, &None, input, |
| 152 | ) |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | /// Contents of a producers field |
| 157 | #[derive (Debug)] |
| 158 | pub struct ProducersField<'a>(&'a IndexMap<String, String>); |
| 159 | |
| 160 | impl<'a> ProducersField<'a> { |
| 161 | /// Get the version associated with a name in the field |
| 162 | pub fn get(&self, name: &str) -> Option<&'a String> { |
| 163 | self.0.get(&name.to_owned()) |
| 164 | } |
| 165 | /// Iterate through all name-version pairs in the field |
| 166 | pub fn iter(&self) -> impl Iterator<Item = (&'a String, &'a String)> + 'a { |
| 167 | self.0.iter() |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | #[cfg (test)] |
| 172 | mod test { |
| 173 | use super::*; |
| 174 | use crate::{Metadata, Payload}; |
| 175 | use wasm_encoder::Module; |
| 176 | |
| 177 | #[test ] |
| 178 | fn producers_empty_module() { |
| 179 | let module = Module::new().finish(); |
| 180 | let mut producers = Producers::empty(); |
| 181 | producers.add("language" , "bar" , "" ); |
| 182 | producers.add("processed-by" , "baz" , "1.0" ); |
| 183 | |
| 184 | let module = producers.add_to_wasm(&module).unwrap(); |
| 185 | |
| 186 | match Payload::from_binary(&module).unwrap() { |
| 187 | Payload::Module(Metadata { |
| 188 | name, producers, .. |
| 189 | }) => { |
| 190 | assert_eq!(name, None); |
| 191 | let producers = producers.expect("some producers" ); |
| 192 | assert_eq!(producers.get("language" ).unwrap().get("bar" ).unwrap(), "" ); |
| 193 | assert_eq!( |
| 194 | producers.get("processed-by" ).unwrap().get("baz" ).unwrap(), |
| 195 | "1.0" |
| 196 | ); |
| 197 | } |
| 198 | _ => panic!("metadata should be module" ), |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | #[test ] |
| 203 | fn producers_add_another_field() { |
| 204 | let module = Module::new().finish(); |
| 205 | let mut producers = Producers::empty(); |
| 206 | producers.add("language" , "bar" , "" ); |
| 207 | producers.add("processed-by" , "baz" , "1.0" ); |
| 208 | let module = producers.add_to_wasm(&module).unwrap(); |
| 209 | |
| 210 | let mut producers = Producers::empty(); |
| 211 | producers.add("language" , "waaat" , "" ); |
| 212 | let module = producers.add_to_wasm(&module).unwrap(); |
| 213 | |
| 214 | match Payload::from_binary(&module).unwrap() { |
| 215 | Payload::Module(Metadata { |
| 216 | name, producers, .. |
| 217 | }) => { |
| 218 | assert_eq!(name, None); |
| 219 | let producers = producers.expect("some producers" ); |
| 220 | assert_eq!(producers.get("language" ).unwrap().get("bar" ).unwrap(), "" ); |
| 221 | assert_eq!(producers.get("language" ).unwrap().get("waaat" ).unwrap(), "" ); |
| 222 | assert_eq!( |
| 223 | producers.get("processed-by" ).unwrap().get("baz" ).unwrap(), |
| 224 | "1.0" |
| 225 | ); |
| 226 | } |
| 227 | _ => panic!("metadata should be module" ), |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | #[test ] |
| 232 | fn producers_overwrite_field() { |
| 233 | let module = Module::new().finish(); |
| 234 | let mut producers = Producers::empty(); |
| 235 | producers.add("processed-by" , "baz" , "1.0" ); |
| 236 | let module = producers.add_to_wasm(&module).unwrap(); |
| 237 | |
| 238 | let mut producers = Producers::empty(); |
| 239 | producers.add("processed-by" , "baz" , "420" ); |
| 240 | let module = producers.add_to_wasm(&module).unwrap(); |
| 241 | |
| 242 | match Payload::from_binary(&module).unwrap() { |
| 243 | Payload::Module(Metadata { producers, .. }) => { |
| 244 | let producers = producers.expect("some producers" ); |
| 245 | assert_eq!( |
| 246 | producers.get("processed-by" ).unwrap().get("baz" ).unwrap(), |
| 247 | "420" |
| 248 | ); |
| 249 | } |
| 250 | _ => panic!("metadata should be module" ), |
| 251 | } |
| 252 | } |
| 253 | } |
| 254 | |