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