gimli/write/
str.rs

1use alloc::vec::Vec;
2use indexmap::IndexSet;
3use std::ops::{Deref, DerefMut};
4
5use crate::common::{DebugLineStrOffset, DebugStrOffset, SectionId};
6use crate::write::{BaseId, Result, Section, Writer};
7
8// Requirements:
9// - values are `[u8]`, null bytes are not allowed
10// - insertion returns a fixed id
11// - inserting a duplicate returns the id of the existing value
12// - able to convert an id to a section offset
13// Optional?
14// - able to get an existing value given an id
15//
16// Limitations of current implementation (using IndexSet):
17// - inserting requires either an allocation for duplicates,
18//   or a double lookup for non-duplicates
19// - doesn't preserve offsets when updating an existing `.debug_str` section
20//
21// Possible changes:
22// - calculate offsets as we add values, and use that as the id.
23//   This would avoid the need for DebugStrOffsets but would make it
24//   hard to implement `get`.
25macro_rules! define_string_table {
26    ($name:ident, $id:ident, $section:ident, $offset:ident, $offsets:ident, $docs:expr) => {
27        #[doc=$docs]
28        #[derive(Debug, Default)]
29        pub struct $name {
30            base_id: BaseId,
31            strings: IndexSet<Vec<u8>>,
32        }
33
34        impl $name {
35            /// Add a string to the string table and return its id.
36            ///
37            /// If the string already exists, then return the id of the existing string.
38            ///
39            /// # Panics
40            ///
41            /// Panics if `bytes` contains a null byte.
42            pub fn add<T>(&mut self, bytes: T) -> $id
43            where
44                T: Into<Vec<u8>>,
45            {
46                let bytes = bytes.into();
47                assert!(!bytes.contains(&0));
48                let (index, _) = self.strings.insert_full(bytes);
49                $id::new(self.base_id, index)
50            }
51
52            /// Return the number of strings in the table.
53            #[inline]
54            pub fn count(&self) -> usize {
55                self.strings.len()
56            }
57
58            /// Get a reference to a string in the table.
59            ///
60            /// # Panics
61            ///
62            /// Panics if `id` is invalid.
63            pub fn get(&self, id: $id) -> &[u8] {
64                debug_assert_eq!(self.base_id, id.base_id);
65                self.strings.get_index(id.index).map(Vec::as_slice).unwrap()
66            }
67
68            /// Write the string table to the `.debug_str` section.
69            ///
70            /// Returns the offsets at which the strings are written.
71            pub fn write<W: Writer>(&self, w: &mut $section<W>) -> Result<$offsets> {
72                let mut offsets = Vec::new();
73                let mut empty = None;
74                for bytes in self.strings.iter() {
75                    offsets.push(w.offset());
76                    w.write(bytes)?;
77                    if empty.is_none() {
78                        empty = Some(w.offset());
79                    }
80                    w.write_u8(0)?;
81                }
82                // Record the offset of the first null, for use as an empty string.
83                if let Some(empty) = empty {
84                    offsets.push(empty);
85                }
86
87                Ok($offsets {
88                    base_id: self.base_id,
89                    offsets,
90                })
91            }
92        }
93
94        impl $offsets {
95            pub(crate) fn get_empty(&self) -> Option<$id> {
96                if self.offsets.is_empty() {
97                    None
98                } else {
99                    // The last offset is always the empty string.
100                    Some($id::new(self.base_id, self.offsets.len() - 1))
101                }
102            }
103        }
104    };
105}
106
107define_id!(StringId, "An identifier for a string in a `StringTable`.");
108
109define_string_table!(
110    StringTable,
111    StringId,
112    DebugStr,
113    DebugStrOffset,
114    DebugStrOffsets,
115    "A table of strings that will be stored in a `.debug_str` section."
116);
117
118define_section!(DebugStr, DebugStrOffset, "A writable `.debug_str` section.");
119
120define_offsets!(
121    DebugStrOffsets: StringId => DebugStrOffset,
122    "The section offsets of all strings within a `.debug_str` section."
123);
124
125define_id!(
126    LineStringId,
127    "An identifier for a string in a `LineStringTable`."
128);
129
130define_string_table!(
131    LineStringTable,
132    LineStringId,
133    DebugLineStr,
134    DebugLineStrOffset,
135    DebugLineStrOffsets,
136    "A table of strings that will be stored in a `.debug_line_str` section."
137);
138
139define_section!(
140    DebugLineStr,
141    DebugLineStrOffset,
142    "A writable `.debug_line_str` section."
143);
144
145define_offsets!(
146    DebugLineStrOffsets: LineStringId => DebugLineStrOffset,
147    "The section offsets of all strings within a `.debug_line_str` section."
148);
149
150#[cfg(test)]
151#[cfg(feature = "read")]
152mod tests {
153    use super::*;
154    use crate::read;
155    use crate::write::EndianVec;
156    use crate::LittleEndian;
157
158    #[test]
159    fn test_string_table() {
160        let mut strings = StringTable::default();
161        assert_eq!(strings.count(), 0);
162        let id1 = strings.add(&b"one"[..]);
163        let id2 = strings.add(&b"two"[..]);
164        assert_eq!(strings.add(&b"one"[..]), id1);
165        assert_eq!(strings.add(&b"two"[..]), id2);
166        assert_eq!(strings.get(id1), &b"one"[..]);
167        assert_eq!(strings.get(id2), &b"two"[..]);
168        assert_eq!(strings.count(), 2);
169
170        let mut debug_str = DebugStr::from(EndianVec::new(LittleEndian));
171        let offsets = strings.write(&mut debug_str).unwrap();
172        assert_eq!(debug_str.slice(), b"one\0two\0");
173        assert_eq!(offsets.get(id1), DebugStrOffset(0));
174        assert_eq!(offsets.get(id2), DebugStrOffset(4));
175        assert_eq!(offsets.get(offsets.get_empty().unwrap()), DebugStrOffset(3));
176        assert_eq!(offsets.count(), 3);
177    }
178
179    #[test]
180    fn test_string_table_read() {
181        let mut strings = StringTable::default();
182        let id1 = strings.add(&b"one"[..]);
183        let id2 = strings.add(&b"two"[..]);
184
185        let mut debug_str = DebugStr::from(EndianVec::new(LittleEndian));
186        let offsets = strings.write(&mut debug_str).unwrap();
187
188        let read_debug_str = read::DebugStr::new(debug_str.slice(), LittleEndian);
189        let str1 = read_debug_str.get_str(offsets.get(id1)).unwrap();
190        let str2 = read_debug_str.get_str(offsets.get(id2)).unwrap();
191        let str3 = read_debug_str
192            .get_str(offsets.get(offsets.get_empty().unwrap()))
193            .unwrap();
194        assert_eq!(str1.slice(), &b"one"[..]);
195        assert_eq!(str2.slice(), &b"two"[..]);
196        assert_eq!(str3.slice(), b"");
197    }
198}