foundry_evm_traces/backtrace/
source_map.rs1use alloy_primitives::Bytes;
4use foundry_compilers::{ProjectCompileOutput, artifacts::sourcemap::SourceMap};
5use foundry_evm_core::ic::IcPcMap;
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Clone)]
10pub struct SourceData {
11 pub source_map: SourceMap,
13 pub bytecode: Bytes,
15}
16
17pub struct PcSourceMapper<'a> {
19 ic_pc_map: IcPcMap,
21 source_data: SourceData,
23 sources: &'a [(PathBuf, String)],
25 line_offsets: Vec<Vec<usize>>,
27}
28
29impl<'a> PcSourceMapper<'a> {
30 pub fn new(source_data: SourceData, sources: &'a [(PathBuf, String)]) -> Self {
32 let ic_pc_map = IcPcMap::new(source_data.bytecode.as_ref());
34
35 let line_offsets =
37 sources.iter().map(|(_, content)| compute_line_offsets(content)).collect();
38
39 Self { ic_pc_map, source_data, sources, line_offsets }
40 }
41
42 pub fn map_pc(&self, pc: usize) -> Option<SourceLocation> {
44 let ic = self.find_instruction_counter(pc)?;
46
47 let element = self.source_data.source_map.get(ic)?;
49
50 let source_idx_opt = element.index();
52
53 let source_idx = source_idx_opt? as usize;
54 if source_idx >= self.sources.len() {
55 return None;
56 }
57
58 let (file_path, content) = &self.sources[source_idx];
60
61 let offset = element.offset() as usize;
63
64 if offset >= content.len() {
66 return None;
67 }
68
69 let line_offsets = self.line_offsets.get(source_idx)?;
70 let (line, column) = offset_to_line_column(line_offsets, offset);
71
72 trace!(
73 file = ?file_path,
74 line = line,
75 column = column,
76 offset = offset,
77 "Mapped PC to source location"
78 );
79
80 Some(SourceLocation {
81 file: file_path.clone(),
82 line,
83 column,
84 length: element.length() as usize,
85 offset,
86 })
87 }
88
89 fn find_instruction_counter(&self, pc: usize) -> Option<usize> {
91 let mut best_ic = None;
94 let mut best_pc = 0;
95
96 for (ic, mapped_pc) in self.ic_pc_map.iter() {
97 let mapped_pc = *mapped_pc as usize;
98 if mapped_pc <= pc && mapped_pc >= best_pc {
99 best_pc = mapped_pc;
100 best_ic = Some(*ic as usize);
101 }
102 }
103
104 best_ic
105 }
106}
107#[derive(Debug, Clone)]
109pub struct SourceLocation {
110 pub file: PathBuf,
111 pub line: usize,
112 pub column: usize,
113 pub length: usize,
114 pub offset: usize,
117}
118
119fn compute_line_offsets(content: &str) -> Vec<usize> {
121 let mut offsets = vec![0];
122 offsets.extend(memchr::memchr_iter(b'\n', content.as_bytes()).map(|offset| offset + 1));
123 offsets
124}
125
126fn offset_to_line_column(line_offsets: &[usize], offset: usize) -> (usize, usize) {
128 let line = line_offsets.partition_point(|&line_start| line_start <= offset) - 1;
129 let column = offset - line_offsets[line];
130 (line + 1, column + 1)
131}
132
133pub fn load_build_sources(
135 build_id: &str,
136 output: &ProjectCompileOutput,
137 root: &Path,
138) -> Option<Vec<(PathBuf, String)>> {
139 let build_ctx = output.builds().find(|(bid, _)| *bid == build_id).map(|(_, ctx)| ctx)?;
140
141 let max_source_id = build_ctx.source_id_to_path.keys().max().map_or(0, |id| *id) as usize;
144
145 let mut sources = vec![(PathBuf::new(), String::new()); max_source_id + 1];
147
148 for (source_id, source_path) in &build_ctx.source_id_to_path {
150 let idx = *source_id as usize;
151
152 let full_path =
153 if source_path.is_absolute() { source_path.clone() } else { root.join(source_path) };
154 let mut source_content = foundry_common::fs::read_to_string(&full_path).unwrap_or_default();
155
156 if source_content.contains('\r') {
158 source_content = source_content.replace("\r\n", "\n");
159 }
160
161 let path_buf = source_path.strip_prefix(root).unwrap_or(source_path).to_path_buf();
163
164 sources[idx] = (path_buf, source_content);
165 }
166
167 Some(sources)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::{compute_line_offsets, offset_to_line_column};
173
174 fn line_column(content: &str, offset: usize) -> (usize, usize) {
175 offset_to_line_column(&compute_line_offsets(content), offset)
176 }
177
178 #[test]
179 fn maps_byte_offsets_to_line_and_column() {
180 assert_eq!(line_column("abc", 0), (1, 1));
181 assert_eq!(line_column("abc", 2), (1, 3));
182
183 assert_eq!(line_column("abc\ndef", 3), (1, 4));
184 assert_eq!(line_column("abc\ndef", 4), (2, 1));
185 assert_eq!(line_column("abc\ndef", 6), (2, 3));
186
187 assert_eq!(line_column("a\nbc\ndef", 0), (1, 1));
188 assert_eq!(line_column("a\nbc\ndef", 1), (1, 2));
189 assert_eq!(line_column("a\nbc\ndef", 2), (2, 1));
190 assert_eq!(line_column("a\nbc\ndef", 4), (2, 3));
191 assert_eq!(line_column("a\nbc\ndef", 5), (3, 1));
192 assert_eq!(line_column("a\nbc\ndef", 7), (3, 3));
193 }
194}