Skip to content

Commit

Permalink
Added compression
Browse files Browse the repository at this point in the history
  • Loading branch information
LPeter1997 committed Sep 30, 2024
1 parent c6a460a commit c882c0e
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 18 deletions.
7 changes: 5 additions & 2 deletions src/Draco.Compiler.Fuzzer/FuzzerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ internal static class FuzzerFactory
private static ICoverageCompressor<int> CoverageCompressor => Fuzzing.CoverageCompressor.SimdHash;
private static IInputMinimizer<SyntaxTree> InputMinimizer => new SyntaxTreeInputMinimizer();
private static IInputMutator<SyntaxTree> InputMutator => new SyntaxTreeInputMutator();
private static IInputCompressor<SyntaxTree, string> InputCompressor => Fuzzing.InputCompressor.String(text => SyntaxTree.Parse(text));
private static TimeSpan Timeout => TimeSpan.FromSeconds(5);

public static Fuzzer<SyntaxTree, int> CreateInProcess(ITracer<SyntaxTree> tracer, int? seed)
public static Fuzzer<SyntaxTree, string, int> CreateInProcess(ITracer<SyntaxTree> tracer, int? seed)
{
// Things we share between compilations
var bclReferences = ReferenceInfos.All
Expand Down Expand Up @@ -56,11 +57,12 @@ void RunCompilation(SyntaxTree syntaxTree)
FaultDetector = FaultDetector.FilterIdenticalTraces(FaultDetector.InProcess(Timeout)),
InputMinimizer = InputMinimizer,
InputMutator = InputMutator,
InputCompressor = InputCompressor,
Tracer = tracer,
};
}

public static Fuzzer<SyntaxTree, int> CreateOutOfProcess(ITracer<SyntaxTree> tracer, int? seed, int? maxParallelism)
public static Fuzzer<SyntaxTree, string, int> CreateOutOfProcess(ITracer<SyntaxTree> tracer, int? seed, int? maxParallelism)
{
static ProcessStartInfo CreateStartInfo(SyntaxTree syntaxTree)
{
Expand Down Expand Up @@ -93,6 +95,7 @@ static ProcessStartInfo CreateStartInfo(SyntaxTree syntaxTree)
FaultDetector = FaultDetector.OutOfProcess(Timeout),
InputMinimizer = InputMinimizer,
InputMutator = InputMutator,
InputCompressor = InputCompressor,
Tracer = tracer,
};
}
Expand Down
4 changes: 2 additions & 2 deletions src/Draco.Compiler.Fuzzer/TuiTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public override string ToString()
}
}

public Fuzzer<SyntaxTree, int>? Fuzzer { get; private set; }
public Fuzzer<SyntaxTree, string, int>? Fuzzer { get; private set; }

// Coverage info
private readonly ProgressBar currentCoverageProgressBar;
Expand Down Expand Up @@ -318,7 +318,7 @@ public TuiTracer()
Application.Top.Add(menuBar, this, statusBar);
}

