use crate::insn_locator::InsnLocator;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RefKind {
String,
Type,
Method,
Field,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FieldAccess {
Read,
Write,
}
impl FieldAccess {
pub const fn from_opcode(opcode: u8) -> Option<Self> {
match opcode {
0x52..=0x58 | 0x60..=0x66 => Some(FieldAccess::Read),
0x59..=0x5f | 0x67..=0x6d => Some(FieldAccess::Write),
_ => None,
}
}
pub const fn as_str(self) -> &'static str {
match self {
FieldAccess::Read => "read",
FieldAccess::Write => "write",
}
}
}
impl RefKind {
pub fn as_str(self) -> &'static str {
match self {
RefKind::String => "string",
RefKind::Type => "type",
RefKind::Method => "method",
RefKind::Field => "field",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct IdxSet {
bits: Vec<u64>,
}
impl IdxSet {
pub fn insert(&mut self, idx: u32) {
let word = (idx >> 6) as usize;
if word >= self.bits.len() {
self.bits.resize(word + 1, 0);
}
self.bits[word] |= 1u64 << (idx & 63);
}
#[inline]
pub fn contains(&self, idx: u32) -> bool {
match self.bits.get((idx >> 6) as usize) {
Some(w) => w & (1u64 << (idx & 63)) != 0,
None => false,
}
}
pub fn is_empty(&self) -> bool {
self.bits.iter().all(|&w| w == 0)
}
pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
self.bits.iter().enumerate().flat_map(|(w, &bits)| {
let mut bits = bits;
std::iter::from_fn(move || {
if bits == 0 {
return None;
}
let t = bits.trailing_zeros();
bits &= bits - 1;
Some((w as u32) * 64 + t)
})
})
}
}
impl FromIterator<u32> for IdxSet {
fn from_iter<I: IntoIterator<Item = u32>>(iter: I) -> Self {
let mut s = IdxSet::default();
for i in iter {
s.insert(i);
}
s
}
}
#[inline]
fn opcode_width(kind: RefKind, b: u8) -> u8 {
match kind {
RefKind::String => match b {
0x1a => 2,
0x1b => 4,
_ => 0,
},
RefKind::Type => matches!(b, 0x1c | 0x1f | 0x20 | 0x22 | 0x23 | 0x24 | 0x25) as u8 * 2,
RefKind::Field => (b.wrapping_sub(0x52) <= 0x6d - 0x52) as u8 * 2,
RefKind::Method => (b.wrapping_sub(0x6e) <= 4 || b.wrapping_sub(0x74) <= 4 || b == 0xfa || b == 0xfb) as u8 * 2,
}
}
const MEMMEM_MAX_TARGETS: usize = 8;
pub struct CodeScanner<'l> {
insn: &'l InsnLocator,
}
impl<'l> CodeScanner<'l> {
pub fn new(insn: &'l InsnLocator) -> Self {
CodeScanner { insn }
}
pub fn scan(&self, buf: &[u8], kind: RefKind, idxs: &IdxSet) -> Vec<(u32, u32)> {
self.scan_inner(buf, kind, idxs, false)
}
pub(crate) fn scan_instruction_candidates(&self, buf: &[u8], kind: RefKind, idxs: &IdxSet) -> Vec<(u32, u32)> {
self.scan_inner(buf, kind, idxs, true)
}
fn scan_inner(&self, buf: &[u8], kind: RefKind, idxs: &IdxSet, aligned: bool) -> Vec<(u32, u32)> {
let end = self.insn.code_item_end().min(buf.len());
let start = self.insn.code_item_start();
if start >= end || idxs.is_empty() {
return Vec::new();
}
let region = &buf[start..end];
let targets = idxs.iter().collect::<Vec<u32>>();
if targets.len() <= MEMMEM_MAX_TARGETS {
return scan_by_operand(region, start, kind, &targets, aligned);
}
scan_by_opcode(region, start, kind, idxs, aligned)
}
}
fn scan_by_operand(region: &[u8], base: usize, kind: RefKind, targets: &[u32], aligned: bool) -> Vec<(u32, u32)> {
let mut out = Vec::new();
for &idx in targets {
let narrow = (idx as u16).to_le_bytes();
let wide = idx.to_le_bytes();
let needles: [(&[u8], u8); 2] = [(&narrow, 2), (&wide, 4)];
for (needle, width) in needles {
if width == 2 && idx > u16::MAX as u32 {
continue;
}
if width == 4 && kind != RefKind::String {
continue; }
let finder = memchr::memmem::Finder::new(needle);
let mut from = 0;
while let Some(rel) = finder.find(®ion[from..]) {
let pos = from + rel;
from = pos + 1;
if pos >= 2 && opcode_width(kind, region[pos - 2]) == width && (!aligned || (base + pos - 2) & 1 == 0) {
out.push(((base + pos - 2) as u32, idx));
}
}
}
}
out.sort_unstable();
out.dedup();
out
}
#[inline]
fn check_candidate(region: &[u8], base: usize, kind: RefKind, idxs: &IdxSet, i: usize, out: &mut Vec<(u32, u32)>) {
let width = opcode_width(kind, region[i]);
if width == 0 {
return;
}
let idx = if width == 2 {
let Some(b) = region.get(i + 2..i + 4) else { return };
u16::from_le_bytes([b[0], b[1]]) as u32
} else {
let Some(b) = region.get(i + 2..i + 6) else { return };
u32::from_le_bytes([b[0], b[1], b[2], b[3]])
};
if idxs.contains(idx) {
out.push(((base + i) as u32, idx));
}
}
fn linear_sweep(region: &[u8], base: usize, kind: RefKind, idxs: &IdxSet, out: &mut Vec<(u32, u32)>) {
let mut table = [0u8; 256];
for (b, w) in table.iter_mut().enumerate() {
*w = opcode_width(kind, b as u8);
}
let len = region.len();
for (i, &b) in region.iter().enumerate() {
let width = table[b as usize];
if width == 0 {
continue;
}
let idx = if width == 2 {
if i + 4 > len {
continue;
}
u16::from_le_bytes([region[i + 2], region[i + 3]]) as u32
} else {
if i + 6 > len {
continue;
}
u32::from_le_bytes([region[i + 2], region[i + 3], region[i + 4], region[i + 5]])
};
if idxs.contains(idx) {
out.push(((base + i) as u32, idx));
}
}
}
fn prefilter_is_selective(highs: &[u8]) -> bool {
highs.len() <= 3 && !highs.iter().any(|&b| b == 0x00 || b == 0xFF)
}
fn scan_by_opcode(region: &[u8], base: usize, kind: RefKind, idxs: &IdxSet, aligned: bool) -> Vec<(u32, u32)> {
let mut out = Vec::new();
if kind == RefKind::String {
for i in memchr::memchr2_iter(0x1a, 0x1b, region) {
if !aligned || (base + i) & 1 == 0 {
check_candidate(region, base, kind, idxs, i, &mut out);
}
}
return out;
}
let mut highs: Vec<u8> = idxs.iter().map(|i| (i >> 8) as u8).collect();
highs.sort_unstable();
highs.dedup();
if !prefilter_is_selective(&highs) {
linear_sweep(region, base, kind, idxs, &mut out);
return out;
}
let positions: Box<dyn Iterator<Item = usize>> = match highs.as_slice() {
[a] => Box::new(memchr::memchr_iter(*a, region)),
[a, b] => Box::new(memchr::memchr2_iter(*a, *b, region)),
[a, b, c] => Box::new(memchr::memchr3_iter(*a, *b, *c, region)),
_ => unreachable!("prefilter_is_selective bounds the byte set"),
};
for p in positions {
if p >= 3 && (!aligned || (base + p - 3) & 1 == 0) {
check_candidate(region, base, kind, idxs, p - 3, &mut out);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefilter_falls_back_on_common_bytes() {
assert!(prefilter_is_selective(&[0x12]));
assert!(prefilter_is_selective(&[0x12, 0x13, 0x14]));
assert!(!prefilter_is_selective(&[0x00]));
assert!(!prefilter_is_selective(&[0x12, 0xff]));
assert!(!prefilter_is_selective(&[1, 2, 3, 4]));
}
#[test]
fn every_field_opcode_has_the_right_access() {
for opcode in 0u8..=u8::MAX {
let expected = match opcode {
0x52..=0x58 | 0x60..=0x66 => Some(FieldAccess::Read),
0x59..=0x5f | 0x67..=0x6d => Some(FieldAccess::Write),
_ => None,
};
assert_eq!(FieldAccess::from_opcode(opcode), expected, "opcode 0x{opcode:02x}");
}
}
fn all_strategies(region: &[u8], kind: RefKind, targets: &[u32]) -> Vec<(&'static str, Vec<(u32, u32)>)> {
let idxs: IdxSet = targets.iter().copied().collect();
let mut linear = Vec::new();
linear_sweep(region, 0, kind, &idxs, &mut linear);
vec![
("operand", scan_by_operand(region, 0, kind, targets, false)),
("opcode", scan_by_opcode(region, 0, kind, &idxs, false)),
("linear", linear),
]
}
fn assert_all_agree(region: &[u8], kind: RefKind, targets: &[u32], expected: &[(u32, u32)]) {
for (label, got) in all_strategies(region, kind, targets) {
assert_eq!(got, expected, "{label} strategy for {kind:?} {targets:x?}");
}
}
#[test]
fn self_overlapping_operand_is_not_skipped() {
let region = [0x1a, 0x05, 0x05, 0x05, 0x0e, 0x00];
assert_all_agree(®ion, RefKind::String, &[0x0505], &[(0, 0x0505)]);
let region = [0x52, 0x05, 0x05, 0x05, 0x0e, 0x00];
assert_all_agree(®ion, RefKind::Field, &[0x0505], &[(0, 0x0505)]);
let region = [0x00, 0x05, 0x05, 0x1b, 0x05, 0x05, 0x05, 0x05, 0x05, 0x0e, 0x00];
assert_all_agree(®ion, RefKind::String, &[0x0505_0505], &[(3, 0x0505_0505)]);
let region = [0x1a, 0x05, 0x05, 0x05, 0x1a, 0x05, 0x05, 0x05];
assert_all_agree(®ion, RefKind::String, &[0x0505], &[(0, 0x0505), (4, 0x0505)]);
}
#[test]
fn jumbo_string_index_uses_wide_operand() {
let mut region = vec![0x1b, 0x01];
region.extend_from_slice(&0x0001_2345u32.to_le_bytes());
region.extend_from_slice(&[0x1a, 0x02, 0x45, 0x23, 0x0e, 0x00]);
assert_all_agree(®ion, RefKind::String, &[0x0001_2345], &[(0, 0x0001_2345)]);
assert_all_agree(®ion, RefKind::String, &[0x2345], &[(6, 0x2345)]);
assert_all_agree(®ion, RefKind::Field, &[0x0001_2345], &[]);
}
#[test]
fn strategies_agree() {
let mut region = Vec::new();
for i in 0u32..600 {
let idx = (i * 7) % 700;
region.extend_from_slice(&[0x52 + (i % 4) as u8, 0x10]);
region.extend_from_slice(&(idx as u16).to_le_bytes());
region.extend_from_slice(&[0x0e, 0x00, 0x00, 0x00]);
}
let targets: Vec<u32> = (0..20).map(|i| i * 7).collect();
let idxs: IdxSet = targets.iter().copied().collect();
let a = scan_by_operand(®ion, 0, RefKind::Field, &targets, false);
let b = scan_by_opcode(®ion, 0, RefKind::Field, &idxs, false);
assert!(!a.is_empty());
assert_eq!(a, b);
let targets: Vec<u32> = (74..94).map(|k| k * 7).collect();
assert!(targets.iter().all(|&t| t >> 8 == 2));
let idxs: IdxSet = targets.iter().copied().collect();
let a = scan_by_operand(®ion, 0, RefKind::Field, &targets, false);
let b = scan_by_opcode(®ion, 0, RefKind::Field, &idxs, false);
assert!(!a.is_empty());
assert_eq!(a, b);
}
}