1use std::{
2 fmt::Display,
3 path::{Component, PathBuf},
4};
5
6use serde::{Deserialize, Serialize};
7use solar::{
8 interface::BytePos,
9 parse::ast::{BinOpKind, Lit, 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 BigNumber(String),
121 Address(String),
122 Bool(bool),
123 Err(String),
124 NegatedNumber(alloy_primitives::U256),
129}
130
131impl From<&Lit<'_>> for OwnedLiteral {
132 fn from(lit: &Lit<'_>) -> Self {
133 match &lit.kind {
134 LitKind::Bool(b) => Self::Bool(*b),
135 LitKind::Number(n) => Self::Number(*n),
136 LitKind::Rational(..) => Self::Rational(lit.symbol.to_string()),
137 LitKind::BigNumber(..) => Self::BigNumber(lit.symbol.to_string()),
138 LitKind::Address(addr) => Self::Address(addr.to_string()),
139 LitKind::Str(sk, bytesym, _extras) => {
140 let text = String::from_utf8_lossy(bytesym.as_byte_str()).into_owned();
141 let kind = match sk {
142 StrKind::Str => OwnedStrKind::Str,
143 StrKind::Unicode => OwnedStrKind::Unicode,
144 StrKind::Hex => OwnedStrKind::Hex,
145 };
146 Self::Str { kind, text }
147 }
148 LitKind::Err(_) => Self::Err("parse_error".to_string()),
149 }
150 }
151}
152
153impl Display for OwnedLiteral {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self {
156 Self::Bool(val) => write!(f, "{val}"),
157 Self::Number(val) => write!(f, "{val}"),
158 Self::NegatedNumber(val) => write!(f, "-{val}"),
159 Self::Rational(s) | Self::BigNumber(s) => write!(f, "{s}"),
160 Self::Address(s) => write!(f, "{s}"),
161 Self::Str { kind, text } => match kind {
162 OwnedStrKind::Str => write!(f, "\"{text}\""),
163 OwnedStrKind::Unicode => write!(f, "unicode\"{text}\""),
164 OwnedStrKind::Hex => write!(f, "hex\"{text}\""),
165 },
166 Self::Err(s) => write!(f, "{s}"),
167 }
168 }
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub enum MutationType {
173 Assignment(AssignVarTypes),
184
185 #[serde(serialize_with = "serialize_binop", deserialize_with = "deserialize_binop")]
188 BinaryOp(BinOpKind),
189
190 BinaryOpExpr {
193 #[serde(serialize_with = "serialize_binop", deserialize_with = "deserialize_binop")]
194 new_op: BinOpKind,
195 mutated_expr: String,
196 },
197
198 DeleteExpression,
200
201 ElimDelegate,
203
204 FunctionCall,
206
207 FunctionBody(String),
209
210 Require,
220
221 RequireCondition {
226 mutated_call: String,
228 },
229
230 SwapArgumentsFunction,
236
237 SwapArgumentsOperator,
242
243 UnaryOperator(UnaryOpMutated),
247
248 YulOpcode {
249 original_opcode: String,
250 new_opcode: String,
251 mutated_expr: String,
252 },
253}
254
255impl Display for MutationType {
256 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 match self {
258 Self::Assignment(kind) => match kind {
259 AssignVarTypes::Literal(lit) => write!(f, "{lit}"),
260 AssignVarTypes::Identifier(ident) => write!(f, "{ident}"),
261 AssignVarTypes::NegatedIdentifier(ident) => write!(f, "-{ident}"),
262 },
263 Self::BinaryOp(kind) => write!(f, "{}", kind.to_str()),
264 Self::BinaryOpExpr { mutated_expr, .. } => write!(f, "{mutated_expr}"),
265 Self::DeleteExpression => write!(f, "assert(true)"),
266 Self::ElimDelegate => write!(f, "call"),
267 Self::UnaryOperator(mutated) => write!(f, "{mutated}"),
268 Self::RequireCondition { mutated_call } => write!(f, "{mutated_call}"),
269 Self::FunctionBody(body) => write!(f, "{body}"),
270
271 Self::YulOpcode { mutated_expr, .. } => write!(f, "{mutated_expr}"),
272
273 Self::FunctionCall
274 | Self::Require
275 | Self::SwapArgumentsFunction
276 | Self::SwapArgumentsOperator => write!(f, ""),
277 }
278 }
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub enum MutationResult {
283 Dead,
284 Alive,
285 Invalid,
286 Skipped,
287 TimedOut,
290}
291
292impl MutationResult {
293 pub const fn label(&self) -> &'static str {
295 match self {
296 Self::Dead => "KILLED",
297 Self::Alive => "SURVIVED",
298 Self::Invalid => "INVALID",
299 Self::Skipped => "SKIPPED",
300 Self::TimedOut => "TIMED OUT",
301 }
302 }
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct Mutant {
308 pub path: PathBuf,
311 #[serde(serialize_with = "serialize_span", deserialize_with = "deserialize_span")]
312 pub span: Span,
313 pub mutation: MutationType,
314 #[serde(default)]
316 pub original: String,
317 #[serde(default)]
319 pub source_line: String,
320 #[serde(default)]
322 pub line_number: usize,
323 #[serde(default)]
325 pub column_number: usize,
326}
327
328fn serialize_span<S>(span: &Span, serializer: S) -> Result<S::Ok, S::Error>
330where
331 S: serde::Serializer,
332{
333 use serde::Serialize;
334 #[derive(Serialize)]
335 struct SpanHelper {
336 lo: u32,
337 hi: u32,
338 }
339 SpanHelper { lo: span.lo().0, hi: span.hi().0 }.serialize(serializer)
340}
341
342fn deserialize_span<'de, D>(deserializer: D) -> Result<Span, D::Error>
343where
344 D: serde::Deserializer<'de>,
345{
346 use serde::Deserialize;
347 #[derive(Deserialize)]
348 struct SpanHelper {
349 lo: u32,
350 hi: u32,
351 }
352 let helper = SpanHelper::deserialize(deserializer)?;
353 Ok(Span::new(BytePos(helper.lo), BytePos(helper.hi)))
354}
355
356impl Mutant {
357 pub fn relative_path(&self) -> String {
363 let components = self
364 .path
365 .components()
366 .filter_map(|c| match c {
367 Component::Normal(s) => Some(s.to_string_lossy()),
368 _ => None,
369 })
370 .collect::<Vec<_>>();
371 let start = if self.path.is_relative() {
372 0
373 } else {
374 components
375 .iter()
376 .position(|s| matches!(s.as_ref(), "src" | "test" | "script" | "lib" | "contracts"))
377 .unwrap_or(components.len().saturating_sub(1))
378 };
379 components[start..].join("/")
380 }
381
382 pub fn short_description(&self) -> String {
384 let original = if self.original.is_empty() {
385 "<unknown>".to_string()
386 } else {
387 self.original.trim().to_string()
388 };
389 let mutated = self.mutation.to_string();
390
391 format!("`{}` → `{}`", original, mutated.trim())
392 }
393}
394
395impl Display for Mutant {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 if self.line_number > 0 {
398 write!(f, "{}:{}: {}", self.relative_path(), self.line_number, self.short_description())
399 } else {
400 write!(f, "{}: {}", self.relative_path(), self.short_description())
401 }
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use solar::{
409 ast::LitUint,
410 interface::{Session, Symbol},
411 };
412
413 #[test]
414 fn preserve_numeric_literal_spelling() {
415 Session::builder().with_stderr_emitter().build().enter(|| {
416 let cases = [
417 (
418 "1.25e-2",
419 LitKind::Rational(LitUint::from_be_bytes(&[1]), LitUint::from_be_bytes(&[80])),
420 ),
421 ("1e100", LitKind::BigNumber(LitUint::from_be_bytes(&[1; 42]))),
422 ];
423 for (source, kind) in cases {
424 let lit = Lit { span: Span::DUMMY, symbol: Symbol::intern(source), kind };
425 assert_eq!(OwnedLiteral::from(&lit).to_string(), source);
426 }
427 });
428 }
429}