llvm_mapper/block/
strtab.rs

1//! Functionality for mapping the `STRTAB_BLOCK` block.
2
3use std::str::Utf8Error;
4
5use llvm_support::bitcodes::{IrBlockId, StrtabCode};
6use llvm_support::StrtabRef;
7use thiserror::Error;
8
9use crate::block::IrBlock;
10use crate::map::{MapError, PartialMapCtx};
11use crate::record::RecordBlobError;
12use crate::unroll::{UnrolledBlock, UnrolledRecord};
13
14/// Errors that can occur when accessing a string table.
15#[derive(Debug, Error)]
16pub enum StrtabError {
17    /// The string table is missing its blob.
18    #[error("malformed string table: missing blob")]
19    MissingBlob,
20
21    /// The blob containing the string table is invalid.
22    #[error("invalid string table: {0}")]
23    BadBlob(#[from] RecordBlobError),
24
25    /// The requested range is invalid.
26    #[error("requested range in string table is invalid")]
27    BadRange,
28
29    /// The requested string is not UTF-8.
30    #[error("could not decode range into a UTF-8 string: {0}")]
31    BadString(#[from] Utf8Error),
32
33    /// A generic mapping error occured.
34    #[error("mapping error in string table")]
35    Map(#[from] MapError),
36}
37
38/// Models the `STRTAB_BLOCK` block.
39#[derive(Clone, Debug, Default)]
40pub struct Strtab(Vec<u8>);
41
42impl AsRef<[u8]> for Strtab {
43    fn as_ref(&self) -> &[u8] {
44        &self.0
45    }
46}
47
48impl IrBlock for Strtab {
49    type Error = StrtabError;
50
51    const BLOCK_ID: IrBlockId = IrBlockId::Strtab;
52
53    fn try_map_inner(block: &UnrolledBlock, _ctx: &mut PartialMapCtx) -> Result<Self, Self::Error> {
54        // TODO(ww): The docs also claim that there's only one STRTAB_BLOB per STRTAB_BLOCK,
55        // but at least one person has reported otherwise here:
56        // https://lists.llvm.org/pipermail/llvm-dev/2020-August/144327.html
57        // Needs investigation.
58        let strtab = block
59            .records()
60            .one(StrtabCode::Blob as u64)
61            .ok_or(StrtabError::MissingBlob)
62            .and_then(|r| r.try_blob(0).map_err(StrtabError::from))?;
63
64        Ok(Self(strtab))
65    }
66}
67
68impl Strtab {
69    /// Get a string in the string table by its index and length.
70    ///
71    /// Returns `None` on all of the error conditions associated with
72    /// [`try_get`](Strtab::try_get).
73    pub fn get(&self, sref: &StrtabRef) -> Option<&str> {
74        self.try_get(sref).ok()
75    }
76
77    /// Get a string in the string table by its index and length.
78    ///
79    /// Returns an error if the requested span is invalid, or if the extracted
80    /// slice isn't a valid string.
81    pub fn try_get(&self, sref: &StrtabRef) -> Result<&str, StrtabError> {
82        let inner = self.as_ref();
83
84        if sref.size == 0 || sref.offset >= inner.len() || sref.offset + sref.size > inner.len() {
85            return Err(StrtabError::BadRange);
86        }
87
88        Ok(std::str::from_utf8(
89            &inner[sref.offset..sref.offset + sref.size],
90        )?)
91    }
92
93    /// Attempts to read a record's name from the string table.
94    ///
95    /// Adheres to the convention that the first two fields in the record are
96    /// the string's offset and length into the string table.
97    ///
98    /// Panic safety: precondition: `record.fields().len() >= 2`
99    pub(crate) fn read_name(&self, record: &UnrolledRecord) -> Result<&str, StrtabError> {
100        let fields = record.fields();
101
102        self.try_get(&(fields[0], fields[1]).into())
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    fn sref(tup: (usize, usize)) -> StrtabRef {
111        tup.into()
112    }
113
114    #[test]
115    fn test_strtab() {
116        let inner = "this is a string table";
117        let strtab = Strtab(inner.into());
118        assert_eq!(strtab.get(&sref((0, 4))).unwrap(), "this");
119        assert_eq!(strtab.get(&sref((0, 7))).unwrap(), "this is");
120        assert_eq!(strtab.get(&sref((8, 14))).unwrap(), "a string table");
121        assert_eq!(
122            strtab.get(&sref((0, inner.len()))).unwrap(),
123            "this is a string table"
124        );
125
126        assert!(strtab.get(&sref((inner.len(), 0))).is_none());
127        assert!(strtab.get(&sref((0, inner.len() + 1))).is_none());
128        assert!(strtab.get(&sref((0, 0))).is_none());
129    }
130}