Skip to main content

windows_metadata/reader/
index.rs

1use super::*;
2
3/// Indexes metadata as raw types and as Win32 `Apis`-expanded items.
4pub struct Index {
5    files: Vec<File>,
6    types: HashMap<String, HashMap<String, Vec<(usize, usize)>>>,
7    items: HashMap<String, HashMap<String, Vec<RawItem>>>,
8    nested: HashMap<(usize, usize), Vec<usize>>,
9    architecture: i32,
10}
11
12#[derive(Copy, Clone)]
13enum RawItem {
14    Type(usize, usize),  // (file_pos, typedef_pos)
15    Fn(usize, usize),    // (file_pos, methoddef_pos)
16    Const(usize, usize), // (file_pos, field_pos)
17}
18
19/// An individually-addressable item exposed by [`Index::iter_items`] and friends.
20#[derive(Copy, Clone, Debug)]
21pub enum Item<'a> {
22    Type(TypeDef<'a>),
23    Fn(MethodDef<'a>),
24    Const(Field<'a>),
25}
26
27impl Index {
28    /// Reads a single metadata file from disk and builds an `Index` for it.
29    #[must_use]
30    pub fn read<P: AsRef<std::path::Path>>(path: P) -> Option<Self> {
31        Some(Self::new(vec![File::read(path)?]))
32    }
33
34    /// Builds an `Index` over the given metadata files.
35    #[must_use]
36    pub fn new(files: Vec<File>) -> Self {
37        Self::new_for_architecture(files, 0)
38    }
39
40    /// Builds an `Index` filtered to one architecture bit (1=X86, 2=X64, 4=Arm64). A zero bit
41    /// preserves all architecture-specific rows.
42    #[must_use]
43    pub fn new_for_architecture(files: Vec<File>, architecture: i32) -> Self {
44        // Build types first so `Apis` detection can inspect synthesized `TypeDef` values.
45        let mut types: HashMap<String, HashMap<String, Vec<(usize, usize)>>> = HashMap::new();
46        let mut nested: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
47
48        for (file_pos, file) in files.iter().enumerate() {
49            for def_pos in file.TypeDef() {
50                let namespace = file.str(def_pos, TypeDef::TABLE, 2);
51
52                if namespace.is_empty() {
53                    continue;
54                }
55
56                let name = file.str(def_pos, TypeDef::TABLE, 1);
57
58                types
59                    .entry(namespace.to_string())
60                    .or_default()
61                    .entry(trim_tick(name).to_string())
62                    .or_default()
63                    .push((file_pos, def_pos));
64            }
65
66            for map in file.NestedClass() {
67                let inner = file.usize(map, NestedClass::TABLE, 0) - 1;
68                let outer = file.usize(map, NestedClass::TABLE, 1) - 1;
69                nested.entry((file_pos, outer)).or_default().push(inner);
70            }
71        }
72
73        let mut index = Self {
74            files,
75            types,
76            items: HashMap::new(),
77            nested,
78            architecture,
79        };
80
81        // Expand Win32 `Apis` into function/constant items.
82        let mut items: HashMap<String, HashMap<String, Vec<RawItem>>> = HashMap::new();
83
84        for (namespace, name, ty) in index.iter() {
85            let apis = !ty.flags().contains(TypeAttributes::WindowsRuntime)
86                && ty.category() == TypeCategory::Class
87                && name == "Apis";
88
89            if apis {
90                for method in ty
91                    .methods()
92                    .filter(|method| index.supports_architecture(*method))
93                {
94                    items
95                        .entry(namespace.to_string())
96                        .or_default()
97                        .entry(method.name().to_string())
98                        .or_default()
99                        .push(RawItem::Fn(ty.0.file, method.0.pos));
100                }
101                for field in ty
102                    .fields()
103                    .filter(|field| index.supports_architecture(*field))
104                {
105                    items
106                        .entry(namespace.to_string())
107                        .or_default()
108                        .entry(field.name().to_string())
109                        .or_default()
110                        .push(RawItem::Const(ty.0.file, field.0.pos));
111                }
112            } else {
113                items
114                    .entry(namespace.to_string())
115                    .or_default()
116                    .entry(name.to_string())
117                    .or_default()
118                    .push(RawItem::Type(ty.0.file, ty.0.pos));
119            }
120        }
121
122        index.items = items;
123        index
124    }
125
126    /// Leaks `self` for consumers that need `'static` metadata rows.
127    #[must_use]
128    pub fn leak(self) -> &'static Self {
129        Box::leak(Box::new(self))
130    }
131
132    /// Reads one metadata file and leaks the resulting `Index`.
133    #[must_use]
134    pub fn read_static<P: AsRef<std::path::Path>>(path: P) -> Option<&'static Self> {
135        Some(Self::new(vec![File::read(path)?]).leak())
136    }
137
138    pub(crate) fn files(&self, pos: usize) -> &File {
139        &self.files[pos]
140    }
141
142    /// Iterates `(namespace, name, TypeDef)` triples over every type in the index.
143    pub fn iter(&self) -> impl Iterator<Item = (&str, &str, TypeDef<'_>)> + '_ {
144        self.types
145            .iter()
146            .flat_map(|(namespace, types)| {
147                types
148                    .iter()
149                    .map(move |(name, types)| (namespace.as_str(), name.as_str(), types))
150            })
151            .flat_map(|(namespace, name, types)| types.iter().map(move |ty| (namespace, name, ty)))
152            .map(|(namespace, name, (file, pos))| {
153                (namespace, name, TypeDef(Row::new(self, *file, *pos)))
154            })
155            .filter(|(_, _, def)| self.supports_architecture(*def))
156    }
157
158    /// Iterates every `TypeDef` in the index.
159    pub fn types(&self) -> impl Iterator<Item = TypeDef<'_>> + '_ {
160        self.types
161            .values()
162            .flat_map(|types| types.values())
163            .flatten()
164            .map(|(file, pos)| TypeDef(Row::new(self, *file, *pos)))
165            .filter(|def| self.supports_architecture(*def))
166    }
167
168    /// Iterates the `TypeDef`s matching `(namespace, name)`.
169    pub fn get(&self, namespace: &str, name: &str) -> impl Iterator<Item = TypeDef<'_>> + '_ {
170        self.types
171            .get(namespace)
172            .and_then(|types| types.get(name))
173            .into_iter()
174            .flatten()
175            .map(|(file, pos)| TypeDef(Row::new(self, *file, *pos)))
176            .filter(|def| self.supports_architecture(*def))
177    }
178
179    /// Returns whether any type lives in `namespace`.
180    pub fn contains_namespace(&self, namespace: &str) -> bool {
181        self.types.get(namespace).is_some_and(|types| {
182            types
183                .values()
184                .flatten()
185                .any(|(file, pos)| self.supports_architecture(TypeDef(Row::new(self, *file, *pos))))
186        })
187    }
188
189    /// Returns whether the `(namespace, name)` type exists.
190    pub fn contains(&self, namespace: &str, name: &str) -> bool {
191        self.get(namespace, name).next().is_some()
192    }
193
194    /// Returns the assembly name of the first file in which `(namespace, name)` was defined.
195    pub fn assembly_name(&self, namespace: &str, name: &str) -> Option<&str> {
196        self.types
197            .get(namespace)
198            .and_then(|types| types.get(name))
199            .and_then(|types| {
200                types.iter().find(|(file, pos)| {
201                    self.supports_architecture(TypeDef(Row::new(self, *file, *pos)))
202                })
203            })
204            .map(|(file, _)| self.files(*file))
205            .and_then(|file| file.assembly_name())
206    }
207
208    /// Returns the single `TypeDef` matching `(namespace, name)`, panicking if there are zero
209    /// or more than one.
210    #[track_caller]
211    pub fn expect(&self, namespace: &str, name: &str) -> TypeDef<'_> {
212        let mut iter = self.get(namespace, name);
213
214        if let Some(def) = iter.next() {
215            if iter.next().is_none() {
216                def
217            } else {
218                panic!("more than one type found: {namespace}.{name}");
219            }
220        } else {
221            panic!("type not found: {namespace}.{name}")
222        }
223    }
224
225    /// Iterates the types directly nested inside `ty`. Use [`Self::nested_recursive`] to recurse.
226    pub fn nested(&self, ty: TypeDef) -> impl Iterator<Item = TypeDef<'_>> + '_ {
227        self.nested
228            .get(&(ty.0.file, ty.0.pos))
229            .into_iter()
230            .flatten()
231            .copied()
232            .map(move |pos| {
233                TypeDef(Row {
234                    index: self,
235                    file: ty.0.file,
236                    pos,
237                })
238            })
239            .filter(|def| self.supports_architecture(*def))
240    }
241
242    /// Depth-first walk of every type nested directly or transitively inside `ty`.
243    pub fn nested_recursive<'a>(&'a self, ty: TypeDef<'a>) -> Vec<TypeDef<'a>> {
244        let mut out = Vec::new();
245        self.collect_nested(ty, &mut out);
246        out
247    }
248
249    fn collect_nested<'a>(&'a self, ty: TypeDef<'a>, out: &mut Vec<TypeDef<'a>>) {
250        for inner in self.nested(ty) {
251            out.push(inner);
252            self.collect_nested(inner, out);
253        }
254    }
255
256    fn supports_architecture<'a, R: HasAttributes<'a>>(&self, row: R) -> bool {
257        let arches = row.arches();
258        self.architecture == 0 || arches == 0 || arches & self.architecture != 0
259    }
260
261    /// Iterates every namespace that contains at least one item.
262    pub fn namespaces(&self) -> impl Iterator<Item = &str> + '_ {
263        self.items.keys().map(String::as_str)
264    }
265
266    /// Iterates `(namespace, name, Item)` triples over every item in the index.
267    pub fn iter_items(&self) -> impl Iterator<Item = (&str, &str, Item<'_>)> + '_ {
268        self.items
269            .iter()
270            .flat_map(|(namespace, items)| {
271                items
272                    .iter()
273                    .map(move |(name, items)| (namespace.as_str(), name.as_str(), items))
274            })
275            .flat_map(move |(namespace, name, items)| {
276                items
277                    .iter()
278                    .map(move |raw| (namespace, name, self.item(*raw)))
279            })
280    }
281
282    /// Iterates every `Item` in the index.
283    pub fn items(&self) -> impl Iterator<Item = Item<'_>> + '_ {
284        self.items
285            .values()
286            .flat_map(|items| items.values())
287            .flatten()
288            .map(move |raw| self.item(*raw))
289    }
290
291    /// Iterates `(name, Item)` pairs in `namespace`.
292    pub fn namespace_items(&self, namespace: &str) -> impl Iterator<Item = (&str, Item<'_>)> + '_ {
293        self.items
294            .get(namespace)
295            .into_iter()
296            .flatten()
297            .flat_map(move |(name, items)| {
298                items
299                    .iter()
300                    .map(move |raw| (name.as_str(), self.item(*raw)))
301            })
302    }
303
304    /// Iterates the `Item`s matching `(namespace, name)`.
305    pub fn get_item(&self, namespace: &str, name: &str) -> impl Iterator<Item = Item<'_>> + '_ {
306        self.items
307            .get(namespace)
308            .and_then(|items| items.get(name))
309            .into_iter()
310            .flatten()
311            .map(move |raw| self.item(*raw))
312    }
313
314    /// Returns the single `Item` matching `(namespace, name)`, panicking if there are zero
315    /// or more than one.
316    #[track_caller]
317    pub fn expect_item(&self, namespace: &str, name: &str) -> Item<'_> {
318        let mut iter = self.get_item(namespace, name);
319
320        if let Some(item) = iter.next() {
321            if iter.next().is_none() {
322                item
323            } else {
324                panic!("more than one item found: {namespace}.{name}");
325            }
326        } else {
327            panic!("item not found: {namespace}.{name}")
328        }
329    }
330
331    fn item(&self, raw: RawItem) -> Item<'_> {
332        match raw {
333            RawItem::Type(file, pos) => Item::Type(TypeDef(Row::new(self, file, pos))),
334            RawItem::Fn(file, pos) => Item::Fn(MethodDef(Row::new(self, file, pos))),
335            RawItem::Const(file, pos) => Item::Const(Field(Row::new(self, file, pos))),
336        }
337    }
338}