#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Style {
#[default]
Lld,
Gnu,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DeadTarget {
Discarded,
Folded,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct SectionTombstone {
pub discarded: Option<u64>,
pub folded: Option<u64>,
}
impl SectionTombstone {
#[must_use]
pub fn get(&self, dead: DeadTarget) -> Option<u64> {
match dead {
DeadTarget::Discarded => self.discarded,
DeadTarget::Folded => self.folded,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RuleError(pub String);
impl std::fmt::Display for RuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for RuleError {}
#[derive(Clone, Debug, Default)]
pub struct Tombstones {
style: Style,
rules: Vec<(Glob, u64)>,
}
impl Tombstones {
#[must_use]
pub fn new(style: Style) -> Self {
Self {
style,
rules: Vec::new(),
}
}
pub fn with_rules<'p>(
mut self,
rules: impl IntoIterator<Item = (&'p [u8], u64)>,
) -> Result<Self, RuleError> {
for (pattern, value) in rules {
let glob = Glob::new(pattern).ok_or_else(|| {
RuleError(format!(
"-z dead-reloc-in-nonalloc=: invalid glob pattern: {}",
String::from_utf8_lossy(pattern)
))
})?;
self.rules.push((glob, value));
}
Ok(self)
}
#[must_use]
pub fn for_section(&self, name: &[u8]) -> SectionTombstone {
let user = self
.rules
.iter()
.rev()
.find(|(glob, _)| glob.matches(name))
.map(|&(_, value)| value);
let is_debug = name.starts_with(b".debug");
match self.style {
Style::Lld => {
let builtin = is_debug.then_some(match name {
b".debug_loc" | b".debug_ranges" => 1,
b".debug_names" => u64::MAX,
_ => 0,
});
let discarded = user.or(builtin);
let folded = if name == b".debug_line" {
None
} else {
discarded
};
SectionTombstone { discarded, folded }
}
Style::Gnu => {
let builtin = if name == b".debug_ranges" { 1 } else { 0 };
SectionTombstone {
discarded: Some(user.unwrap_or(builtin)),
folded: None,
}
}
}
}
#[must_use]
pub fn value(&self, name: &[u8], dead: DeadTarget) -> Option<u64> {
self.for_section(name).get(dead)
}
}
#[must_use]
pub fn truncate(value: u64, width: usize) -> u64 {
match width {
0 => 0,
1..=7 => value & !u64::MAX.wrapping_shl(u32::try_from(width).unwrap_or(0).wrapping_mul(8)),
_ => value,
}
}
#[must_use]
pub fn parse_rule(text: &str) -> Option<(&str, u64)> {
let (glob, value) = text.rsplit_once('=')?;
let value = if let Some(hex) = value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
{
u64::from_str_radix(hex, 16).ok()?
} else if value.len() > 1
&& let Some(octal) = value.strip_prefix('0')
{
u64::from_str_radix(octal, 8).ok()?
} else {
value.parse().ok()?
};
Some((glob, value))
}
#[derive(Clone, Debug)]
pub struct Glob {
alternatives: Vec<Vec<Token>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum Token {
Byte(u8),
Any,
Star,
Class(Box<[u64; 4]>),
}
const MAX_ALTERNATIVES: usize = 1024;
impl Glob {
#[must_use]
pub fn new(pattern: &[u8]) -> Option<Self> {
let mut alternatives: Vec<Vec<Token>> = vec![Vec::new()];
let mut i = 0usize;
while let Some(&c) = pattern.get(i) {
i = i.wrapping_add(1);
match c {
b'\\' => {
let &escaped = pattern.get(i)?;
i = i.wrapping_add(1);
push_all(&mut alternatives, &[Token::Byte(escaped)]);
}
b'*' => push_all(&mut alternatives, &[Token::Star]),
b'?' => push_all(&mut alternatives, &[Token::Any]),
b'[' => {
let (class, next) = parse_class(pattern, i)?;
i = next;
push_all(&mut alternatives, &[Token::Class(class)]);
}
b'{' => {
let close = pattern
.get(i..)?
.iter()
.position(|&b| b == b'}')
.map(|p| p.wrapping_add(i))?;
let body = pattern.get(i..close)?;
i = close.wrapping_add(1);
let mut expanded = Vec::new();
for choice in body.split(|&b| b == b',') {
let choice = Glob::new(choice)?;
for prefix in &alternatives {
for suffix in &choice.alternatives {
if expanded.len() >= MAX_ALTERNATIVES {
return None;
}
let mut tokens = prefix.clone();
tokens.extend(suffix.iter().cloned());
expanded.push(tokens);
}
}
}
alternatives = expanded;
}
other => push_all(&mut alternatives, &[Token::Byte(other)]),
}
}
Some(Self { alternatives })
}
#[must_use]
pub fn matches(&self, name: &[u8]) -> bool {
self.alternatives
.iter()
.any(|tokens| match_tokens(tokens, name))
}
}
fn push_all(alternatives: &mut [Vec<Token>], tokens: &[Token]) {
for alternative in alternatives {
alternative.extend(tokens.iter().cloned());
}
}
fn parse_class(pattern: &[u8], mut i: usize) -> Option<(Box<[u64; 4]>, usize)> {
let mut set = [0u64; 4];
let negate = matches!(pattern.get(i), Some(b'^' | b'!'));
if negate {
i = i.wrapping_add(1);
}
let mut first = true;
loop {
let &c = pattern.get(i)?;
if c == b']' && !first {
i = i.wrapping_add(1);
break;
}
first = false;
let lo = if c == b'\\' {
i = i.wrapping_add(1);
*pattern.get(i)?
} else {
c
};
i = i.wrapping_add(1);
let hi = if pattern.get(i) == Some(&b'-') && pattern.get(i.wrapping_add(1)) != Some(&b']') {
let &hi = pattern.get(i.wrapping_add(1))?;
i = i.wrapping_add(2);
hi
} else {
lo
};
for b in lo..=hi {
set[usize::from(b >> 6)] |= 1 << (b & 63);
}
}
if negate {
for word in &mut set {
*word = !*word;
}
}
Some((Box::new(set), i))
}
fn match_tokens(tokens: &[Token], name: &[u8]) -> bool {
let (mut t, mut n) = (0usize, 0usize);
let mut star: Option<(usize, usize)> = None;
loop {
match (tokens.get(t), name.get(n)) {
(Some(Token::Star), _) => {
t = t.wrapping_add(1);
star = Some((t, n));
}
(Some(token), Some(&byte))
if match token {
Token::Byte(b) => *b == byte,
Token::Any => true,
Token::Class(set) => set[usize::from(byte >> 6)] & (1 << (byte & 63)) != 0,
Token::Star => false,
} =>
{
t = t.wrapping_add(1);
n = n.wrapping_add(1);
}
(None, None) => return true,
_ => match star {
Some((star_t, star_n)) if star_n < name.len() => {
let next = star_n.wrapping_add(1);
star = Some((star_t, next));
t = star_t;
n = next;
}
_ => return false,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lld_builtin_rules() {
let t = Tombstones::default();
let d = DeadTarget::Discarded;
assert_eq!(t.value(b".debug_info", d), Some(0));
assert_eq!(t.value(b".debug_line", d), Some(0));
assert_eq!(t.value(b".debug_loc", d), Some(1));
assert_eq!(t.value(b".debug_ranges", d), Some(1));
assert_eq!(t.value(b".debug_rnglists", d), Some(0));
assert_eq!(t.value(b".debug_names", d), Some(u64::MAX));
assert_eq!(t.value(b".comment", d), None);
assert_eq!(t.value(b".stab", d), None);
assert_eq!(t.value(b".debug_info", DeadTarget::Folded), Some(0));
assert_eq!(t.value(b".debug_ranges", DeadTarget::Folded), Some(1));
assert_eq!(t.value(b".debug_line", DeadTarget::Folded), None);
}
#[test]
fn gnu_builtin_rules() {
let t = Tombstones::new(Style::Gnu);
let d = DeadTarget::Discarded;
assert_eq!(t.value(b".debug_ranges", d), Some(1));
assert_eq!(t.value(b".debug_loc", d), Some(0));
assert_eq!(t.value(b".debug_info", d), Some(0));
assert_eq!(t.value(b".stab", d), Some(0));
assert_eq!(t.value(b".debug_info", DeadTarget::Folded), None);
}
#[test]
fn user_rules_override_and_last_wins() {
let t = Tombstones::default()
.with_rules([
(b".debug_*".as_slice(), 42),
(b".debug_ranges".as_slice(), 7),
(b".my_notes".as_slice(), 9),
])
.unwrap();
let d = DeadTarget::Discarded;
assert_eq!(t.value(b".debug_info", d), Some(42));
assert_eq!(t.value(b".debug_loc", d), Some(42));
assert_eq!(t.value(b".debug_ranges", d), Some(7));
assert_eq!(t.value(b".my_notes", d), Some(9));
assert_eq!(t.value(b".my_notes", DeadTarget::Folded), Some(9));
assert_eq!(t.value(b".debug_line", DeadTarget::Folded), None);
assert_eq!(t.value(b".comment", DeadTarget::Folded), None);
assert!(
Tombstones::default()
.with_rules([(b"[abc".as_slice(), 1)])
.is_err()
);
}
#[test]
fn globs() {
let m = |p: &[u8], n: &[u8]| Glob::new(p).unwrap().matches(n);
assert!(m(b".debug_*", b".debug_info"));
assert!(m(b".debug_*", b".debug_"));
assert!(!m(b".debug_*", b".debug"));
assert!(m(b"*", b""));
assert!(m(b"*info*", b".debug_info.dwo"));
assert!(m(b".debug_?oc", b".debug_loc"));
assert!(!m(b".debug_?oc", b".debug_lloc"));
assert!(m(b".debug_[lr]*", b".debug_ranges"));
assert!(!m(b".debug_[!lr]*", b".debug_ranges"));
assert!(m(b".debug_[a-m]*", b".debug_info"));
assert!(m(br".debug\*", b".debug*"));
assert!(!m(br".debug\*", b".debug_info"));
assert!(m(b".debug_{loc,ranges}", b".debug_loc"));
assert!(m(b".debug_{loc,ranges}", b".debug_ranges"));
assert!(!m(b".debug_{loc,ranges}", b".debug_info"));
assert!(m(b"{.a,.b}*{x,y}", b".b123y"));
assert!(m(b"a*b*c*d", b"aXXbYYcZZd"));
assert!(!m(b"a*b*c*d", b"aXXbYYcZZ"));
assert!(Glob::new(b"[").is_none());
assert!(Glob::new(b"{a").is_none());
assert!(Glob::new(b"a\\").is_none());
assert!(m(b"[]]", b"]"));
}
#[test]
fn rule_parsing_and_truncation() {
assert_eq!(
parse_rule(".debug_*=0xffffffffffffffff"),
Some((".debug_*", u64::MAX))
);
assert_eq!(parse_rule(".debug_ranges=1"), Some((".debug_ranges", 1)));
assert_eq!(parse_rule("a=b=010"), Some(("a=b", 8)));
assert_eq!(parse_rule("x=0"), Some(("x", 0)));
assert_eq!(parse_rule("nothing"), None);
assert_eq!(parse_rule("x=zz"), None);
assert_eq!(truncate(u64::MAX, 4), 0xffff_ffff);
assert_eq!(truncate(u64::MAX, 8), u64::MAX);
assert_eq!(truncate(0x1234, 1), 0x34);
}
}