A Self-ish Bug
I recently found a bug in mutagen: The “exchange arguments” mutation was actually ineffective, because the plugin code inserting it looked like the following (simplified):
quote_stmt!(cx, report_coverage($n..$n + 1, &$sym[$flag], $mask);
let ($a, $b) = if now($n) { ($b, $a) } else { ($a, $b) };)
Spot the bug? There are two statements in the quote!
macro call. Only the
first one will actually be inserted, though, the second is silently dropped.
Ok, that wouldn’t be too bad. I was in the process of refactoring the code to
pull coverage reporting into the mutagen calls (to reduce the amount of code
generated), so the report_coverage
call was to go away anyway.
Except this bug masked another, more insiduous one: When I refactored, I found
that one of the test would no longer compile methods with self
arguments,
running into Error E0424
(self
keyword used in static method). Consider me
confused.
The offending test code in question:
#[mutate]
impl ComplexStruct {
fn interchange_self(self, other: Self) { }
}
The mutation should exchange self
and other
. Due to the fact that mutagen
bakes all mutations in, selecting the active mutation at runtime, we would emit
code like the following:
struct ComplexStruct;
impl ComplexStruct {
fn interchange_self(self, other: Self) {
let (__self, other) = if ::mutagen::now(1, ..) {
(other, self)
} else {
(self, other)
}
}
}
Note the __self
: We cannot bind self
within a let
binding, so we need to
replace it with another name, and this is the one we generate. Within the
block, we will replace all occurrences of self
with __self
, btw. but as
the method here is empty, this doesn’t happen here.
So if I put the above code in a plain rust file, add an fn main() {}
at the
end and run rustc
, it compiles. My confusion intensified at this point.
In such situation, I usually take a step back to form a theory on what is going
on. Since the function invites the “exchange args” mutation and this mutation
might in this case modify self
, I’ll best confirm that the self
parameter
is in fact the problem.
Changing the let
binding in the mutation to let (_, _)
fixes the
compilation and cargo expand --test integration
shows the expected code:
impl ComplexStruct {
fn interchange_self(self, other: Self) {
// some other code removed for brevity
static __COVERAGE115: [AtomicUsize; 1] = [ATOMIC_USIZE_INIT];
let (_, _) = if ::mutagen::now(116, &__COVERAGE115[0], 2) {
(other, self)
} else {
(self, other)
};
}
}
Could it be that the self
isn’t actually changed to a different ident? To
avoid making a long story longer, yes it could. Adding an unwrap
call
confirmed this suspicion: The self_sym
symbol was only defined if self
was
actually mutable.
(That’s why I ought to refactor this code – it’s getting hard to navigate)
With that out of the way, we could finally replace self
s and be on our merry
way, correcting the bug while making the code leaner.