bcore_mutation/operators/
mod.rs1use regex::Regex;
2
3use crate::project::Project;
4
5mod bitcoin_core;
6mod common;
7mod secp256k1;
8
9pub use bitcoin_core::BitcoinCore;
10pub use secp256k1::Secp256k1;
11
12#[derive(Debug, Clone)]
13pub struct MutationOperator {
14 pub pattern: Regex,
15 pub replacement: String,
16}
17
18impl MutationOperator {
19 pub fn new(pattern: &str, replacement: &str) -> Result<Self, regex::Error> {
20 Ok(MutationOperator {
21 pattern: Regex::new(pattern)?,
22 replacement: replacement.to_string(),
23 })
24 }
25}
26
27pub(crate) fn build(pairs: Vec<(&str, &str)>) -> Result<Vec<MutationOperator>, regex::Error> {
30 pairs
31 .into_iter()
32 .map(|(pattern, replacement)| MutationOperator::new(pattern, replacement))
33 .collect()
34}
35
36pub trait OperatorSet {
42 fn regex_operators(&self) -> Result<Vec<MutationOperator>, regex::Error>;
44
45 fn security_operators(&self) -> Result<Vec<MutationOperator>, regex::Error>;
47
48 fn test_operators(&self) -> Result<Vec<MutationOperator>, regex::Error>;
50
51 fn do_not_mutate_patterns(&self) -> Vec<&'static str>;
53
54 fn do_not_mutate_py_patterns(&self) -> Vec<&'static str>;
56
57 fn do_not_mutate_unit_patterns(&self) -> Vec<&'static str>;
59
60 fn skip_if_contain_patterns(&self) -> Vec<&'static str>;
62
63 fn test_line_skip_prefixes(&self) -> Vec<&'static str>;
66
67 fn should_mutate_test_line(&self, line: &str) -> bool {
73 let trimmed = line.trim();
74
75 for pattern in self.test_line_skip_prefixes() {
76 if trimmed.starts_with(pattern) {
77 return false;
78 }
79 }
80
81 let function_call_pattern =
83 Regex::new(r"^\s*(?:\w+(?:\.|->|::))*(\w+)\s*\([^)]*\)\s*;?\s*$").unwrap();
84 function_call_pattern.is_match(line)
85 }
86}
87
88pub fn for_project(project: Project) -> Box<dyn OperatorSet> {
90 match project {
91 Project::BitcoinCore => Box::new(BitcoinCore),
92 Project::Secp256k1 => Box::new(Secp256k1),
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 fn generic_call_deletion_op() -> MutationOperator {
101 MutationOperator::new(
102 r"^\s*[a-zA-Z_]\w*(?:::[a-zA-Z_]\w*)*(?:(?:->|\.)[a-zA-Z_]\w*)*\s*\([^;]*\)\s*;$",
103 "",
104 )
105 .unwrap()
106 }
107
108 #[test]
109 fn test_generic_call_deletion_matches_free_function() {
110 let op = generic_call_deletion_op();
111 assert!(op.pattern.is_match(" Foo(arg1, arg2);"));
112 assert!(op.pattern.is_match("DoSomething();"));
113 }
114
115 #[test]
116 fn test_generic_call_deletion_matches_dot_member_call() {
117 let op = generic_call_deletion_op();
118 assert!(op.pattern.is_match(" obj.Method(arg);"));
119 assert!(op.pattern.is_match("obj.Method();"));
120 }
121
122 #[test]
123 fn test_generic_call_deletion_matches_arrow_member_call() {
124 let op = generic_call_deletion_op();
125 assert!(op.pattern.is_match(" ptr->Method(arg);"));
126 assert!(op.pattern.is_match("ptr->Method();"));
127 }
128
129 #[test]
130 fn test_generic_call_deletion_matches_namespaced_call() {
131 let op = generic_call_deletion_op();
132 assert!(op.pattern.is_match(" Namespace::Function(arg);"));
133 assert!(op.pattern.is_match("ns::Foo();"));
134 }
135
136 #[test]
137 fn test_generic_call_deletion_ignores_control_flow_and_keywords() {
138 let op = generic_call_deletion_op();
139 assert!(!op.pattern.is_match(" if (condition) {"));
140 assert!(!op.pattern.is_match(" while (x > 0) {"));
141 assert!(!op.pattern.is_match(" for (int i = 0; i < n; i++) {"));
142 assert!(!op.pattern.is_match(" switch (value) {"));
143 assert!(!op.pattern.is_match(" return Foo();"));
144 assert!(!op.pattern.is_match(" delete ptr;"));
145 assert!(!op
146 .pattern
147 .is_match(" throw std::runtime_error(\"err\");"));
148 }
149
150 #[test]
151 fn test_for_project_returns_distinct_sets() {
152 let btc = for_project(Project::BitcoinCore);
155 let secp = for_project(Project::Secp256k1);
156 assert!(!btc.do_not_mutate_py_patterns().is_empty());
157 assert!(secp.do_not_mutate_py_patterns().is_empty());
158 }
159}