1use super::{
2 CoverageItemKind, ExecutionAnchor, ExecutionAnchorKind, ItemAnchor, ItemAnchors, SourceLocation,
3};
4use crate::analysis::{EmptySpecialFunctionKind, SourceAnalysis};
5use alloy_primitives::map::rustc_hash::FxHashSet;
6use eyre::ensure;
7use foundry_compilers::artifacts::sourcemap::{SourceElement, SourceMap};
8use foundry_evm_core::{bytecode::InstIter, ic::IcPcMap};
9use revm::bytecode::opcode;
10
11pub fn find_anchors(
13 bytecode: &[u8],
14 source_map: &SourceMap,
15 ic_pc_map: &IcPcMap,
16 analysis: &SourceAnalysis,
17) -> ItemAnchors {
18 let mut anchors = ItemAnchors::default();
19 let select_branch = |(fallthrough, taken): (ItemAnchor, ItemAnchor), path_id| match path_id {
20 0 => (fallthrough, None),
21 1 => (taken, Some(fallthrough.instruction - 1)),
22 _ => panic!("too many path IDs for branch"),
23 };
24 let mut seen_sources = FxHashSet::default();
25 source_map
26 .iter()
27 .filter_map(|element| element.index())
28 .filter(|&source| seen_sources.insert(source))
29 .flat_map(|source| analysis.items_for_source_enumerated(source))
30 .filter_map(|(item_id, item)| {
31 if analysis.empty_special_function_kind(item_id).is_some() {
32 return None;
33 }
34 let anchor_loc = item.anchor_loc.as_ref().unwrap_or(&item.loc);
35 match item.kind {
36 CoverageItemKind::Branch { path_id: 1, is_first_opcode: true, .. }
37 if item.anchor_loc.is_some() =>
38 {
39 find_anchor_simple(source_map, ic_pc_map, item_id, anchor_loc)
40 .map(|anchor| (anchor, None))
41 .or_else(|_| {
42 find_anchor_branch(bytecode, source_map, item_id, &item.loc)
43 .map(|anchors| select_branch(anchors, 1))
44 })
45 }
46 CoverageItemKind::Branch { branch_id, path_id, is_first_opcode: false } => {
47 let exact = analysis.is_ternary_branch(item.loc.source_id as u32, branch_id);
48 find_anchor_branch_inner(bytecode, source_map, item_id, anchor_loc, exact)
49 .map(|anchors| select_branch(anchors, path_id))
50 }
51 _ => find_anchor_simple(source_map, ic_pc_map, item_id, anchor_loc)
52 .map(|anchor| (anchor, None)),
53 }
54 .inspect_err(|err| warn!(%item, %err, "could not find anchor"))
55 .ok()
56 })
57 .for_each(|(anchor, jump)| {
58 if let Some(jump) = jump {
59 anchors.jumps.insert(anchors.anchors.len(), jump);
60 }
61 anchors.anchors.push(anchor);
62 });
63 anchors
64}
65
66pub fn find_execution_anchors(
69 source_id: u32,
70 contract_name: &str,
71 analysis: &SourceAnalysis,
72) -> Vec<ExecutionAnchor> {
73 analysis
74 .empty_special_function_ids(source_id, contract_name)
75 .filter_map(|item_id| {
76 analysis.empty_special_function_kind(item_id).map(|kind| ExecutionAnchor {
77 item_id,
78 kind: match kind {
79 EmptySpecialFunctionKind::Constructor => ExecutionAnchorKind::Constructor,
80 EmptySpecialFunctionKind::Receive => ExecutionAnchorKind::Receive,
81 EmptySpecialFunctionKind::Fallback => ExecutionAnchorKind::Fallback,
82 },
83 })
84 })
85 .collect()
86}
87
88pub fn find_anchor_simple(
90 source_map: &SourceMap,
91 ic_pc_map: &IcPcMap,
92 item_id: u32,
93 loc: &SourceLocation,
94) -> eyre::Result<ItemAnchor> {
95 let instruction =
96 source_map.iter().position(|element| is_in_source_range(element, loc)).ok_or_else(
97 || eyre::eyre!("Could not find anchor: No matching instruction in range {loc}"),
98 )?;
99
100 Ok(ItemAnchor {
101 instruction: ic_pc_map.get(instruction as u32).ok_or_else(|| {
102 eyre::eyre!("We found an anchor, but we can't translate it to a program counter")
103 })?,
104 item_id,
105 })
106}
107
108pub fn find_anchor_branch(
137 bytecode: &[u8],
138 source_map: &SourceMap,
139 item_id: u32,
140 loc: &SourceLocation,
141) -> eyre::Result<(ItemAnchor, ItemAnchor)> {
142 find_anchor_branch_inner(bytecode, source_map, item_id, loc, false)
143}
144
145fn find_anchor_branch_inner(
147 bytecode: &[u8],
148 source_map: &SourceMap,
149 item_id: u32,
150 loc: &SourceLocation,
151 exact: bool,
152) -> eyre::Result<(ItemAnchor, ItemAnchor)> {
153 let mut anchors: Option<(ItemAnchor, ItemAnchor)> = None;
154 for (ic, (pc, inst)) in InstIter::new(bytecode).with_pc().enumerate() {
155 if (opcode::PUSH1..=opcode::PUSH32).contains(&inst.opcode.get()) {
160 let Some(element) = source_map.get(ic) else {
161 break;
164 };
165
166 let next_pc = pc + inst.immediate.len() + 1;
169 let push_size = inst.immediate.len();
170 if bytecode.get(next_pc).copied() == Some(opcode::JUMPI)
171 && if exact {
172 source_map.get(ic + 1).is_some_and(|jump| {
173 jump.index() == Some(loc.source_id as u32)
174 && jump.offset() == loc.bytes.start
175 && jump.length() == loc.len()
176 })
177 } else {
178 is_in_source_range(element, loc)
179 }
180 {
181 ensure!(push_size <= 4, "jump destination overflow");
183
184 let mut pc_bytes = [0u8; 4];
186 pc_bytes[4 - push_size..].copy_from_slice(inst.immediate);
187 let pc_jump = u32::from_be_bytes(pc_bytes);
188 let found = (
189 ItemAnchor {
190 item_id,
191 instruction: (next_pc + 1).try_into()?,
193 },
194 ItemAnchor { item_id, instruction: pc_jump },
195 );
196 if exact {
197 return Ok(found);
199 }
200 anchors = Some(found);
201 }
202 }
203 }
204
205 anchors.ok_or_else(|| eyre::eyre!("Could not detect branches in source: {}", loc))
206}
207
208fn is_in_source_range(element: &SourceElement, location: &SourceLocation) -> bool {
210 let source_ids_match = element.index_i32() == location.source_id as i32;
212 if !source_ids_match {
213 return false;
214 }
215
216 let is_within_start = element.offset() >= location.bytes.start;
218 if !is_within_start {
219 return false;
220 }
221
222 let start_of_ranges = location.bytes.start.max(element.offset());
223 let end_of_ranges =
224 (location.bytes.start + location.len()).min(element.offset() + element.length());
225 start_of_ranges <= end_of_ranges
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use foundry_compilers::artifacts::sourcemap;
232
233 #[test]
234 fn ternary_anchor_rejects_missing_node_mapping() {
235 let loc =
236 SourceLocation { source_id: 0, contract_name: "T".into(), bytes: 10..30, lines: 1..2 };
237 let bytecode = [opcode::PUSH1, 6, opcode::JUMPI, opcode::PUSH1, 7, opcode::JUMPI];
238 let inner_only = sourcemap::parse("15:5:0;;;").unwrap();
240 assert!(find_anchor_branch_inner(&bytecode, &inner_only, 0, &loc, true).is_err());
241 }
242}