Analyzers can be used separately from Faultify.
They are able to provide the user mutations.
The reader can look at AssemblyMutator
for inspiration.
This is the abstraction that Faultify uses to analyze for mutations.
Implement Custom Analyzer
To implement your own Analyzer there are a couple of steps:
- Implement (or use existing)
IMutation
. - Implement instance of
IMutationAnalyzer
.
Implement IMutation
The mutation, as shown in the previous chapter, is responsible for performing the mutation. Implement this to your use-case.
public class CustomMutation : IMutation
{
public OpCode OriginalValue { get; set; }
public OpCode NewValue { get; set; }
public Instruction InstructionReference { get; set; }
public void Mutate()
{
InstructionReference.OpCode = NewValue;
}
public void Reset()
{
InstructionReference.OpCode = NewValue;
}
}
Faultify provides a set of IMutation
implementations that serve some general goals.
Implement Analyzer
IMutationAnalyzer
has two generic parameters.
- The first one indicates the mutation type that is returned if the analyzer detects this mutation kind.
- The second one indicates in which scope the mutation can be found.
The scope should be a type from
Mono.Cecil
such as:MethodDefinion
,Instruction
,FieldDefinition
,TypeDefinition
,AssemblyDefinition
.
The general idea is that the analyzer inspects this scope for some possible mutations and returns the generic mutation.
In the following example, an analyzer that searches for add
to sub
mutations in the Instruction
scope is demonstrated:
public class CustomMutationAnalyzer : IMutationAnalyzer<CustomMutation, Instruction>
{
public string Description => "Addition to subtraction analyzer.";
public string Name => "Addition to Subtraction";
public IEnumerable<CustomMutation> AnalyzeMutations(Instruction field,
MutationLevel mutationLevel)
{
if (field.OpCode.Code == Code.Add)
{
yield return new CustomMutation() {OriginalValue = field.OpCode, NewValue = OpCodes.Sub};
}
}
}
Use Analyzer
var analyzer = new CustomMutationAnalyzer();
using var assembly = ModuleDefinition.ReadModule("assembly.dll");
var mutations = assembly.Types
.SelectMany(x => x.Methods)
.SelectMany(x => x.Body.Instructions)
.SelectMany(instruction => analyzer.AnalyzeMutations(instruction, MutationLevel.Detailed).ToList());
foreach (var mutation in mutations)
{
mutation.Mutate();
// do some stuff
mutation.Reset();
}
After mutation or resetting a mutation back to original
one has to write the ModuleDefinition
back to the file system.
Otherwise, the mutations are just purely performed in memory.
Assembly Mutator
The AssemblyMutator
can be used to analyze all kinds of mutations in a target assembly.
It can be extended with custom analyzers.
Tho an extension must correspond to one of the following collections in AssemblyMutator
:
- ArrayMutationAnalyzers (
IMutationAnalyzer<ArrayMutation, MethodDefinition>
) - ConstantAnalyzers (
IMutationAnalyzer<ConstantMutation, FieldDefinition>
) - VariableMutationAnalyzer (
IMutationAnalyzer<VariableMutation, MethodDefinition>
) - OpCodeMutationAnalyzer (
IMutationAnalyzer<OpCodeMutation, Instruction>
)
If you add your analyzer to one of those collections then it will be used in the process of analyzing.
Unfortunately, if your analyzer does not fit the interfaces, it can not be used with the AssemblyMutator
.
Then you would have to create some kind of AssemblyMutator
for yourself.