How it works
Mutato injects mutants by rewriting source at compile time, then runs the tests once per mutant in a pool of warm, coverage-filtered hosts. There is one recompile total — never a recompile per mutant.
The compile-time rewrite
Section titled “The compile-time rewrite”csc hook
Section titled “csc hook”MSBuild’s Csc task honours CscToolPath / CscToolExe. Mutato points them at
a bundled wrapper, csc-mutato, which receives the exact argv the real compiler
would get, parses the response file with Roslyn’s own command-line parser, and
builds a real CSharpCompilation (with a full semantic model) from it — no
project-graph reconstruction.
Type-aware schemata rewrite
Section titled “Type-aware schemata rewrite”Each mutation point becomes a runtime selection on MutantContext.ActiveId
(Sel(id) == id ? mutant : original). Operators are chosen against the semantic
model, so only type-valid mutants are emitted, each with an exact source span
and replacement text recorded in the catalog. Every mutant is woven into one
instrumented copy of the target assembly, and the active mutant is flipped by
writing a single static field (~0 µs). Because the rewrite happens on source with
a semantic model, the resulting IL is valid by construction.
Compile-error backstop
Section titled “Compile-error backstop”The mutated compilation is verified in-process — under the real assembly name and
strong-name key, so InternalsVisibleTo still holds — and any mutant that
doesn’t compile is dropped before the real csc ever runs.
Hazard guards
Section titled “Hazard guards”Loop and recursion guards are woven only into scopes that contain a mutation. Infinite loops and broken base cases throw catchable exceptions in-process instead of hanging the run.
Injected runtime
Section titled “Injected runtime”The static surface the instrumented code calls into (MutantContext) is
injected as source into the target, so the instrumented assembly is
self-contained — no DLL to deploy, no deps.json patching. If several copies of
the type coexist, all state lives in corlib-typed cells shared through a
process-global rendezvous, so every copy aliases the same state.
Running the mutants
Section titled “Running the mutants”Warm host pool
Section titled “Warm host pool”Mutato runs one coverage pass to build a covering-test map, shards the covered mutants across N long-lived worker processes, then for each mutant flips the field and reruns only its covering tests. Timeouts adapt per mutant, and a worker that crashes is respawned. A cold subprocess handles static-constructor mutants that can’t be re-flipped in a warm host.
Reports
Section titled “Reports”Survivors are rendered as exact text or HTML diffs, spliced from the rewriter’s own replacement text, and the run drives Stryker’s standard schema — so you can open the report in Stryker’s viewer or dashboard.
Why source rewriting
Section titled “Why source rewriting”The approach was proven across three generations: a Harmony runtime-detour
prototype, a Mono.Cecil static IL weave, and finally this csc-wrapper source
rewrite — which replaced the IL weave entirely. Rewriting source with a semantic
model makes the invalid-IL bug class structurally impossible, maps every mutant
to an exact source span, and works for Release builds too.