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