summaryrefslogtreecommitdiff
path: root/lib/Fuzzer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Fuzzer')
-rw-r--r--lib/Fuzzer/CMakeLists.txt2
-rw-r--r--lib/Fuzzer/FuzzerCorpus.h81
-rw-r--r--lib/Fuzzer/FuzzerDriver.cpp6
-rw-r--r--lib/Fuzzer/FuzzerExtFunctionsWeak.cpp3
-rw-r--r--lib/Fuzzer/FuzzerFlags.def4
-rw-r--r--lib/Fuzzer/FuzzerIOWindows.cpp4
-rw-r--r--lib/Fuzzer/FuzzerInternal.h8
-rw-r--r--lib/Fuzzer/FuzzerLoop.cpp66
-rw-r--r--lib/Fuzzer/FuzzerOptions.h1
-rw-r--r--lib/Fuzzer/FuzzerUtilDarwin.cpp13
-rw-r--r--lib/Fuzzer/test/CMakeLists.txt3
-rw-r--r--lib/Fuzzer/test/FuzzerUnittest.cpp5
-rw-r--r--lib/Fuzzer/test/ShrinkControlFlowSimpleTest.cpp19
-rw-r--r--lib/Fuzzer/test/reduce_inputs.test13
14 files changed, 175 insertions, 53 deletions
diff --git a/lib/Fuzzer/CMakeLists.txt b/lib/Fuzzer/CMakeLists.txt
index b886021aee3f..fa743c280e86 100644
--- a/lib/Fuzzer/CMakeLists.txt
+++ b/lib/Fuzzer/CMakeLists.txt
@@ -13,6 +13,7 @@ if( APPLE )
endif()
endif()
+set(LIBFUZZER_FLAGS_BASE "${CMAKE_CXX_FLAGS}")
if( LLVM_USE_SANITIZE_COVERAGE )
if(NOT "${LLVM_USE_SANITIZER}" STREQUAL "Address")
message(FATAL_ERROR
@@ -20,7 +21,6 @@ if( LLVM_USE_SANITIZE_COVERAGE )
"LLVM_USE_SANITIZE_COVERAGE=YES to be set."
)
endif()
- set(LIBFUZZER_FLAGS_BASE "${CMAKE_CXX_FLAGS}")
# Disable the coverage and sanitizer instrumentation for the fuzzer itself.
set(CMAKE_CXX_FLAGS "${LIBFUZZER_FLAGS_BASE} -fno-sanitize-coverage=trace-pc-guard,edge,trace-cmp,indirect-calls,8bit-counters -Werror")
diff --git a/lib/Fuzzer/FuzzerCorpus.h b/lib/Fuzzer/FuzzerCorpus.h
index 0f0573994a03..218ae5b6ac4d 100644
--- a/lib/Fuzzer/FuzzerCorpus.h
+++ b/lib/Fuzzer/FuzzerCorpus.h
@@ -34,6 +34,7 @@ struct InputInfo {
size_t NumExecutedMutations = 0;
size_t NumSuccessfullMutations = 0;
bool MayDeleteFile = false;
+ std::vector<uint32_t> FeatureSet;
};
class InputCorpus {
@@ -68,24 +69,84 @@ class InputCorpus {
}
bool empty() const { return Inputs.empty(); }
const Unit &operator[] (size_t Idx) const { return Inputs[Idx]->U; }
- void AddToCorpus(const Unit &U, size_t NumFeatures,
- bool MayDeleteFile = false) {
+ void AddToCorpus(const Unit &U, size_t NumFeatures, bool MayDeleteFile,
+ const std::vector<uint32_t> &FeatureSet) {
assert(!U.empty());
- uint8_t Hash[kSHA1NumBytes];
if (FeatureDebug)
Printf("ADD_TO_CORPUS %zd NF %zd\n", Inputs.size(), NumFeatures);
- ComputeSHA1(U.data(), U.size(), Hash);
- Hashes.insert(Sha1ToString(Hash));
Inputs.push_back(new InputInfo());
InputInfo &II = *Inputs.back();
II.U = U;
II.NumFeatures = NumFeatures;
II.MayDeleteFile = MayDeleteFile;
- memcpy(II.Sha1, Hash, kSHA1NumBytes);
+ II.FeatureSet = FeatureSet;
+ ComputeSHA1(U.data(), U.size(), II.Sha1);
+ Hashes.insert(Sha1ToString(II.Sha1));
UpdateCorpusDistribution();
+ PrintCorpus();
// ValidateFeatureSet();
}
+ // Debug-only
+ void PrintUnit(const Unit &U) {
+ if (!FeatureDebug) return;
+ for (uint8_t C : U) {
+ if (C != 'F' && C != 'U' && C != 'Z')
+ C = '.';
+ Printf("%c", C);
+ }
+ }
+
+ // Debug-only
+ void PrintFeatureSet(const std::vector<uint32_t> &FeatureSet) {
+ if (!FeatureDebug) return;
+ Printf("{");
+ for (uint32_t Feature: FeatureSet)
+ Printf("%u,", Feature);
+ Printf("}");
+ }
+
+ // Debug-only
+ void PrintCorpus() {
+ if (!FeatureDebug) return;
+ Printf("======= CORPUS:\n");
+ int i = 0;
+ for (auto II : Inputs) {
+ if (std::find(II->U.begin(), II->U.end(), 'F') != II->U.end()) {
+ Printf("[%2d] ", i);
+ Printf("%s sz=%zd ", Sha1ToString(II->Sha1).c_str(), II->U.size());
+ PrintUnit(II->U);
+ Printf(" ");
+ PrintFeatureSet(II->FeatureSet);
+ Printf("\n");
+ }
+ i++;
+ }
+ }
+
+ // If FeatureSet is that same as in II, replace II->U with {Data,Size}.
+ bool TryToReplace(InputInfo *II, const uint8_t *Data, size_t Size,
+ const std::vector<uint32_t> &FeatureSet) {
+ if (II->U.size() > Size && II->FeatureSet.size() &&
+ II->FeatureSet == FeatureSet) {
+ if (FeatureDebug)
+ Printf("Replace: %zd => %zd\n", II->U.size(), Size);
+ Replace(II, {Data, Data + Size});
+ PrintCorpus();
+ return true;
+ }
+ return false;
+ }
+
+ void Replace(InputInfo *II, const Unit &U) {
+ assert(II->U.size());
+ Hashes.erase(Sha1ToString(II->Sha1));
+ DeleteFile(*II);
+ ComputeSHA1(U.data(), U.size(), II->Sha1);
+ Hashes.insert(Sha1ToString(II->Sha1));
+ II->U = U;
+ }
+
bool HasUnit(const Unit &U) { return Hashes.count(Hash(U)); }
bool HasUnit(const std::string &H) { return Hashes.count(H); }
InputInfo &ChooseUnitToMutate(Random &Rand) {
@@ -124,10 +185,14 @@ class InputCorpus {
Printf("\n");
}
- void DeleteInput(size_t Idx) {
- InputInfo &II = *Inputs[Idx];
+ void DeleteFile(const InputInfo &II) {
if (!OutputCorpus.empty() && II.MayDeleteFile)
RemoveFile(DirPlusFile(OutputCorpus, Sha1ToString(II.Sha1)));
+ }
+
+ void DeleteInput(size_t Idx) {
+ InputInfo &II = *Inputs[Idx];
+ DeleteFile(II);
Unit().swap(II.U);
if (FeatureDebug)
Printf("EVICTED %zd\n", Idx);
diff --git a/lib/Fuzzer/FuzzerDriver.cpp b/lib/Fuzzer/FuzzerDriver.cpp
index 0453a7f443b5..87968893853e 100644
--- a/lib/Fuzzer/FuzzerDriver.cpp
+++ b/lib/Fuzzer/FuzzerDriver.cpp
@@ -265,7 +265,7 @@ int RunOneTest(Fuzzer *F, const char *InputFilePath, size_t MaxLen) {
Unit U = FileToVector(InputFilePath);
if (MaxLen && MaxLen < U.size())
U.resize(MaxLen);
- F->RunOne(U.data(), U.size());
+ F->ExecuteCallback(U.data(), U.size());
F->TryDetectingAMemoryLeak(U.data(), U.size(), true);
return 0;
}
@@ -441,7 +441,6 @@ int MinimizeCrashInputInternalStep(Fuzzer *F, InputCorpus *Corpus) {
Printf("INFO: The input is small enough, exiting\n");
exit(0);
}
- Corpus->AddToCorpus(U, 0);
F->SetMaxInputLen(U.size());
F->SetMaxMutationLen(U.size() - 1);
F->MinimizeCrashLoop(U);
@@ -572,6 +571,7 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
Options.UseCmp = Flags.use_cmp;
Options.UseValueProfile = Flags.use_value_profile;
Options.Shrink = Flags.shrink;
+ Options.ReduceInputs = Flags.reduce_inputs;
Options.ShuffleAtStartUp = Flags.shuffle;
Options.PreferSmall = Flags.prefer_small;
Options.ReloadIntervalSec = Flags.reload;
@@ -657,7 +657,7 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
size_t Size = SMR.ReadByteArraySize();
SMR.WriteByteArray(nullptr, 0);
const Unit tmp(SMR.GetByteArray(), SMR.GetByteArray() + Size);
- F->RunOne(tmp.data(), tmp.size());
+ F->ExecuteCallback(tmp.data(), tmp.size());
SMR.PostServer();
}
return 0;
diff --git a/lib/Fuzzer/FuzzerExtFunctionsWeak.cpp b/lib/Fuzzer/FuzzerExtFunctionsWeak.cpp
index 7b02b6f0b701..503f0395cf8f 100644
--- a/lib/Fuzzer/FuzzerExtFunctionsWeak.cpp
+++ b/lib/Fuzzer/FuzzerExtFunctionsWeak.cpp
@@ -41,7 +41,8 @@ namespace fuzzer {
ExternalFunctions::ExternalFunctions() {
#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
this->NAME = ::NAME; \
- CheckFnPtr((void *)::NAME, #NAME, WARN);
+ CheckFnPtr(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(::NAME)), \
+ #NAME, WARN);
#include "FuzzerExtFunctions.def"
diff --git a/lib/Fuzzer/FuzzerFlags.def b/lib/Fuzzer/FuzzerFlags.def
index 7ff196c8fa96..5e70cbad3cf1 100644
--- a/lib/Fuzzer/FuzzerFlags.def
+++ b/lib/Fuzzer/FuzzerFlags.def
@@ -65,7 +65,9 @@ FUZZER_FLAG_INT(use_memmem, 1,
FUZZER_FLAG_INT(use_value_profile, 0,
"Experimental. Use value profile to guide fuzzing.")
FUZZER_FLAG_INT(use_cmp, 1, "Use CMP traces to guide mutations")
-FUZZER_FLAG_INT(shrink, 0, "Experimental. Try to shrink corpus elements.")
+FUZZER_FLAG_INT(shrink, 0, "Experimental. Try to shrink corpus inputs.")
+FUZZER_FLAG_INT(reduce_inputs, 0, "Experimental. "
+ "Try to reduce the size of inputs wile preserving their full feature sets")
FUZZER_FLAG_UNSIGNED(jobs, 0, "Number of jobs to run. If jobs >= 1 we spawn"
" this number of jobs in separate worker processes"
" with stdout/stderr redirected to fuzz-JOB.log.")
diff --git a/lib/Fuzzer/FuzzerIOWindows.cpp b/lib/Fuzzer/FuzzerIOWindows.cpp
index 75d4e3a06071..742520267b73 100644
--- a/lib/Fuzzer/FuzzerIOWindows.cpp
+++ b/lib/Fuzzer/FuzzerIOWindows.cpp
@@ -182,7 +182,7 @@ static size_t ParseFileName(const std::string &FileName, const size_t Offset) {
return Pos - Offset;
}
-// Parse a directory ending in separator, like: SomeDir\
+// Parse a directory ending in separator, like: `SomeDir\`
// Returns number of characters considered if successful.
static size_t ParseDir(const std::string &FileName, const size_t Offset) {
size_t Pos = Offset;
@@ -197,7 +197,7 @@ static size_t ParseDir(const std::string &FileName, const size_t Offset) {
return Pos - Offset;
}
-// Parse a servername and share, like: SomeServer\SomeShare\
+// Parse a servername and share, like: `SomeServer\SomeShare\`
// Returns number of characters considered if successful.
static size_t ParseServerAndShare(const std::string &FileName,
const size_t Offset) {
diff --git a/lib/Fuzzer/FuzzerInternal.h b/lib/Fuzzer/FuzzerInternal.h
index 5f184c2316e2..a732f895375e 100644
--- a/lib/Fuzzer/FuzzerInternal.h
+++ b/lib/Fuzzer/FuzzerInternal.h
@@ -65,7 +65,8 @@ public:
static void StaticFileSizeExceedCallback();
void ExecuteCallback(const uint8_t *Data, size_t Size);
- size_t RunOne(const uint8_t *Data, size_t Size);
+ bool RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile = false,
+ InputInfo *II = nullptr);
// Merge Corpora[1:] into Corpora[0].
void Merge(const std::vector<std::string> &Corpora);
@@ -95,13 +96,12 @@ private:
void InterruptCallback();
void MutateAndTestOne();
void ReportNewCoverage(InputInfo *II, const Unit &U);
- size_t RunOne(const Unit &U) { return RunOne(U.data(), U.size()); }
+ void PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size);
void WriteToOutputCorpus(const Unit &U);
void WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix);
void PrintStats(const char *Where, const char *End = "\n", size_t Units = 0);
void PrintStatusForNewUnit(const Unit &U);
void ShuffleCorpus(UnitVector *V);
- void AddToCorpus(const Unit &U);
void CheckExitOnSrcPosOrItem();
// Trace-based fuzzing: we run a unit with some kind of tracing
@@ -142,6 +142,8 @@ private:
size_t MaxInputLen = 0;
size_t MaxMutationLen = 0;
+ std::vector<uint32_t> FeatureSetTmp;
+
// Need to know our own thread.
static thread_local bool IsMyThread;
};
diff --git a/lib/Fuzzer/FuzzerLoop.cpp b/lib/Fuzzer/FuzzerLoop.cpp
index fbf18357ede6..6816f3af8a6f 100644
--- a/lib/Fuzzer/FuzzerLoop.cpp
+++ b/lib/Fuzzer/FuzzerLoop.cpp
@@ -22,9 +22,6 @@
#include <set>
#if defined(__has_include)
-#if __has_include(<sanitizer / coverage_interface.h>)
-#include <sanitizer/coverage_interface.h>
-#endif
#if __has_include(<sanitizer / lsan_interface.h>)
#include <sanitizer/lsan_interface.h>
#endif
@@ -348,11 +345,8 @@ void Fuzzer::RereadOutputCorpus(size_t MaxSize) {
if (U.size() > MaxSize)
U.resize(MaxSize);
if (!Corpus.HasUnit(U)) {
- if (size_t NumFeatures = RunOne(U)) {
- CheckExitOnSrcPosOrItem();
- Corpus.AddToCorpus(U, NumFeatures);
+ if (RunOne(U.data(), U.size()))
Reloaded = true;
- }
}
}
if (Reloaded)
@@ -377,10 +371,7 @@ void Fuzzer::ShuffleAndMinimize(UnitVector *InitialCorpus) {
ExecuteCallback(&dummy, 0);
for (const auto &U : *InitialCorpus) {
- if (size_t NumFeatures = RunOne(U)) {
- CheckExitOnSrcPosOrItem();
- Corpus.AddToCorpus(U, NumFeatures);
- }
+ RunOne(U.data(), U.size());
TryDetectingAMemoryLeak(U.data(), U.size(),
/*DuringInitialCorpusExecution*/ true);
}
@@ -392,18 +383,7 @@ void Fuzzer::ShuffleAndMinimize(UnitVector *InitialCorpus) {
}
}
-size_t Fuzzer::RunOne(const uint8_t *Data, size_t Size) {
- if (!Size) return 0;
- TotalNumberOfRuns++;
-
- ExecuteCallback(Data, Size);
-
- size_t NumUpdatesBefore = Corpus.NumFeatureUpdates();
- TPC.CollectFeatures([&](size_t Feature) {
- Corpus.AddFeature(Feature, Size, Options.Shrink);
- });
- size_t NumUpdatesAfter = Corpus.NumFeatureUpdates();
-
+void Fuzzer::PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size) {
auto TimeOfUnit =
duration_cast<seconds>(UnitStopTime - UnitStartTime).count();
if (!(TotalNumberOfRuns & (TotalNumberOfRuns - 1)) &&
@@ -415,7 +395,34 @@ size_t Fuzzer::RunOne(const uint8_t *Data, size_t Size) {
Printf("Slowest unit: %zd s:\n", TimeOfLongestUnitInSeconds);
WriteUnitToFileWithPrefix({Data, Data + Size}, "slow-unit-");
}
- return NumUpdatesAfter - NumUpdatesBefore;
+}
+
+bool Fuzzer::RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile,
+ InputInfo *II) {
+ if (!Size) return false;
+
+ ExecuteCallback(Data, Size);
+
+ FeatureSetTmp.clear();
+ size_t NumUpdatesBefore = Corpus.NumFeatureUpdates();
+ TPC.CollectFeatures([&](size_t Feature) {
+ Corpus.AddFeature(Feature, Size, Options.Shrink);
+ if (Options.ReduceInputs)
+ FeatureSetTmp.push_back(Feature);
+ });
+ PrintPulseAndReportSlowInput(Data, Size);
+ size_t NumNewFeatures = Corpus.NumFeatureUpdates() - NumUpdatesBefore;
+ if (NumNewFeatures) {
+ Corpus.AddToCorpus({Data, Data + Size}, NumNewFeatures, MayDeleteFile,
+ FeatureSetTmp);
+ CheckExitOnSrcPosOrItem();
+ return true;
+ }
+ if (II && Corpus.TryToReplace(II, Data, Size, FeatureSetTmp)) {
+ CheckExitOnSrcPosOrItem();
+ return true;
+ }
+ return false;
}
size_t Fuzzer::GetCurrentUnitInFuzzingThead(const uint8_t **Data) const {
@@ -443,6 +450,7 @@ static bool LooseMemeq(const uint8_t *A, const uint8_t *B, size_t Size) {
}
void Fuzzer::ExecuteCallback(const uint8_t *Data, size_t Size) {
+ TotalNumberOfRuns++;
assert(InFuzzingThread());
if (SMR.IsClient())
SMR.WriteByteArray(Data, Size);
@@ -595,12 +603,9 @@ void Fuzzer::MutateAndTestOne() {
if (i == 0)
StartTraceRecording();
II.NumExecutedMutations++;
- if (size_t NumFeatures = RunOne(CurrentUnitData, Size)) {
- Corpus.AddToCorpus({CurrentUnitData, CurrentUnitData + Size}, NumFeatures,
- /*MayDeleteFile=*/true);
+ if (RunOne(CurrentUnitData, Size, /*MayDeleteFile=*/true, &II))
ReportNewCoverage(&II, {CurrentUnitData, CurrentUnitData + Size});
- CheckExitOnSrcPosOrItem();
- }
+
StopTraceRecording();
TryDetectingAMemoryLeak(CurrentUnitData, Size,
/*DuringInitialCorpusExecution*/ false);
@@ -638,7 +643,8 @@ void Fuzzer::MinimizeCrashLoop(const Unit &U) {
for (int i = 0; i < Options.MutateDepth; i++) {
size_t NewSize = MD.Mutate(CurrentUnitData, U.size(), MaxMutationLen);
assert(NewSize > 0 && NewSize <= MaxMutationLen);
- RunOne(CurrentUnitData, NewSize);
+ ExecuteCallback(CurrentUnitData, NewSize);
+ PrintPulseAndReportSlowInput(CurrentUnitData, NewSize);
TryDetectingAMemoryLeak(CurrentUnitData, NewSize,
/*DuringInitialCorpusExecution*/ false);
}
diff --git a/lib/Fuzzer/FuzzerOptions.h b/lib/Fuzzer/FuzzerOptions.h
index b1366789be00..9500235e2b1f 100644
--- a/lib/Fuzzer/FuzzerOptions.h
+++ b/lib/Fuzzer/FuzzerOptions.h
@@ -32,6 +32,7 @@ struct FuzzingOptions {
bool UseCmp = false;
bool UseValueProfile = false;
bool Shrink = false;
+ bool ReduceInputs = false;
int ReloadIntervalSec = 1;
bool ShuffleAtStartUp = true;
bool PreferSmall = true;
diff --git a/lib/Fuzzer/FuzzerUtilDarwin.cpp b/lib/Fuzzer/FuzzerUtilDarwin.cpp
index 9674368c355e..2df4872a9206 100644
--- a/lib/Fuzzer/FuzzerUtilDarwin.cpp
+++ b/lib/Fuzzer/FuzzerUtilDarwin.cpp
@@ -15,6 +15,8 @@
#include <mutex>
#include <signal.h>
#include <spawn.h>
+#include <stdlib.h>
+#include <string.h>
#include <sys/wait.h>
// There is no header for this on macOS so declare here
@@ -97,11 +99,16 @@ int ExecuteCommand(const std::string &Command) {
pid_t Pid;
char **Environ = environ; // Read from global
const char *CommandCStr = Command.c_str();
- const char *Argv[] = {"sh", "-c", CommandCStr, NULL};
+ char *const Argv[] = {
+ strdup("sh"),
+ strdup("-c"),
+ strdup(CommandCStr),
+ NULL
+ };
int ErrorCode = 0, ProcessStatus = 0;
// FIXME: We probably shouldn't hardcode the shell path.
ErrorCode = posix_spawn(&Pid, "/bin/sh", NULL, &SpawnAttributes,
- (char *const *)Argv, Environ);
+ Argv, Environ);
(void)posix_spawnattr_destroy(&SpawnAttributes);
if (!ErrorCode) {
pid_t SavedPid = Pid;
@@ -120,6 +127,8 @@ int ExecuteCommand(const std::string &Command) {
// Shell execution failure.
ProcessStatus = W_EXITCODE(127, 0);
}
+ for (unsigned i = 0, n = sizeof(Argv) / sizeof(Argv[0]); i < n; ++i)
+ free(Argv[i]);
// Restore the signal handlers of the current process when the last thread
// using this function finishes.
diff --git a/lib/Fuzzer/test/CMakeLists.txt b/lib/Fuzzer/test/CMakeLists.txt
index 1cf6c9502a2b..30566bdc87ae 100644
--- a/lib/Fuzzer/test/CMakeLists.txt
+++ b/lib/Fuzzer/test/CMakeLists.txt
@@ -118,6 +118,7 @@ set(Tests
SingleStrncmpTest
SpamyTest
ShrinkControlFlowTest
+ ShrinkControlFlowSimpleTest
ShrinkValueProfileTest
StrcmpTest
StrncmpOOBTest
@@ -271,5 +272,5 @@ add_lit_testsuite(check-fuzzer "Running Fuzzer tests"
# Don't add dependencies on Windows. The linker step would fail on Windows,
# since cmake will use link.exe for linking and won't include compiler-rt libs.
if(NOT MSVC)
- add_dependencies(check-fuzzer FileCheck sancov not)
+ add_dependencies(check-fuzzer FileCheck sancov not llvm-symbolizer)
endif()
diff --git a/lib/Fuzzer/test/FuzzerUnittest.cpp b/lib/Fuzzer/test/FuzzerUnittest.cpp
index 812894fd947f..1053c28527bf 100644
--- a/lib/Fuzzer/test/FuzzerUnittest.cpp
+++ b/lib/Fuzzer/test/FuzzerUnittest.cpp
@@ -5,6 +5,9 @@
// with ASan) involving C++ standard library types when using libcxx.
#define _LIBCPP_HAS_NO_ASAN
+// Do not attempt to use LLVM ostream from gtest.
+#define GTEST_NO_LLVM_RAW_OSTREAM 1
+
#include "FuzzerCorpus.h"
#include "FuzzerDictionary.h"
#include "FuzzerInternal.h"
@@ -590,7 +593,7 @@ TEST(Corpus, Distribution) {
size_t N = 10;
size_t TriesPerUnit = 1<<16;
for (size_t i = 0; i < N; i++)
- C->AddToCorpus(Unit{ static_cast<uint8_t>(i) }, 0);
+ C->AddToCorpus(Unit{ static_cast<uint8_t>(i) }, 0, false, {});
std::vector<size_t> Hist(N);
for (size_t i = 0; i < N * TriesPerUnit; i++) {
diff --git a/lib/Fuzzer/test/ShrinkControlFlowSimpleTest.cpp b/lib/Fuzzer/test/ShrinkControlFlowSimpleTest.cpp
new file mode 100644
index 000000000000..0afd26df23a0
--- /dev/null
+++ b/lib/Fuzzer/test/ShrinkControlFlowSimpleTest.cpp
@@ -0,0 +1,19 @@
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+
+// Test that we can find the minimal item in the corpus (3 bytes: "FUZ").
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+static volatile int Sink;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+ if (Size < 2) return 0;
+ if (Data[0] == 'F' && Data[Size / 2] == 'U' && Data[Size - 1] == 'Z')
+ Sink++;
+ return 0;
+}
+
diff --git a/lib/Fuzzer/test/reduce_inputs.test b/lib/Fuzzer/test/reduce_inputs.test
new file mode 100644
index 000000000000..a4a5c57123d3
--- /dev/null
+++ b/lib/Fuzzer/test/reduce_inputs.test
@@ -0,0 +1,13 @@
+# Test -reduce_inputs=1
+
+RUN: rm -rf %t/C
+RUN: mkdir -p %t/C
+RUN: LLVMFuzzer-ShrinkControlFlowSimpleTest -exit_on_item=0eb8e4ed029b774d80f2b66408203801cb982a60 -reduce_inputs=1 -runs=1000000 %t/C 2>&1 | FileCheck %s
+CHECK: INFO: found item with checksum '0eb8e4ed029b774d80f2b66408203801cb982a60'
+
+# Test that reduce_inputs deletes redundant files in the corpus.
+RUN: LLVMFuzzer-ShrinkControlFlowSimpleTest -runs=0 %t/C 2>&1 | FileCheck %s --check-prefix=COUNT
+COUNT: READ units: 3
+
+
+