use crate::adversarial::mutations::catalog::BinOpKind;
use syn::visit::Visit;
fn op_symbol(op: BinOpKind) -> &'static str {
match op {
BinOpKind::Add => "+",
BinOpKind::Sub => "-",
BinOpKind::Mul => "*",
BinOpKind::Div => "/",
BinOpKind::Shl => "<<",
BinOpKind::Shr => ">>",
BinOpKind::And => "&",
BinOpKind::Or => "|",
BinOpKind::Xor => "^",
BinOpKind::Lt => "<",
BinOpKind::Le => "<=",
BinOpKind::Gt => ">",
BinOpKind::Ge => ">=",
BinOpKind::Eq => "==",
BinOpKind::Ne => "!=",
}
}
#[inline]
pub fn apply_op_swap(source: &str, from: BinOpKind, to: BinOpKind) -> String {
crate::adversarial::mutations::catalog::replace_operator(
source,
op_symbol(from),
op_symbol(to),
1,
)
}
#[inline]
pub fn apply_wrapping_to_saturating(source: &str) -> String {
crate::adversarial::mutations::catalog::lexical::replace_code_word(
source,
"wrapping_add",
"saturating_add",
usize::MAX,
)
}
#[inline]
pub fn apply_wrapping_to_checked(source: &str) -> String {
crate::adversarial::mutations::catalog::lexical::replace_code_word(
source,
"wrapping_add",
"checked_add",
usize::MAX,
)
}
#[inline]
pub fn apply_constant_increment(source: &str, by: i64) -> String {
let mut result = String::with_capacity(source.len());
let mut replaced = false;
let mut i = 0;
while i < source.len() {
if !replaced
&& crate::adversarial::mutations::catalog::lexical::is_code_index(source, i)
&& source[i..]
.chars()
.next()
.is_some_and(|c| c.is_ascii_digit())
{
let start = i;
let mut end = i;
while let Some(c) = source[end..].chars().next() {
if c.is_ascii_digit() {
end += c.len_utf8();
} else {
break;
}
}
let next_char = source.get(end..).and_then(|s| s.chars().next());
let is_standalone = next_char.is_none_or(|c| !c.is_alphanumeric() && c != '_');
if is_standalone {
let num_str = &source[start..end];
if let Ok(n) = num_str.parse::<i64>() {
result.push_str(&n.saturating_add(by).to_string());
i = end;
replaced = true;
continue;
}
}
}
if let Some(c) = source[i..].chars().next() {
result.push(c);
i += c.len_utf8();
} else {
break;
}
}
result
}
struct LitIntVisitor {
target: Option<proc_macro2::Span>,
value: u64,
}
struct AnyLitIntVisitor {
target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn, u64)>,
}
impl<'ast> Visit<'ast> for AnyLitIntVisitor {
fn visit_lit_int(&mut self, i: &'ast syn::LitInt) {
if self.target.is_some() {
return;
}
if let Ok(v) = i.base10_parse::<u64>() {
self.target = Some((i.span().start(), i.span().end(), v));
}
}
}
struct AnyLitFloatVisitor {
target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn, f32)>,
}
impl<'ast> Visit<'ast> for AnyLitFloatVisitor {
fn visit_lit_float(&mut self, i: &'ast syn::LitFloat) {
if self.target.is_some() {
return;
}
if let Ok(v) = i.base10_parse::<f32>() {
self.target = Some((i.span().start(), i.span().end(), v));
}
}
}
impl<'ast> Visit<'ast> for LitIntVisitor {
fn visit_lit_int(&mut self, i: &'ast syn::LitInt) {
if self.target.is_some() {
return;
}
if let Ok(v) = i.base10_parse::<u64>() {
if v == self.value {
self.target = Some(i.span());
}
}
}
}
fn replace_first_lit_int(source: &str, match_value: u64, replacement: &str) -> String {
let syntax = match syn::parse_file(source) {
Ok(s) => s,
Err(_) => return source.to_string(),
};
let mut visitor = LitIntVisitor {
target: None,
value: match_value,
};
visitor.visit_file(&syntax);
if let Some(span) = visitor.target {
let start = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
source,
span.start().line,
span.start().column,
);
let end = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
source,
span.end().line,
span.end().column,
);
let mut result = String::with_capacity(source.len() - (end - start) + replacement.len());
result.push_str(&source[..start]);
result.push_str(replacement);
result.push_str(&source[end..]);
result
} else {
source.to_string()
}
}
#[inline]
pub fn apply_constant_zero_to_one(source: &str) -> String {
replace_first_lit_int(source, 0, "1")
}
#[inline]
pub fn apply_constant_one_to_zero(source: &str) -> String {
replace_first_lit_int(source, 1, "0")
}
#[inline]
pub fn apply_constant_max_to_zero(source: &str) -> String {
crate::adversarial::mutations::catalog::lexical::replace_code(source, "u32::MAX", "0", 1)
}
#[inline]
pub fn apply_integer_constant_bit_flip(source: &str, bit: u8) -> String {
let syntax = match syn::parse_file(source) {
Ok(s) => s,
Err(_) => return source.to_string(),
};
let mut visitor = AnyLitIntVisitor { target: None };
visitor.visit_file(&syntax);
let Some((start_lc, end_lc, value)) = visitor.target else {
return source.to_string();
};
let start = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
source,
start_lc.line,
start_lc.column,
);
let end = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
source,
end_lc.line,
end_lc.column,
);
let mask = 1u64.checked_shl(u32::from(bit % 64)).unwrap_or(0);
let replacement = (value ^ mask).to_string();
let mut result = String::with_capacity(source.len() - (end - start) + replacement.len());
result.push_str(&source[..start]);
result.push_str(&replacement);
result.push_str(&source[end..]);
result
}
#[inline]
pub fn apply_float_constant_bit_flip(source: &str, bit: u8) -> String {
let syntax = match syn::parse_file(source) {
Ok(s) => s,
Err(_) => return source.to_string(),
};
let mut visitor = AnyLitFloatVisitor { target: None };
visitor.visit_file(&syntax);
let Some((start_lc, end_lc, value)) = visitor.target else {
return source.to_string();
};
let start = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
source,
start_lc.line,
start_lc.column,
);
let end = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
source,
end_lc.line,
end_lc.column,
);
let mask = 1u32.checked_shl(u32::from(bit % 32)).unwrap_or(0);
let replacement = format!("f32::from_bits(0x{:08x})", value.to_bits() ^ mask);
let mut result = String::with_capacity(source.len() - (end - start) + replacement.len());
result.push_str(&source[..start]);
result.push_str(&replacement);
result.push_str(&source[end..]);
result
}