foundry_evm_coverage/
anchors.rs1use super::{CoverageItemKind, ExecutionAnchor, ExecutionAnchorKind, ItemAnchor, SourceLocation};
2use crate::analysis::{EmptySpecialFunctionKind, SourceAnalysis};
3use alloy_primitives::map::rustc_hash::FxHashSet;
4use eyre::ensure;
5use foundry_compilers::artifacts::sourcemap::{SourceElement, SourceMap};
6use foundry_evm_core::{bytecode::InstIter, ic::IcPcMap};
7use revm::bytecode::opcode;
8
9pub fn find_anchors(
11 bytecode: &[u8],
12 source_map: &SourceMap,
13 ic_pc_map: &IcPcMap,
14 analysis: &SourceAnalysis,
15) -> Vec<ItemAnchor> {
16 let mut seen_sources = FxHashSet::default();
17 source_map
18 .iter()
19 .filter_map(|element| element.index())
20 .filter(|&source| seen_sources.insert(source))
21 .flat_map(|source| analysis.items_for_source_enumerated(source))
22 .filter_map(|(item_id, item)| {
23 if analysis.empty_special_function_kind(item_id).is_some() {
24 return None;
25 }
26 let anchor_loc = item.anchor_loc.as_ref().unwrap_or(&item.loc);
27 match item.kind {
28 CoverageItemKind::Branch { path_id, is_first_opcode: true, .. }
29 if item.anchor_loc.is_some() =>
30 {
31 find_anchor_simple(source_map, ic_pc_map, item_id, anchor_loc).or_else(|_| {
32 find_anchor_branch(bytecode, source_map, item_id, &item.loc).map(
33 |anchors| match path_id {
34 0 => anchors.0,
35 1 => anchors.1,
36 _ => panic!("too many path IDs for branch"),
37 },
38 )
39 })
40 }
41 CoverageItemKind::Branch { path_id, is_first_opcode: false, .. } => {
42 find_anchor_branch(bytecode, source_map, item_id, anchor_loc).map(|anchors| {
43 match path_id {
44 0 => anchors.0,
45 1 => anchors.1,
46 _ => panic!("too many path IDs for branch"),
47 }
48 })
49 }
50 _ => find_anchor_simple(source_map, ic_pc_map, item_id, anchor_loc),
51 }
52 .inspect_err(|err| warn!(%item, %err, "could not find anchor"))
53 .ok()
54 })
55 .collect()
56}
57
58pub fn find_execution_anchors(
61 source_id: u32,
62 contract_name: &str,
63 analysis: &SourceAnalysis,
64) -> Vec<ExecutionAnchor> {
65 analysis
66 .empty_special_function_ids(source_id, contract_name)
67 .filter_map(|item_id| {
68 analysis.empty_special_function_kind(item_id).map(|kind| ExecutionAnchor {
69 item_id,
70 kind: match kind {
71 EmptySpecialFunctionKind::Constructor => ExecutionAnchorKind::Constructor,
72 EmptySpecialFunctionKind::Receive => ExecutionAnchorKind::Receive,
73 EmptySpecialFunctionKind::Fallback => ExecutionAnchorKind::Fallback,
74 },
75 })
76 })
77 .collect()
78}
79
80pub fn find_anchor_simple(
82 source_map: &SourceMap,
83 ic_pc_map: &IcPcMap,
84 item_id: u32,
85 loc: &SourceLocation,
86) -> eyre::Result<ItemAnchor> {
87 let instruction =
88 source_map.iter().position(|element| is_in_source_range(element, loc)).ok_or_else(
89 || eyre::eyre!("Could not find anchor: No matching instruction in range {loc}"),
90 )?;
91
92 Ok(ItemAnchor {
93 instruction: ic_pc_map.get(instruction as u32).ok_or_else(|| {
94 eyre::eyre!("We found an anchor, but we can't translate it to a program counter")
95 })?,
96 item_id,
97 })
98}
99
100pub fn find_anchor_branch(
129 bytecode: &[u8],
130 source_map: &SourceMap,
131 item_id: u32,
132 loc: &SourceLocation,
133) -> eyre::Result<(ItemAnchor, ItemAnchor)> {
134 let mut anchors: Option<(ItemAnchor, ItemAnchor)> = None;
135 for (ic, (pc, inst)) in InstIter::new(bytecode).with_pc().enumerate() {
136 if (opcode::PUSH1..=opcode::PUSH32).contains(&inst.opcode.get()) {
141 let Some(element) = source_map.get(ic) else {
142 break;
145 };
146
147 let next_pc = pc + inst.immediate.len() + 1;
150 let push_size = inst.immediate.len();
151 if bytecode.get(next_pc).copied() == Some(opcode::JUMPI)
152 && is_in_source_range(element, loc)
153 {
154 ensure!(push_size <= 4, "jump destination overflow");
156
157 let mut pc_bytes = [0u8; 4];
159 pc_bytes[4 - push_size..].copy_from_slice(inst.immediate);
160 let pc_jump = u32::from_be_bytes(pc_bytes);
161 anchors = Some((
162 ItemAnchor {
163 item_id,
164 instruction: (next_pc + 1) as u32,
166 },
167 ItemAnchor { item_id, instruction: pc_jump },
168 ));
169 }
170 }
171 }
172
173 anchors.ok_or_else(|| eyre::eyre!("Could not detect branches in source: {}", loc))
174}
175
176fn is_in_source_range(element: &SourceElement, location: &SourceLocation) -> bool {
178 let source_ids_match = element.index_i32() == location.source_id as i32;
180 if !source_ids_match {
181 return false;
182 }
183
184 let is_within_start = element.offset() >= location.bytes.start;
186 if !is_within_start {
187 return false;
188 }
189
190 let start_of_ranges = location.bytes.start.max(element.offset());
191 let end_of_ranges =
192 (location.bytes.start + location.len()).min(element.offset() + element.length());
193 start_of_ranges <= end_of_ranges
194}