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 | |