1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
use symbolic_common::Language;

use super::{raw, PortablePdbCache};

/// Line information for a given IL offset in a function.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct LineInfo<'data> {
    /// The line in the source file.
    pub line: u32,
    /// The source file's name.
    pub file_name: &'data str,
    /// The source language.
    pub file_lang: Language,
}

impl<'data> PortablePdbCache<'data> {
    /// Looks up line information for a function in the cache.
    ///
    /// `func_idx` is the (1-based) index of the function in the ECMA-335 `MethodDef` table
    /// (see the ECMA-335 spec, Section II.22.26). In C#, it is encoded in the
    /// [`MetadataToken`](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.memberinfo.metadatatoken?view=net-6.0#system-reflection-memberinfo-metadatatoken)
    /// property on the [`MethodBase`](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase?view=net-6.0) class.
    /// See [Metadata Tokens](https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms404456(v=vs.100)) for an
    /// explanation of the encoding.
    ///
    /// `il_offset` is the offset from the start of the method's Intermediate Language code.
    /// It can be obtained via the [`StackFrame.GetILOffset`](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stackframe.getiloffset?view=net-6.0#system-diagnostics-stackframe-getiloffset)
    /// method.
    pub fn lookup(&self, func_idx: u32, il_offset: u32) -> Option<LineInfo<'data>> {
        let range = raw::Range {
            func_idx,
            il_offset,
        };
        let sl = match self.ranges.binary_search(&range) {
            Ok(idx) => self.source_locations.get(idx)?,
            Err(idx) => {
                let idx = idx.checked_sub(1)?;
                let range = self.ranges.get(idx)?;
                if range.func_idx < func_idx {
                    return None;
                }

                self.source_locations.get(idx)?
            }
        };

        let (file_name, file_lang) = self.get_file(sl.file_idx)?;

        Some(LineInfo {
            line: sl.line,
            file_name,
            file_lang,
        })
    }

    fn get_file(&self, idx: u32) -> Option<(&'data str, Language)> {
        let raw = self.files.get(idx as usize)?;
        let name = self.get_string(raw.name_offset)?;

        Some((name, Language::from_u32(raw.lang)))
    }

    /// Resolves a string reference to the pointed-to `&str` data.
    fn get_string(&self, offset: u32) -> Option<&'data str> {
        watto::StringTable::read(self.string_bytes, offset as usize).ok()
    }
}