wasm_metadata/
producers.rs

1use anyhow::Result;
2use indexmap::{map::Entry, IndexMap};
3use wasm_encoder::Encode;
4use wasmparser::{BinaryReader, KnownCustom, Parser, ProducersSectionReader};
5
6use crate::{rewrite_wasm, AddMetadata};
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))]
12pub 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
20impl Default for Producers {
21    fn default() -> Self {
22        Self::empty()
23    }
24}
25
26impl Producers {
27    /// Creates an empty producers section
28    pub fn empty() -> Self {
29        Producers(IndexMap::new())
30    }
31
32    /// Indicates if section is empty
33    pub fn is_empty(&self) -> bool {
34        self.0.is_empty()
35    }
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    pub fn from_wasm(bytes: &[u8]) -> Result<Option<Self>> {
42        let mut depth = 0;
43        for payload in Parser::new(0).parse_all(bytes) {
44            let payload = payload?;
45            use wasmparser::Payload::*;
46            match payload {
47                ModuleSection { .. } | ComponentSection { .. } => depth += 1,
48                End { .. } => depth -= 1,
49                CustomSection(c) if depth == 0 => {
50                    if let KnownCustom::Producers(_) = c.as_known() {
51                        let producers = Self::from_bytes(c.data(), c.data_offset())?;
52                        return Ok(Some(producers));
53                    }
54                }
55                _ => {}
56            }
57        }
58        Ok(None)
59    }
60    /// Read the producers section from a Wasm binary.
61    pub fn from_bytes(bytes: &[u8], offset: usize) -> Result<Self> {
62        let reader = BinaryReader::new(bytes, offset);
63        let section = ProducersSectionReader::new(reader)?;
64        let mut fields = IndexMap::new();
65        for field in section.into_iter() {
66            let field = field?;
67            let mut values = IndexMap::new();
68            for value in field.values.into_iter() {
69                let value = value?;
70                values.insert(value.name.to_owned(), value.version.to_owned());
71            }
72            fields.insert(field.name.to_owned(), values);
73        }
74        Ok(Producers(fields))
75    }
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    pub fn add(&mut self, field: &str, name: &str, version: &str) {
81        match self.0.entry(field.to_string()) {
82            Entry::Occupied(e) => {
83                e.into_mut().insert(name.to_owned(), version.to_owned());
84            }
85            Entry::Vacant(e) => {
86                let mut m = IndexMap::new();
87                m.insert(name.to_owned(), version.to_owned());
88                e.insert(m);
89            }
90        }
91    }
92
93    /// Add all values found in another `Producers` section. Values in `other` take
94    /// precedence.
95    pub fn merge(&mut self, other: &Self) {
96        for (field, values) in other.iter() {
97            for (name, version) in values.iter() {
98                self.add(field, name, version);
99            }
100        }
101    }
102
103    /// Get the contents of a field
104    pub fn get<'a>(&'a self, field: &str) -> Option<ProducersField<'a>> {
105        self.0.get(&field.to_owned()).map(ProducersField)
106    }
107
108    /// Iterate through all fields
109    pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, ProducersField<'a>)> + 'a {
110        self.0
111            .iter()
112            .map(|(name, field)| (name, ProducersField(field)))
113    }
114
115    /// Construct the fields specified by [`AddMetadata`]
116    pub(crate) fn from_meta(add: &AddMetadata) -> Self {
117        let mut s = Self::empty();
118        for (lang, version) in add.language.iter() {
119            s.add("language", &lang, &version);
120        }
121        for (name, version) in add.processed_by.iter() {
122            s.add("processed-by", &name, &version);
123        }
124        for (name, version) in add.sdk.iter() {
125            s.add("sdk", &name, &version);
126        }
127        s
128    }
129
130    /// Serialize into [`wasm_encoder::ProducersSection`].
131    pub(crate) fn section(&self) -> wasm_encoder::ProducersSection {
132        let mut section = wasm_encoder::ProducersSection::new();
133        for (fieldname, fieldvalues) in self.0.iter() {
134            let mut field = wasm_encoder::ProducersField::new();
135            for (name, version) in fieldvalues {
136                field.value(&name, &version);
137            }
138            section.field(&fieldname, &field);
139        }
140        section
141    }
142
143    /// Serialize into the raw bytes of a wasm custom section.
144    pub fn raw_custom_section(&self) -> Vec<u8> {
145        let mut ret = Vec::new();
146        self.section().encode(&mut ret);
147        ret
148    }
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    pub fn add_to_wasm(&self, input: &[u8]) -> Result<Vec<u8>> {
153        rewrite_wasm(&Default::default(), self, input)
154    }
155}
156
157/// Contents of a producers field
158#[derive(Debug)]
159pub struct ProducersField<'a>(&'a IndexMap<String, String>);
160
161impl<'a> ProducersField<'a> {
162    /// Get the version associated with a name in the field
163    pub fn get(&self, name: &str) -> Option<&'a String> {
164        self.0.get(&name.to_owned())
165    }
166    /// Iterate through all name-version pairs in the field
167    pub fn iter(&self) -> impl Iterator<Item = (&'a String, &'a String)> + 'a {
168        self.0.iter()
169    }
170}
171
172#[cfg(test)]
173mod 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}