Skip to main content

boa_engine/vm/source_info/
mod.rs

1use std::{
2    fmt::Display,
3    path::{Path, PathBuf},
4    rc::Rc,
5};
6
7use boa_ast::Position;
8
9mod builder;
10
11use boa_gc::{Finalize, Trace};
12use boa_string::JsString;
13pub(crate) use builder::SourceMapBuilder;
14
15use crate::SpannedSourceText;
16
17#[cfg(test)]
18mod tests;
19
20/// Source information.
21#[derive(Debug, Default, Clone, Finalize, Trace)]
22// SAFETY: Nothing in Inner needs tracing, so this is safe.
23#[boa_gc(unsafe_empty_trace)]
24pub(crate) struct SourceInfo {
25    inner: Rc<Inner>,
26}
27
28impl SourceInfo {
29    pub(crate) fn new(
30        source_map: SourceMap,
31        function_name: JsString,
32        source_text_spanned: SpannedSourceText,
33    ) -> Self {
34        Self {
35            inner: Rc::new(Inner {
36                map: source_map,
37                function_name,
38                text_spanned: source_text_spanned,
39            }),
40        }
41    }
42
43    pub(crate) fn map(&self) -> &SourceMap {
44        &self.inner.map
45    }
46
47    pub(crate) fn function_name(&self) -> &JsString {
48        &self.inner.function_name
49    }
50
51    pub(crate) fn text_spanned(&self) -> &SpannedSourceText {
52        &self.inner.text_spanned
53    }
54}
55
56#[derive(Debug, Default, Clone)]
57struct Inner {
58    map: SourceMap,
59    function_name: JsString,
60
61    text_spanned: SpannedSourceText,
62}
63
64/// Bytecode to source code mapping.
65#[derive(Debug, Default, Clone)]
66pub(crate) struct SourceMap {
67    entries: Box<[Entry]>,
68    path: SourcePath,
69}
70
71impl SourceMap {
72    pub(crate) fn new(entries: Box<[Entry]>, path: SourcePath) -> Self {
73        Self { entries, path }
74    }
75
76    pub(crate) fn entries(&self) -> &[Entry] {
77        &self.entries
78    }
79
80    pub(crate) fn find(&self, pc: u32) -> Option<Position> {
81        find_entry(self.entries(), pc)
82    }
83
84    pub(crate) fn path(&self) -> &SourcePath {
85        &self.path
86    }
87}
88
89fn find_entry(entries: &[Entry], pc: u32) -> Option<Position> {
90    let first = entries.first()?;
91
92    if pc < first.pc() {
93        return None;
94    }
95
96    let mut low = 0;
97    let mut high = entries.len() - 1;
98
99    while low <= high {
100        let mid = low.midpoint(high);
101        let entry = &entries[mid];
102        let start = entry.pc;
103
104        let end = entries.get(mid + 1).map_or(u32::MAX, |entry| entry.pc);
105
106        if pc < start {
107            high = mid;
108        } else if pc >= end {
109            low = mid + 1;
110        } else {
111            return entry.position();
112        }
113    }
114
115    // Since the last element defines the start of the end of the range,
116    // therefore we return the last element's position.
117    entries.last().and_then(Entry::position)
118}
119
120// TODO: The line number increments slower than column,
121//       maybe we can take advantage of this, for memory optimization?
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub(crate) struct Entry {
124    /// Represents the start of a bytecode range that falls under the given position.
125    ///
126    /// The end of the range is the pc of the next entry.
127    /// If the entry is the last, the end of the range is [`u32::MAX`].
128    pub(crate) pc: u32,
129
130    /// Source code [`Position`].
131    pub(crate) position: Option<Position>,
132}
133
134impl Entry {
135    pub(crate) const fn pc(&self) -> u32 {
136        self.pc
137    }
138
139    pub(crate) const fn position(&self) -> Option<Position> {
140        self.position
141    }
142}
143
144/// The Path and type of [`boa_engine::Source`]. This applies to functions and objects.
145#[derive(Debug, Default, Clone, PartialEq, Eq)]
146pub enum SourcePath {
147    /// There was no source associated with this call.
148    #[default]
149    None,
150    /// The source is from an `eval()` call.
151    // TODO: Could add more information, like path in which the eval is located.
152    Eval,
153    /// The source is from a `JSON.parse()` call.
154    // TODO: Could add more information, like path in which the JSON.parse is located.
155    Json,
156    /// The source is from a file or module. This contains the [`Path`] of the
157    /// module (e.g. the absolute file path).
158    Path(Rc<Path>),
159}
160
161impl From<Option<PathBuf>> for SourcePath {
162    fn from(value: Option<PathBuf>) -> Self {
163        match value {
164            None => Self::None,
165            Some(path) => Self::Path(path.into()),
166        }
167    }
168}
169
170impl From<Option<Rc<Path>>> for SourcePath {
171    fn from(value: Option<Rc<Path>>) -> Self {
172        match value {
173            None => Self::None,
174            Some(path) => Self::Path(path),
175        }
176    }
177}
178
179impl Display for SourcePath {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        match self {
182            SourcePath::None => f.write_str("unknown at "),
183            SourcePath::Eval => f.write_str("eval at "),
184            SourcePath::Json => f.write_str("json at "),
185            SourcePath::Path(path) => write!(f, "{}", path.display()),
186        }
187    }
188}
189
190impl SourcePath {
191    pub(crate) fn is_none(&self) -> bool {
192        matches!(self, Self::None)
193    }
194
195    pub(crate) fn is_some(&self) -> bool {
196        !self.is_none()
197    }
198}
199
200/// A struct containing information about native source code.
201///
202/// # Note
203///
204/// If the `native-backtrace` feature is not enabled the this becomes [zero sized type][zst].
205///
206/// [zst]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts
207#[derive(Debug, Clone, Copy)]
208pub struct NativeSourceInfo {
209    #[cfg(feature = "native-backtrace")]
210    inner: &'static std::panic::Location<'static>,
211
212    #[cfg(not(feature = "native-backtrace"))]
213    inner: std::marker::PhantomData<()>,
214}
215
216impl NativeSourceInfo {
217    /// Returns the source location of the caller of this function.
218    ///
219    /// If that function’s caller is annotated with `#[track_caller]`, then its call
220    /// location will be returned, and so on up the stack to the first call within
221    /// a non-tracked function body.
222    #[inline]
223    #[must_use]
224    #[cfg_attr(feature = "native-backtrace", track_caller)]
225    pub const fn caller() -> Self {
226        Self {
227            #[cfg(feature = "native-backtrace")]
228            inner: std::panic::Location::caller(),
229
230            #[cfg(not(feature = "native-backtrace"))]
231            inner: std::marker::PhantomData,
232        }
233    }
234
235    /// Return a [`std::panic::Location`].
236    ///
237    /// # Note
238    ///
239    /// If the `native-backtrace` feature is not enabled, then this always returns [`None`].
240    #[inline]
241    #[must_use]
242    pub const fn as_location(self) -> Option<&'static std::panic::Location<'static>> {
243        #[cfg(feature = "native-backtrace")]
244        return Some(self.inner);
245
246        #[cfg(not(feature = "native-backtrace"))]
247        return None;
248    }
249}