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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::cmp::{Ordering, PartialOrd};
use std::fmt;

/// Describes a "debug location" (source location)
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct DebugLoc {
    /// The source line number
    pub line: u32,
    /// The source column number
    ///
    /// `Instruction`s and `Terminator`s have this info (and will have `Some`
    /// here), while `GlobalVariable`s and `Function`s do not have this info (and
    /// will have `None`)
    pub col: Option<u32>,
    /// The source filename
    pub filename: String,
    /// The source directory, if available
    pub directory: Option<String>,
}

impl PartialOrd for DebugLoc {
    #[rustfmt::skip] // self on one line, other on the next
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        // compare in the order (directory, filename, line, col)
        Some(
            (&self.directory, &self.filename, &self.line, &self.col)
                .cmp(&(&other.directory, &other.filename, &other.line, &other.col))
        )
    }
}

impl Ord for DebugLoc {
    #[rustfmt::skip] // self on one line, other on the next
    fn cmp(&self, other: &Self) -> Ordering {
        // compare in the order (directory, filename, line, col)
        (&self.directory, &self.filename, &self.line, &self.col)
            .cmp(&(&other.directory, &other.filename, &other.line, &other.col))
    }
}

impl fmt::Display for DebugLoc {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let pretty_directory = match &self.directory {
            Some(dir) => dir,
            None => "",
        };
        let need_slash = match &self.directory {
            Some(dir) => !dir.is_empty() && !dir.ends_with('/') && !self.filename.starts_with('/'),
            None => false,
        };
        let pretty_filename = match &self.filename as &str {
            "" => "<no filename available>",
            filename if !pretty_directory.is_empty() => {
                filename.trim_start_matches(pretty_directory)
            },
            filename => filename,
        };
        let pretty_column = match self.col {
            Some(col) => format!(", col {}", col),
            None => String::new(),
        };
        write!(
            f,
            "{}{}{}, line {}{}",
            pretty_directory,
            if need_slash { "/" } else { "" },
            pretty_filename,
            self.line,
            pretty_column,
        )
    }
}

pub trait HasDebugLoc {
    /// Returns the `DebugLoc` associated with the given `Instruction`,
    /// `Terminator`, `GlobalVariable`, or `Function`; or `None` if it doesn't
    /// have a `DebugLoc`.
    ///
    /// Reasons something might not have a `DebugLoc` include:
    ///     (1) the file was compiled without debuginfo;
    ///     (2) for an `Instruction`, it might not directly correspond to any source
    ///     line. For instance, it may be just setting up the stack frame for a
    ///     function.
    fn get_debug_loc(&self) -> &Option<DebugLoc>;
}

// ********* //
// from_llvm //
// ********* //

use crate::from_llvm::*;
use crate::llvm_sys::*;

impl DebugLoc {
    /// `value`: must represent an Instruction, Terminator, GlobalVariable, or Function
    ///
    /// Returns `None` if the object does not have a `DebugLoc`
    pub(crate) fn from_llvm_no_col(value: LLVMValueRef) -> Option<Self> {
        match unsafe { get_debugloc_filename(value) } {
            None => None, // if no filename, assume no debugloc. To my knowledge, everything with a debugloc has a filename.
            Some(filename) => Some(Self {
                line: unsafe { LLVMGetDebugLocLine(value) },
                col: None,
                filename,
                directory: unsafe { get_debugloc_directory(value) },
            }),
        }
    }

    /// `value`: must represent an Instruction or Terminator
    ///
    /// Returns `None` if the object does not have a `DebugLoc`
    pub(crate) fn from_llvm_with_col(value: LLVMValueRef) -> Option<Self> {
        match Self::from_llvm_no_col(value) {
            Some(mut debugloc) => {
                debugloc.col = Some(unsafe { LLVMGetDebugLocColumn(value) });
                Some(debugloc)
            },
            None => None,
        }
    }
}