use crate::adversarial::mutations::catalog::Branch;
use syn::spanned::Spanned;
use syn::visit::Visit;
#[inline]
pub fn apply_branch_delete(source: &str, branch: Branch) -> String {
match branch {
Branch::If => crate::adversarial::mutations::catalog::lexical::replace_code(
source,
"if ",
"if false ",
1,
),
Branch::Else => {
let syntax = match syn::parse_file(source) {
Ok(s) => s,
Err(_) => return source.to_string(),
};
let mut visitor = ElseDeleteVisitor { target: None };
visitor.visit_file(&syntax);
if let Some((start_lc, end_lc)) = visitor.target {
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 mut result = String::with_capacity(source.len() - (end - start));
result.push_str(&source[..start]);
result.push_str(&source[end..]);
result
} else {
source.to_string()
}
}
}
}
struct ElseDeleteVisitor {
target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn)>,
}
impl<'ast> Visit<'ast> for ElseDeleteVisitor {
fn visit_expr_if(&mut self, i: &'ast syn::ExprIf) {
if self.target.is_some() {
return;
}
if let Some((else_token, else_branch)) = &i.else_branch {
self.target = Some((else_token.span.start(), else_branch.span().end()));
}
syn::visit::visit_expr_if(self, i);
}
}
#[inline]
pub fn apply_condition_invert(source: &str) -> String {
let syntax = match syn::parse_file(source) {
Ok(s) => s,
Err(_) => return source.to_string(),
};
let mut visitor = ConditionInvertVisitor { target: None };
visitor.visit_file(&syntax);
if let Some((start_lc, end_lc)) = visitor.target {
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 mut result = String::with_capacity(source.len() + 3);
result.push_str(&source[..start]);
result.push_str("!(");
result.push_str(&source[start..end]);
result.push(')');
result.push_str(&source[end..]);
result
} else {
source.to_string()
}
}
struct ConditionInvertVisitor {
target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn)>,
}
impl<'ast> Visit<'ast> for ConditionInvertVisitor {
fn visit_expr_if(&mut self, i: &'ast syn::ExprIf) {
if self.target.is_some() {
return;
}
if !matches!(i.cond.as_ref(), syn::Expr::Let(_)) {
self.target = Some((i.cond.span().start(), i.cond.span().end()));
}
syn::visit::visit_expr_if(self, i);
}
}
#[inline]
pub fn apply_loop_end_shift(source: &str, by: i64) -> String {
let syntax = match syn::parse_file(source) {
Ok(s) => s,
Err(_) => return source.to_string(),
};
let mut visitor = LoopEndShiftVisitor { target: None };
visitor.visit_file(&syntax);
if let Some((start_lc, end_lc)) = visitor.target {
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 rhs = &source[start..end];
let replacement = if by >= 0 {
format!("({}).saturating_add({})", rhs, by)
} else {
format!("({}).saturating_sub({})", rhs, by.unsigned_abs())
};
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()
}
}
struct LoopEndShiftVisitor {
target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn)>,
}
impl<'ast> Visit<'ast> for LoopEndShiftVisitor {
fn visit_expr_range(&mut self, i: &'ast syn::ExprRange) {
if self.target.is_some() {
return;
}
if let Some(end) = &i.end {
self.target = Some((end.span().start(), end.span().end()));
}
syn::visit::visit_expr_range(self, i);
}
}
#[inline]
pub fn apply_loop_start_shift(source: &str, by: i32) -> String {
if let Some(for_pos) =
crate::adversarial::mutations::catalog::lexical::find_code(source, "for ")
{
let rest = &source[for_pos + 4..];
if let Some(range_pos) = rest.find("..") {
let prefix_end = for_pos + 4 + range_pos;
let prefix = &source[..prefix_end];
if let Some(start_pos) = prefix.rfind('0') {
let left_ok = start_pos
.checked_sub(1)
.and_then(|j| prefix.get(j..=j))
.map(|s| {
let c = s.chars().next().unwrap();
c.is_whitespace() || c == '=' || c == '(' || c == '[' || c == ','
})
.unwrap_or(true);
if left_ok {
return format!("{}{}{}", &source[..start_pos], by, &source[start_pos + 1..]);
}
}
}
}
source.to_string()
}
#[inline]
pub fn apply_break_to_continue(source: &str) -> String {
crate::adversarial::mutations::catalog::lexical::replace_code_word(
source, "break", "continue", 1,
)
}
#[inline]
pub fn apply_continue_remove(source: &str) -> String {
crate::adversarial::mutations::catalog::lexical::replace_code(source, "continue;", "", 1)
}
#[inline]
pub fn apply_flow_skip(source: &str) -> String {
crate::adversarial::mutations::catalog::lexical::replace_code(source, "if ", "if false && ", 1)
}