vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Bitwise mutations.

use crate::adversarial::mutations::catalog::BinOpKind;
use syn::spanned::Spanned;
use syn::visit::Visit;

fn op_symbol(op: BinOpKind) -> &'static str {
    match op {
        BinOpKind::And => "&",
        BinOpKind::Or => "|",
        BinOpKind::Xor => "^",
        _ => "",
    }
}

/// Swap a bitwise operator.
#[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,
    )
}

/// Delete the first unary bitwise-not (`!x` → `x`).
#[inline]
pub fn apply_not_delete(source: &str) -> 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..].starts_with('!')
        {
            let left_ok =
                crate::adversarial::mutations::catalog::lexical::previous_non_ws(source, i)
                    .map(|c| !c.is_alphanumeric() && c != '_' && c != ')' && c != ']')
                    .unwrap_or(true);
            let right_idx = i + 1;
            let right_ok = source
                .get(right_idx..)
                .and_then(|s| s.chars().next())
                .map(|c| c.is_alphanumeric() || c == '_' || c == '(' || c == '[')
                .unwrap_or(false);
            if left_ok && right_ok {
                i += 1;
                replaced = true;
                continue;
            }
        }
        if let Some(c) = source[i..].chars().next() {
            result.push(c);
            i += c.len_utf8();
        } else {
            break;
        }
    }
    result
}

/// Delete the first ` & <operand>` sequence.
#[inline]
pub fn apply_and_mask_delete(source: &str) -> String {
    let syntax = match syn::parse_file(source) {
        Ok(s) => s,
        Err(_) => return source.to_string(),
    };
    let mut visitor = BitAndDeleteVisitor { 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 BitAndDeleteVisitor {
    target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn)>,
}

impl<'ast> Visit<'ast> for BitAndDeleteVisitor {
    fn visit_expr_binary(&mut self, i: &'ast syn::ExprBinary) {
        if self.target.is_some() {
            return;
        }
        if matches!(i.op, syn::BinOp::BitAnd(_)) {
            self.target = Some((i.left.span().end(), i.right.span().end()));
        }
        syn::visit::visit_expr_binary(self, i);
    }
}