1use crate::insn_locator::InsnLocator;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum RefKind {
8 String,
9 Type,
10 Method,
11 Field,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub enum FieldAccess {
17 Read,
18 Write,
19}
20
21impl FieldAccess {
22 pub const fn from_opcode(opcode: u8) -> Option<Self> {
25 match opcode {
26 0x52..=0x58 | 0x60..=0x66 => Some(FieldAccess::Read),
27 0x59..=0x5f | 0x67..=0x6d => Some(FieldAccess::Write),
28 _ => None,
29 }
30 }
31
32 pub const fn as_str(self) -> &'static str {
33 match self {
34 FieldAccess::Read => "read",
35 FieldAccess::Write => "write",
36 }
37 }
38}
39
40impl RefKind {
41 pub fn as_str(self) -> &'static str {
42 match self {
43 RefKind::String => "string",
44 RefKind::Type => "type",
45 RefKind::Method => "method",
46 RefKind::Field => "field",
47 }
48 }
49}
50
51#[derive(Debug, Clone, Default)]
53pub struct IdxSet {
54 bits: Vec<u64>,
55}
56
57impl IdxSet {
58 pub fn insert(&mut self, idx: u32) {
59 let word = (idx >> 6) as usize;
60 if word >= self.bits.len() {
61 self.bits.resize(word + 1, 0);
62 }
63 self.bits[word] |= 1u64 << (idx & 63);
64 }
65
66 #[inline]
67 pub fn contains(&self, idx: u32) -> bool {
68 match self.bits.get((idx >> 6) as usize) {
69 Some(w) => w & (1u64 << (idx & 63)) != 0,
70 None => false,
71 }
72 }
73
74 pub fn is_empty(&self) -> bool {
75 self.bits.iter().all(|&w| w == 0)
76 }
77
78 pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
80 self.bits.iter().enumerate().flat_map(|(w, &bits)| {
81 let mut bits = bits;
82 std::iter::from_fn(move || {
83 if bits == 0 {
84 return None;
85 }
86 let t = bits.trailing_zeros();
87 bits &= bits - 1;
88 Some((w as u32) * 64 + t)
89 })
90 })
91 }
92}
93
94impl FromIterator<u32> for IdxSet {
95 fn from_iter<I: IntoIterator<Item = u32>>(iter: I) -> Self {
96 let mut s = IdxSet::default();
97 for i in iter {
98 s.insert(i);
99 }
100 s
101 }
102}
103
104#[inline]
106fn opcode_width(kind: RefKind, b: u8) -> u8 {
107 match kind {
108 RefKind::String => match b {
109 0x1a => 2,
110 0x1b => 4,
111 _ => 0,
112 },
113 RefKind::Type => matches!(b, 0x1c | 0x1f | 0x20 | 0x22 | 0x23 | 0x24 | 0x25) as u8 * 2,
114 RefKind::Field => (b.wrapping_sub(0x52) <= 0x6d - 0x52) as u8 * 2,
115 RefKind::Method => (b.wrapping_sub(0x6e) <= 4 || b.wrapping_sub(0x74) <= 4 || b == 0xfa || b == 0xfb) as u8 * 2,
116 }
117}
118
119const MEMMEM_MAX_TARGETS: usize = 8;
122
123pub struct CodeScanner<'l> {
125 insn: &'l InsnLocator,
126}
127
128impl<'l> CodeScanner<'l> {
129 pub fn new(insn: &'l InsnLocator) -> Self {
130 CodeScanner { insn }
131 }
132
133 pub fn scan(&self, buf: &[u8], kind: RefKind, idxs: &IdxSet) -> Vec<(u32, u32)> {
140 self.scan_inner(buf, kind, idxs, false)
141 }
142
143 pub(crate) fn scan_instruction_candidates(&self, buf: &[u8], kind: RefKind, idxs: &IdxSet) -> Vec<(u32, u32)> {
148 self.scan_inner(buf, kind, idxs, true)
149 }
150
151 fn scan_inner(&self, buf: &[u8], kind: RefKind, idxs: &IdxSet, aligned: bool) -> Vec<(u32, u32)> {
152 let end = self.insn.code_item_end().min(buf.len());
153 let start = self.insn.code_item_start();
154 if start >= end || idxs.is_empty() {
155 return Vec::new();
156 }
157 let region = &buf[start..end];
158 let targets = idxs.iter().collect::<Vec<u32>>();
159 if targets.len() <= MEMMEM_MAX_TARGETS {
160 return scan_by_operand(region, start, kind, &targets, aligned);
161 }
162 scan_by_opcode(region, start, kind, idxs, aligned)
163 }
164}
165
166fn scan_by_operand(region: &[u8], base: usize, kind: RefKind, targets: &[u32], aligned: bool) -> Vec<(u32, u32)> {
170 let mut out = Vec::new();
171 for &idx in targets {
172 let narrow = (idx as u16).to_le_bytes();
173 let wide = idx.to_le_bytes();
174 let needles: [(&[u8], u8); 2] = [(&narrow, 2), (&wide, 4)];
175 for (needle, width) in needles {
176 if width == 2 && idx > u16::MAX as u32 {
177 continue;
178 }
179 if width == 4 && kind != RefKind::String {
180 continue; }
182 let finder = memchr::memmem::Finder::new(needle);
183 let mut from = 0;
188 while let Some(rel) = finder.find(®ion[from..]) {
189 let pos = from + rel;
190 from = pos + 1;
191 if pos >= 2 && opcode_width(kind, region[pos - 2]) == width && (!aligned || (base + pos - 2) & 1 == 0) {
192 out.push(((base + pos - 2) as u32, idx));
193 }
194 }
195 }
196 }
197 out.sort_unstable();
198 out.dedup();
199 out
200}
201
202#[inline]
205fn check_candidate(region: &[u8], base: usize, kind: RefKind, idxs: &IdxSet, i: usize, out: &mut Vec<(u32, u32)>) {
206 let width = opcode_width(kind, region[i]);
207 if width == 0 {
208 return;
209 }
210 let idx = if width == 2 {
211 let Some(b) = region.get(i + 2..i + 4) else { return };
212 u16::from_le_bytes([b[0], b[1]]) as u32
213 } else {
214 let Some(b) = region.get(i + 2..i + 6) else { return };
215 u32::from_le_bytes([b[0], b[1], b[2], b[3]])
216 };
217 if idxs.contains(idx) {
218 out.push(((base + i) as u32, idx));
219 }
220}
221
222fn linear_sweep(region: &[u8], base: usize, kind: RefKind, idxs: &IdxSet, out: &mut Vec<(u32, u32)>) {
224 let mut table = [0u8; 256];
225 for (b, w) in table.iter_mut().enumerate() {
226 *w = opcode_width(kind, b as u8);
227 }
228 let len = region.len();
229 for (i, &b) in region.iter().enumerate() {
230 let width = table[b as usize];
231 if width == 0 {
232 continue;
233 }
234 let idx = if width == 2 {
235 if i + 4 > len {
236 continue;
237 }
238 u16::from_le_bytes([region[i + 2], region[i + 3]]) as u32
239 } else {
240 if i + 6 > len {
241 continue;
242 }
243 u32::from_le_bytes([region[i + 2], region[i + 3], region[i + 4], region[i + 5]])
244 };
245 if idxs.contains(idx) {
246 out.push(((base + i) as u32, idx));
247 }
248 }
249}
250
251fn prefilter_is_selective(highs: &[u8]) -> bool {
256 highs.len() <= 3 && !highs.iter().any(|&b| b == 0x00 || b == 0xFF)
257}
258
259fn scan_by_opcode(region: &[u8], base: usize, kind: RefKind, idxs: &IdxSet, aligned: bool) -> Vec<(u32, u32)> {
265 let mut out = Vec::new();
266 if kind == RefKind::String {
267 for i in memchr::memchr2_iter(0x1a, 0x1b, region) {
268 if !aligned || (base + i) & 1 == 0 {
269 check_candidate(region, base, kind, idxs, i, &mut out);
270 }
271 }
272 return out;
273 }
274 let mut highs: Vec<u8> = idxs.iter().map(|i| (i >> 8) as u8).collect();
275 highs.sort_unstable();
276 highs.dedup();
277 if !prefilter_is_selective(&highs) {
278 linear_sweep(region, base, kind, idxs, &mut out);
279 return out;
280 }
281 let positions: Box<dyn Iterator<Item = usize>> = match highs.as_slice() {
282 [a] => Box::new(memchr::memchr_iter(*a, region)),
283 [a, b] => Box::new(memchr::memchr2_iter(*a, *b, region)),
284 [a, b, c] => Box::new(memchr::memchr3_iter(*a, *b, *c, region)),
285 _ => unreachable!("prefilter_is_selective bounds the byte set"),
286 };
287 for p in positions {
289 if p >= 3 && (!aligned || (base + p - 3) & 1 == 0) {
290 check_candidate(region, base, kind, idxs, p - 3, &mut out);
291 }
292 }
293 out
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn prefilter_falls_back_on_common_bytes() {
302 assert!(prefilter_is_selective(&[0x12]));
303 assert!(prefilter_is_selective(&[0x12, 0x13, 0x14]));
304 assert!(!prefilter_is_selective(&[0x00]));
305 assert!(!prefilter_is_selective(&[0x12, 0xff]));
306 assert!(!prefilter_is_selective(&[1, 2, 3, 4]));
307 }
308
309 #[test]
310 fn every_field_opcode_has_the_right_access() {
311 for opcode in 0u8..=u8::MAX {
312 let expected = match opcode {
313 0x52..=0x58 | 0x60..=0x66 => Some(FieldAccess::Read),
314 0x59..=0x5f | 0x67..=0x6d => Some(FieldAccess::Write),
315 _ => None,
316 };
317 assert_eq!(FieldAccess::from_opcode(opcode), expected, "opcode 0x{opcode:02x}");
318 }
319 }
320
321 fn all_strategies(region: &[u8], kind: RefKind, targets: &[u32]) -> Vec<(&'static str, Vec<(u32, u32)>)> {
323 let idxs: IdxSet = targets.iter().copied().collect();
324 let mut linear = Vec::new();
325 linear_sweep(region, 0, kind, &idxs, &mut linear);
326 vec![
327 ("operand", scan_by_operand(region, 0, kind, targets, false)),
328 ("opcode", scan_by_opcode(region, 0, kind, &idxs, false)),
329 ("linear", linear),
330 ]
331 }
332
333 fn assert_all_agree(region: &[u8], kind: RefKind, targets: &[u32], expected: &[(u32, u32)]) {
334 for (label, got) in all_strategies(region, kind, targets) {
335 assert_eq!(got, expected, "{label} strategy for {kind:?} {targets:x?}");
336 }
337 }
338
339 #[test]
340 fn self_overlapping_operand_is_not_skipped() {
341 let region = [0x1a, 0x05, 0x05, 0x05, 0x0e, 0x00];
344 assert_all_agree(®ion, RefKind::String, &[0x0505], &[(0, 0x0505)]);
345 let region = [0x52, 0x05, 0x05, 0x05, 0x0e, 0x00];
347 assert_all_agree(®ion, RefKind::Field, &[0x0505], &[(0, 0x0505)]);
348 let region = [0x00, 0x05, 0x05, 0x1b, 0x05, 0x05, 0x05, 0x05, 0x05, 0x0e, 0x00];
350 assert_all_agree(®ion, RefKind::String, &[0x0505_0505], &[(3, 0x0505_0505)]);
351 let region = [0x1a, 0x05, 0x05, 0x05, 0x1a, 0x05, 0x05, 0x05];
353 assert_all_agree(®ion, RefKind::String, &[0x0505], &[(0, 0x0505), (4, 0x0505)]);
354 }
355
356 #[test]
357 fn jumbo_string_index_uses_wide_operand() {
358 let mut region = vec![0x1b, 0x01];
361 region.extend_from_slice(&0x0001_2345u32.to_le_bytes());
362 region.extend_from_slice(&[0x1a, 0x02, 0x45, 0x23, 0x0e, 0x00]);
363 assert_all_agree(®ion, RefKind::String, &[0x0001_2345], &[(0, 0x0001_2345)]);
365 assert_all_agree(®ion, RefKind::String, &[0x2345], &[(6, 0x2345)]);
367 assert_all_agree(®ion, RefKind::Field, &[0x0001_2345], &[]);
369 }
370
371 #[test]
372 fn strategies_agree() {
373 let mut region = Vec::new();
375 for i in 0u32..600 {
376 let idx = (i * 7) % 700;
377 region.extend_from_slice(&[0x52 + (i % 4) as u8, 0x10]);
378 region.extend_from_slice(&(idx as u16).to_le_bytes());
379 region.extend_from_slice(&[0x0e, 0x00, 0x00, 0x00]);
380 }
381 let targets: Vec<u32> = (0..20).map(|i| i * 7).collect();
384 let idxs: IdxSet = targets.iter().copied().collect();
385 let a = scan_by_operand(®ion, 0, RefKind::Field, &targets, false);
386 let b = scan_by_opcode(®ion, 0, RefKind::Field, &idxs, false);
387 assert!(!a.is_empty());
388 assert_eq!(a, b);
389 let targets: Vec<u32> = (74..94).map(|k| k * 7).collect();
391 assert!(targets.iter().all(|&t| t >> 8 == 2));
392 let idxs: IdxSet = targets.iter().copied().collect();
393 let a = scan_by_operand(®ion, 0, RefKind::Field, &targets, false);
394 let b = scan_by_opcode(®ion, 0, RefKind::Field, &idxs, false);
395 assert!(!a.is_empty());
396 assert_eq!(a, b);
397 }
398}