1use std::{
2 fmt::Display,
3 path::{Component, PathBuf},
4};
5
6use serde::{Deserialize, Serialize};
7use solar::{
8 interface::BytePos,
9 parse::ast::{BinOpKind, LitKind, Span, StrKind, UnOpKind},
10};
11
12use super::visitor::AssignVarTypes;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct UnaryOpMutated {
17 new_expression: String,
20
21 #[serde(serialize_with = "serialize_unop_kind", deserialize_with = "deserialize_unop_kind")]
23 pub resulting_op_kind: UnOpKind,
24}
25
26fn serialize_unop_kind<S>(value: &UnOpKind, serializer: S) -> Result<S::Ok, S::Error>
28where
29 S: serde::Serializer,
30{
31 let s = format!("{value:?}");
32 serializer.serialize_str(&s)
33}
34
35fn deserialize_unop_kind<'de, D>(deserializer: D) -> Result<UnOpKind, D::Error>
36where
37 D: serde::Deserializer<'de>,
38{
39 let s = String::deserialize(deserializer)?;
40 match s.as_str() {
41 "PreInc" => Ok(UnOpKind::PreInc),
42 "PostInc" => Ok(UnOpKind::PostInc),
43 "PreDec" => Ok(UnOpKind::PreDec),
44 "PostDec" => Ok(UnOpKind::PostDec),
45 "Not" => Ok(UnOpKind::Not),
46 "BitNot" => Ok(UnOpKind::BitNot),
47 "Neg" => Ok(UnOpKind::Neg),
48 other => Err(serde::de::Error::custom(format!("Unknown UnOpKind: {other}"))),
49 }
50}
51
52impl UnaryOpMutated {
53 pub const fn new(new_expression: String, resulting_op_kind: UnOpKind) -> Self {
54 Self { new_expression, resulting_op_kind }
55 }
56}
57
58impl Display for UnaryOpMutated {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(f, "{}", self.new_expression)
61 }
62}
63
64fn serialize_binop<S>(value: &BinOpKind, serializer: S) -> Result<S::Ok, S::Error>
66where
67 S: serde::Serializer,
68{
69 let s = format!("{value:?}");
70 serializer.serialize_str(&s)
71}
72
73fn deserialize_binop<'de, D>(deserializer: D) -> Result<BinOpKind, D::Error>
74where
75 D: serde::Deserializer<'de>,
76{
77 let s = String::deserialize(deserializer)?;
78 match s.as_str() {
79 "Add" => Ok(BinOpKind::Add),
80 "Sub" => Ok(BinOpKind::Sub),
81 "Mul" => Ok(BinOpKind::Mul),
82 "Div" => Ok(BinOpKind::Div),
83 "And" => Ok(BinOpKind::And),
84 "Or" => Ok(BinOpKind::Or),
85 "Eq" => Ok(BinOpKind::Eq),
86 "Ne" => Ok(BinOpKind::Ne),
87 "Lt" => Ok(BinOpKind::Lt),
88 "Le" => Ok(BinOpKind::Le),
89 "Gt" => Ok(BinOpKind::Gt),
90 "Ge" => Ok(BinOpKind::Ge),
91 "BitAnd" => Ok(BinOpKind::BitAnd),
92 "BitOr" => Ok(BinOpKind::BitOr),
93 "BitXor" => Ok(BinOpKind::BitXor),
94 "Shl" => Ok(BinOpKind::Shl),
95 "Shr" => Ok(BinOpKind::Shr),
96 "Sar" => Ok(BinOpKind::Sar),
97 "Pow" => Ok(BinOpKind::Pow),
98 "Rem" => Ok(BinOpKind::Rem),
99 other => Err(serde::de::Error::custom(format!("Unknown BinOpKind: {other}"))),
100 }
101}
102
103#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
106pub enum OwnedStrKind {
107 Str,
108 Unicode,
109 Hex,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub enum OwnedLiteral {
114 Str {
115 kind: OwnedStrKind,
116 text: String,
117 },
118 Number(alloy_primitives::U256),
119 Rational(String),
120 Address(String),
121 Bool(bool),
122 Err(String),
123 NegatedNumber(alloy_primitives::U256),
128}
129
130impl From<&LitKind<'_>> for OwnedLiteral {
131 fn from(lit_kind: &LitKind<'_>) -> Self {
132 match lit_kind {
133 LitKind::Bool(b) => Self::Bool(*b),
134 LitKind::Number(n) => Self::Number(*n),
135 LitKind::Rational(r) => Self::Rational(r.to_string()),
136 LitKind::Address(addr) => Self::Address(addr.to_string()),
137 LitKind::Str(sk, bytesym, _extras) => {
138 let text = String::from_utf8_lossy(bytesym.as_byte_str()).into_owned();
139 let kind = match sk {
140 StrKind::Str => OwnedStrKind::Str,
141 StrKind::Unicode => OwnedStrKind::Unicode,
142 StrKind::Hex => OwnedStrKind::Hex,
143 };
144 Self::Str { kind, text }
145 }
146 LitKind::Err(_) => Self::Err("parse_error".to_string()),
147 }
148 }
149}
150
151impl Display for OwnedLiteral {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 match self {
154 Self::Bool(val) => write!(f, "{val}"),
155 Self::Number(val) => write!(f, "{val}"),
156 Self::NegatedNumber(val) => write!(f, "-{val}"),
157 Self::Rational(s) => write!(f, "{s}"),
158 Self::Address(s) => write!(f, "{s}"),
159 Self::Str { kind, text } => match kind {
160 OwnedStrKind::Str => write!(f, "\"{text}\""),
161 OwnedStrKind::Unicode => write!(f, "unicode\"{text}\""),
162 OwnedStrKind::Hex => write!(f, "hex\"{text}\""),
163 },
164 Self::Err(s) => write!(f, "{s}"),
165 }
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub enum MutationType {
171 Assignment(AssignVarTypes),
182
183 #[serde(serialize_with = "serialize_binop", deserialize_with = "deserialize_binop")]
186 BinaryOp(BinOpKind),
187
188 BinaryOpExpr {
191 #[serde(serialize_with = "serialize_binop", deserialize_with = "deserialize_binop")]
192 new_op: BinOpKind,
193 mutated_expr: String,
194 },
195
196 DeleteExpression,
198
199 ElimDelegate,
201
202 FunctionCall,
204
205 Require,
215
216 RequireCondition {
221 mutated_call: String,
223 },
224
225 SwapArgumentsFunction,
231
232 SwapArgumentsOperator,
237
238 UnaryOperator(UnaryOpMutated),
242
243 YulOpcode {
244 original_opcode: String,
245 new_opcode: String,
246 mutated_expr: String,
247 },
248}
249
250impl Display for MutationType {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 match self {
253 Self::Assignment(kind) => match kind {
254 AssignVarTypes::Literal(lit) => write!(f, "{lit}"),
255 AssignVarTypes::Identifier(ident) => write!(f, "{ident}"),
256 AssignVarTypes::NegatedIdentifier(ident) => write!(f, "-{ident}"),
257 },
258 Self::BinaryOp(kind) => write!(f, "{}", kind.to_str()),
259 Self::BinaryOpExpr { mutated_expr, .. } => write!(f, "{mutated_expr}"),
260 Self::DeleteExpression => write!(f, "assert(true)"),
261 Self::ElimDelegate => write!(f, "call"),
262 Self::UnaryOperator(mutated) => write!(f, "{mutated}"),
263 Self::RequireCondition { mutated_call } => write!(f, "{mutated_call}"),
264
265 Self::YulOpcode { mutated_expr, .. } => write!(f, "{mutated_expr}"),
266
267 Self::FunctionCall
268 | Self::Require
269 | Self::SwapArgumentsFunction
270 | Self::SwapArgumentsOperator => write!(f, ""),
271 }
272 }
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub enum MutationResult {
277 Dead,
278 Alive,
279 Invalid,
280 Skipped,
281 TimedOut,
284}
285
286impl MutationResult {
287 pub const fn label(&self) -> &'static str {
289 match self {
290 Self::Dead => "KILLED",
291 Self::Alive => "SURVIVED",
292 Self::Invalid => "INVALID",
293 Self::Skipped => "SKIPPED",
294 Self::TimedOut => "TIMED OUT",
295 }
296 }
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct Mutant {
302 pub path: PathBuf,
305 #[serde(serialize_with = "serialize_span", deserialize_with = "deserialize_span")]
306 pub span: Span,
307 pub mutation: MutationType,
308 #[serde(default)]
310 pub original: String,
311 #[serde(default)]
313 pub source_line: String,
314 #[serde(default)]
316 pub line_number: usize,
317 #[serde(default)]
319 pub column_number: usize,
320}
321
322fn serialize_span<S>(span: &Span, serializer: S) -> Result<S::Ok, S::Error>
324where
325 S: serde::Serializer,
326{
327 use serde::Serialize;
328 #[derive(Serialize)]
329 struct SpanHelper {
330 lo: u32,
331 hi: u32,
332 }
333 SpanHelper { lo: span.lo().0, hi: span.hi().0 }.serialize(serializer)
334}
335
336fn deserialize_span<'de, D>(deserializer: D) -> Result<Span, D::Error>
337where
338 D: serde::Deserializer<'de>,
339{
340 use serde::Deserialize;
341 #[derive(Deserialize)]
342 struct SpanHelper {
343 lo: u32,
344 hi: u32,
345 }
346 let helper = SpanHelper::deserialize(deserializer)?;
347 Ok(Span::new(BytePos(helper.lo), BytePos(helper.hi)))
348}
349
350impl Mutant {
351 pub fn relative_path(&self) -> String {
357 let components = self
358 .path
359 .components()
360 .filter_map(|c| match c {
361 Component::Normal(s) => Some(s.to_string_lossy()),
362 _ => None,
363 })
364 .collect::<Vec<_>>();
365 let start = if self.path.is_relative() {
366 0
367 } else {
368 components
369 .iter()
370 .position(|s| matches!(s.as_ref(), "src" | "test" | "script" | "lib" | "contracts"))
371 .unwrap_or(components.len().saturating_sub(1))
372 };
373 components[start..].join("/")
374 }
375
376 pub fn short_description(&self) -> String {
378 let original = if self.original.is_empty() {
379 "<unknown>".to_string()
380 } else {
381 self.original.trim().to_string()
382 };
383 let mutated = self.mutation.to_string();
384
385 format!("`{}` → `{}`", original, mutated.trim())
386 }
387}
388
389impl Display for Mutant {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 if self.line_number > 0 {
392 write!(f, "{}:{}: {}", self.relative_path(), self.line_number, self.short_description())
393 } else {
394 write!(f, "{}: {}", self.relative_path(), self.short_description())
395 }
396 }
397}