wasm_metadata/
producers.rs

1use anyhow::Result;
2use indexmap::{map::Entry, IndexMap};
3use serde_derive::Serialize;
4use wasm_encoder::Encode;
5use wasmparser::{BinaryReader, KnownCustom, Parser, ProducersSectionReader};
6
7use 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)]
12pub struct Producers(
13    #[serde(serialize_with = "indexmap::map::serde_seq::serialize")]
14    IndexMap<String, IndexMap<String, String>>,
15);
16
17impl Default for Producers {
18    fn default() -> Self {
19        Self::empty()
20    }
21}
22
23impl 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)]
158pub struct ProducersField<'a>(&'a IndexMap<String, String>);
159
160impl<'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)]
172mod 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}