public void SetFuzzer(Fuzzer<SyntaxTree, int> fuzzer)
public void SetFuzzer(Fuzzer<SyntaxTree, string, int> fuzzer)
{
this.Fuzzer = fuzzer;
this.seedStatusItem.Title = GetSeedStatusBarTitle(fuzzer.Seed);
Expand Down
79 changes: 65 additions & 14 deletions src/Draco.Fuzzing/Fuzzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,47 @@ namespace Draco.Fuzzing;
/// 3. Mutate the test case
/// </summary>
/// <typeparam name="TInput">The type of the input data.</typeparam>
/// <typeparam name="TCompressedInput">The type of the compressed input data.</typeparam>
/// <typeparam name="TCoverage">The type of the compressed coverage data.</typeparam>
public sealed class Fuzzer<TInput, TCoverage>
public sealed class Fuzzer<TInput, TCompressedInput, TCoverage>
where TCoverage : notnull
{
// Minimal result info of an execution
private readonly record struct ExecutionResult(TCoverage Coverage, FaultResult FaultResult);

// Initial inputs have no coverage data, so the entry needs to handle the case where coverage is not yet present
// and we fill it out later
private sealed class QueueEntry(TInput input, ExecutionResult? executionResult = null)
private sealed class QueueEntry
{
public TInput Input { get; } = input;
public ExecutionResult? ExecutionResult { get; set; } = executionResult;
public ExecutionResult? ExecutionResult { get; set; }

private bool isInputCompressed;
private TInput? input;
private TCompressedInput? compressedInput;

public QueueEntry(TInput input, ExecutionResult? executionResult = null)
{
this.isInputCompressed = false;
this.input = input;
this.ExecutionResult = executionResult;
}

public QueueEntry(TCompressedInput compressedInput, ExecutionResult? executionResult = null)
{
this.isInputCompressed = true;
this.compressedInput = compressedInput;
this.ExecutionResult = executionResult;
}

public TInput GetInput(IInputCompressor<TInput, TCompressedInput> compressor)
{
if (this.isInputCompressed)
{
this.input = compressor.Decompress(this.compressedInput!);
this.isInputCompressed = false;
}
return this.input!;
}
}

/// <summary>
Expand Down Expand Up @@ -54,6 +82,11 @@ private sealed class QueueEntry(TInput input, ExecutionResult? executionResult =
/// </summary>
public required IInputMutator<TInput> InputMutator { get; init; }

/// <summary>
/// The input compressor to use.
/// </summary>
public required IInputCompressor<TInput, TCompressedInput> InputCompressor { get; init; }

/// <summary>
/// The reader to read coverage data with.
/// </summary>
Expand Down Expand Up @@ -96,7 +129,7 @@ public Fuzzer(int? seed = null, int? maxDegreeOfParallelism = null)
/// <param name="input">The input to enqueue.</param>
public void Enqueue(TInput input)
{
this.inputQueue.Add(new QueueEntry(input));
this.inputQueue.Add(this.MakeQueueEntry(input));
lock (this.tracerSync) this.Tracer.InputsEnqueued([input]);
}

Expand All @@ -106,7 +139,7 @@ public void Enqueue(TInput input)
/// <param name="inputs">The inputs to enqueue.</param>
public void EnqueueRange(IEnumerable<TInput> inputs)
{
foreach (var input in inputs) this.inputQueue.Add(new QueueEntry(input));
foreach (var input in inputs) this.inputQueue.Add(this.MakeQueueEntry(input));
lock (this.tracerSync) this.Tracer.InputsEnqueued(inputs);
}

Expand All @@ -131,7 +164,7 @@ public void Run(CancellationToken cancellationToken = default)
EnumerablePartitionerOptions.NoBuffering);
Parallel.ForEach(limitedPartitioner, parallelOptions, entry =>
{
lock (this.tracerSync) this.Tracer.InputDequeued(entry.Input);
lock (this.tracerSync) this.Tracer.InputDequeued(entry.GetInput(this.InputCompressor));
// We want to minimize the input first
entry = this.Minimize(entry);
Expand All @@ -153,14 +186,15 @@ private QueueEntry Minimize(QueueEntry entry)
// While we find a minimization step, we continue to minimize
while (true)
{
foreach (var minimizedInput in this.InputMinimizer.Minimize(this.Random, entry.Input))
var entryInput = entry.GetInput(this.InputCompressor);
foreach (var minimizedInput in this.InputMinimizer.Minimize(this.Random, entryInput))
{
var (minimizedResult, _) = this.Execute(minimizedInput);
if (AreEqualExecutions(referenceResult, minimizedResult))
{
// We found an equivalent execution, replace entry
lock (this.tracerSync) this.Tracer.MinimizationFound(entry.Input, minimizedInput);
entry = new QueueEntry(minimizedInput, minimizedResult);
lock (this.tracerSync) this.Tracer.MinimizationFound(entryInput, minimizedInput);
entry = this.MakeQueueEntry(minimizedInput, minimizedResult);
goto found;
}
}
Expand All @@ -173,19 +207,21 @@ private QueueEntry Minimize(QueueEntry entry)

private void Mutate(QueueEntry entry)
{
foreach (var mutatedInput in this.InputMutator.Mutate(this.Random, entry.Input))
var entryInput = entry.GetInput(this.InputCompressor);
foreach (var mutatedInput in this.InputMutator.Mutate(this.Random, entryInput))
{
var (_, isInteresting) = this.Execute(mutatedInput);
if (isInteresting)
{
lock (this.tracerSync) this.Tracer.MutationFound(entry.Input, mutatedInput);
lock (this.tracerSync) this.Tracer.MutationFound(entryInput, mutatedInput);
}
}
}

private ExecutionResult GetExecutionResult(QueueEntry entry)
{
entry.ExecutionResult ??= this.Execute(entry.Input, dontRequeue: true).Result;
var entryInput = entry.GetInput(this.InputCompressor);
entry.ExecutionResult ??= this.Execute(entryInput, dontRequeue: true).Result;
return entry.ExecutionResult.Value;
}

Expand All @@ -206,12 +242,27 @@ private ExecutionResult GetExecutionResult(QueueEntry entry)
var executionResult = new ExecutionResult(compressedCoverage, faultResult);
if (!dontRequeue && isInteresting)
{
this.inputQueue.Add(new QueueEntry(input, executionResult));
this.inputQueue.Add(this.MakeQueueEntry(input, executionResult));
lock (this.tracerSync) this.Tracer.InputsEnqueued([input]);
}
return (executionResult, isInteresting);
}

private QueueEntry MakeQueueEntry(TInput input, ExecutionResult? executionResult = null)
{
if (this.inputQueue.Count > 5000)
{
// Compress
var compressedInput = this.InputCompressor.Compress(input);
return new QueueEntry(compressedInput, executionResult);
}
else
{
// Don't compress
return new QueueEntry(input, executionResult);
}
}

// We deem an input interesting, if it has not been seen before in terms of coverage
private bool IsInteresting(TCoverage coverage) => this.seenCoverages.Add(coverage);

Expand Down
17 changes: 17 additions & 0 deletions src/Draco.Fuzzing/IInputCompressor.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;

namespace Draco.Fuzzing;

/// <summary>
Expand Down Expand Up @@ -34,9 +36,24 @@ public static class InputCompressor
/// <returns>A compressor that does nothing.</returns>
public static IInputCompressor<TInput, TInput> Null<TInput>() => new NullCompressor<TInput>();

/// <summary>
/// Creates an input compressor that converts the input to a string by calling <see cref="object.ToString"/>
/// and then parses it back using the given parser function.
/// </summary>
/// <typeparam name="TInput">The type of the input data.</typeparam>
/// <param name="parser">The parser function to convert the string back to the input type.</param>
/// <returns>The input compressor.</returns>
public static IInputCompressor<TInput, string> String<TInput>(Func<string, TInput> parser) => new StringCompressor<TInput>(parser);

private sealed class NullCompressor<TInput> : IInputCompressor<TInput, TInput>
{
public TInput Compress(TInput input) => input;
public TInput Decompress(TInput compressedInput) => compressedInput;
}

private sealed class StringCompressor<TInput>(Func<string, TInput> parser) : IInputCompressor<TInput, string>
{
public string Compress(TInput input) => input?.ToString() ?? string.Empty;
public TInput Decompress(string compressedInput) => parser(compressedInput);
}
}

0 comments on commit c882c0e

Please sign in to comment.