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