use regex::Regex;
use crate::project::Project;
mod bitcoin_core;
mod common;
mod secp256k1;
pub use bitcoin_core::BitcoinCore;
pub use secp256k1::Secp256k1;
#[derive(Debug, Clone)]
pub struct MutationOperator {
pub pattern: Regex,
pub replacement: String,
}
impl MutationOperator {
pub fn new(pattern: &str, replacement: &str) -> Result<Self, regex::Error> {
Ok(MutationOperator {
pattern: Regex::new(pattern)?,
replacement: replacement.to_string(),
})
}
}
pub(crate) fn build(pairs: Vec<(&str, &str)>) -> Result<Vec<MutationOperator>, regex::Error> {
pairs
.into_iter()
.map(|(pattern, replacement)| MutationOperator::new(pattern, replacement))
.collect()
}
pub trait OperatorSet {
fn regex_operators(&self) -> Result<Vec<MutationOperator>, regex::Error>;
fn security_operators(&self) -> Result<Vec<MutationOperator>, regex::Error>;
fn test_operators(&self) -> Result<Vec<MutationOperator>, regex::Error>;
fn do_not_mutate_patterns(&self) -> Vec<&'static str>;
fn do_not_mutate_py_patterns(&self) -> Vec<&'static str>;
fn do_not_mutate_unit_patterns(&self) -> Vec<&'static str>;
fn skip_if_contain_patterns(&self) -> Vec<&'static str>;
fn test_line_skip_prefixes(&self) -> Vec<&'static str>;
fn should_mutate_test_line(&self, line: &str) -> bool {
let trimmed = line.trim();
for pattern in self.test_line_skip_prefixes() {
if trimmed.starts_with(pattern) {
return false;
}
}
let function_call_pattern =
Regex::new(r"^\s*(?:\w+(?:\.|->|::))*(\w+)\s*\([^)]*\)\s*;?\s*$").unwrap();
function_call_pattern.is_match(line)
}
}
pub fn for_project(project: Project) -> Box<dyn OperatorSet> {
match project {
Project::BitcoinCore => Box::new(BitcoinCore),
Project::Secp256k1 => Box::new(Secp256k1),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn generic_call_deletion_op() -> MutationOperator {
MutationOperator::new(
r"^\s*[a-zA-Z_]\w*(?:::[a-zA-Z_]\w*)*(?:(?:->|\.)[a-zA-Z_]\w*)*\s*\([^;]*\)\s*;$",
"",
)
.unwrap()
}
#[test]
fn test_generic_call_deletion_matches_free_function() {
let op = generic_call_deletion_op();
assert!(op.pattern.is_match(" Foo(arg1, arg2);"));
assert!(op.pattern.is_match("DoSomething();"));
}
#[test]
fn test_generic_call_deletion_matches_dot_member_call() {
let op = generic_call_deletion_op();
assert!(op.pattern.is_match(" obj.Method(arg);"));
assert!(op.pattern.is_match("obj.Method();"));
}
#[test]
fn test_generic_call_deletion_matches_arrow_member_call() {
let op = generic_call_deletion_op();
assert!(op.pattern.is_match(" ptr->Method(arg);"));
assert!(op.pattern.is_match("ptr->Method();"));
}
#[test]
fn test_generic_call_deletion_matches_namespaced_call() {
let op = generic_call_deletion_op();
assert!(op.pattern.is_match(" Namespace::Function(arg);"));
assert!(op.pattern.is_match("ns::Foo();"));
}
#[test]
fn test_generic_call_deletion_ignores_control_flow_and_keywords() {
let op = generic_call_deletion_op();
assert!(!op.pattern.is_match(" if (condition) {"));
assert!(!op.pattern.is_match(" while (x > 0) {"));
assert!(!op.pattern.is_match(" for (int i = 0; i < n; i++) {"));
assert!(!op.pattern.is_match(" switch (value) {"));
assert!(!op.pattern.is_match(" return Foo();"));
assert!(!op.pattern.is_match(" delete ptr;"));
assert!(!op
.pattern
.is_match(" throw std::runtime_error(\"err\");"));
}
#[test]
fn test_for_project_returns_distinct_sets() {
let btc = for_project(Project::BitcoinCore);
let secp = for_project(Project::Secp256k1);
assert!(!btc.do_not_mutate_py_patterns().is_empty());
assert!(secp.do_not_mutate_py_patterns().is_empty());
}
}