Index: lib/fuzzer/FuzzerDriver.cpp =================================================================== --- lib/fuzzer/FuzzerDriver.cpp +++ lib/fuzzer/FuzzerDriver.cpp @@ -620,7 +620,8 @@ Options.PrintCoverage = Flags.print_coverage; Options.PrintUnstableStats = Flags.print_unstable_stats; if (Flags.handle_unstable == TracePC::MinUnstable || - Flags.handle_unstable == TracePC::ZeroUnstable) + Flags.handle_unstable == TracePC::ZeroUnstable || + Flags.handle_unstable == TracePC::PoisonUnstable) Options.HandleUnstable = Flags.handle_unstable; Options.DumpCoverage = Flags.dump_coverage; if (Flags.exit_on_src_pos) Index: lib/fuzzer/FuzzerInternal.h =================================================================== --- lib/fuzzer/FuzzerInternal.h +++ lib/fuzzer/FuzzerInternal.h @@ -67,7 +67,7 @@ static void StaticGracefulExitCallback(); void ExecuteCallback(const uint8_t *Data, size_t Size); - void CheckForUnstableCounters(const uint8_t *Data, size_t Size); + int CheckForUnstableCounters(const uint8_t *Data, size_t Size); bool RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile = false, InputInfo *II = nullptr, bool *FoundUniqFeatures = nullptr); Index: lib/fuzzer/FuzzerLoop.cpp =================================================================== --- lib/fuzzer/FuzzerLoop.cpp +++ lib/fuzzer/FuzzerLoop.cpp @@ -449,7 +449,7 @@ } } -void Fuzzer::CheckForUnstableCounters(const uint8_t *Data, size_t Size) { +int Fuzzer::CheckForUnstableCounters(const uint8_t *Data, size_t Size) { auto CBSetupAndRun = [&]() { ScopedEnableMsanInterceptorChecks S; UnitStartTime = system_clock::now(); @@ -465,16 +465,18 @@ // First Rerun CBSetupAndRun(); - TPC.UpdateUnstableCounters(Options.HandleUnstable); + int NumNewUnstableEdges = TPC.UpdateUnstableCounters(Options.HandleUnstable); // Second Rerun CBSetupAndRun(); - TPC.UpdateUnstableCounters(Options.HandleUnstable); + NumNewUnstableEdges += TPC.UpdateUnstableCounters(Options.HandleUnstable); // Move minimum hit counts back to ModuleInline8bitCounters if (Options.HandleUnstable == TracePC::MinUnstable || Options.HandleUnstable == TracePC::ZeroUnstable) TPC.ApplyUnstableCounters(); + + return NumNewUnstableEdges; } bool Fuzzer::RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile, @@ -488,6 +490,7 @@ size_t FoundUniqFeaturesOfII = 0; size_t NumUpdatesBefore = Corpus.NumFeatureUpdates(); bool NewFeaturesUnstable = false; + int NumNewUnstableEdges = 0; if (Options.HandleUnstable || Options.PrintUnstableStats) { TPC.CollectFeatures([&](size_t Feature) { @@ -495,7 +498,7 @@ NewFeaturesUnstable = true; }); if (NewFeaturesUnstable) - CheckForUnstableCounters(Data, Size); + NumNewUnstableEdges = CheckForUnstableCounters(Data, Size); } TPC.CollectFeatures([&](size_t Feature) { @@ -512,6 +515,11 @@ PrintPulseAndReportSlowInput(Data, Size); size_t NumNewFeatures = Corpus.NumFeatureUpdates() - NumUpdatesBefore; + if (Options.HandleUnstable == TPC.PoisonUnstable) { + assert(NumNewFeatures >= NumNewUnstableEdges); + NumNewFeatures -= NumNewUnstableEdges; + } + if (NumNewFeatures) { TPC.UpdateObservedPCs(); Corpus.AddToCorpus({Data, Data + Size}, NumNewFeatures, MayDeleteFile, Index: lib/fuzzer/FuzzerTracePC.h =================================================================== --- lib/fuzzer/FuzzerTracePC.h +++ lib/fuzzer/FuzzerTracePC.h @@ -76,6 +76,7 @@ enum HandleUnstableOptions { MinUnstable = 1, ZeroUnstable = 2, + PoisonUnstable = 3, }; void HandleInit(uint32_t *Start, uint32_t *Stop); @@ -142,7 +143,7 @@ bool ObservedFocusFunction(); void InitializeUnstableCounters(); - void UpdateUnstableCounters(int UnstableMode); + int UpdateUnstableCounters(int UnstableMode); void ApplyUnstableCounters(); private: Index: lib/fuzzer/FuzzerTracePC.cpp =================================================================== --- lib/fuzzer/FuzzerTracePC.cpp +++ lib/fuzzer/FuzzerTracePC.cpp @@ -81,18 +81,21 @@ // Compares the current counters with counters from previous runs // and records differences as unstable edges. -void TracePC::UpdateUnstableCounters(int UnstableMode) { +int TracePC::UpdateUnstableCounters(int UnstableMode) { + int NumNewUnstableEdges = 0; IterateInline8bitCounters([&](int i, int j, int UnstableIdx) { if (ModuleCounters[i].Start[j] != UnstableCounters[UnstableIdx].Counter) { + if (!UnstableCounters[UnstableIdx].IsUnstable) + NumNewUnstableEdges++; UnstableCounters[UnstableIdx].IsUnstable = true; if (UnstableMode == ZeroUnstable) UnstableCounters[UnstableIdx].Counter = 0; - else if (UnstableMode == MinUnstable) { + else if (UnstableMode == MinUnstable) UnstableCounters[UnstableIdx].Counter = std::min( ModuleCounters[i].Start[j], UnstableCounters[UnstableIdx].Counter); - } } }); + return NumNewUnstableEdges; } // Moves the minimum hit counts to ModuleCounters. Index: test/fuzzer/HandleUnstableTest.cpp =================================================================== --- /dev/null +++ test/fuzzer/HandleUnstableTest.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include + +int x = 0; +int c = 0; +bool skip0 = false; +bool skip1 = false; +bool skip2 = false; + +__attribute__((noinline)) void det0() { x++; } +__attribute__((noinline)) void det1() { x++; } +__attribute__((noinline)) void det2() { x++; } + +__attribute__((noinline)) void ini0() { x++; } +__attribute__((noinline)) void ini1() { x++; } +__attribute__((noinline)) void ini2() { x++; } + +__attribute__((noinline)) void t0() { x++; } +__attribute__((noinline)) void t1() { x++; } +__attribute__((noinline)) void t2() { x++; } + +__attribute__((noinline)) void poison0() { x++; } +__attribute__((noinline)) void poison1() { x++; } +__attribute__((noinline)) void poison2() { x++; } + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size == 1 && Data[0] == 'A' && !skip0) { + skip0 = true; + ini0(); + } + if (Size == 1 && Data[0] == 'B' && !skip1) { + skip1 = true; + ini1(); + } + if (Size == 1 && Data[0] == 'C' && !skip2) { + skip2 = true; + ini2(); + } + + det0(); + det1(); + det2(); + + if (c < 1000) { + switch (c % 3) { + case 0: + t0(); + poison0(); + printf("0"); + break; + case 1: + t1(); + poison1(); + printf("1"); + break; + case 2: + t2(); + poison2(); + printf("2"); + break; + default: + assert(false); + } + } else { + poison0(); + poison1(); + poison2(); + } + c++; + + return 0; +} Index: test/fuzzer/handle-unstable.test =================================================================== --- test/fuzzer/handle-unstable.test +++ test/fuzzer/handle-unstable.test @@ -1,39 +1,59 @@ -RUN: %cpp_compiler %S/PrintUnstableStatsTest.cpp -o %t-HandleUnstableTest +RUN: %cpp_compiler %S/HandleUnstableTest.cpp -o %t-HandleUnstableTest ; Normal RUN: %run %t-HandleUnstableTest -print_coverage=1 -runs=100000 2>&1 | FileCheck %s --check-prefix=NORMAL NORMAL-DAG: det0() NORMAL-DAG: det1() NORMAL-DAG: det2() -NORMAL-DAG: det3() -NORMAL-DAG: det4() NORMAL-DAG: ini0() NORMAL-DAG: ini1() NORMAL-DAG: ini2() NORMAL-DAG: t0() NORMAL-DAG: t1() NORMAL-DAG: t2() -NORMAL-DAG: t3() -NORMAL-DAG: t4() ; MinUnstable RUN: %run %t-HandleUnstableTest -print_coverage=1 -handle_unstable=1 -runs=100000 2>&1 | FileCheck %s --check-prefix=MIN -MIN-NOT: ini0() -MIN-NOT: ini1() -MIN-NOT: ini2() MIN: det0() MIN: det1() MIN: det2() -MIN: det3() -MIN: det4() +MIN: poison0() +MIN: poison1() +MIN: poison2() +MIN-NOT: ini0() +MIN-NOT: ini1() +MIN-NOT: ini2() +MIN-NOT: t0() +MIN-NOT: t1() +MIN-NOT: t2() ; ZeroUnstable -RUN: %run %t-HandleUnstableTest -print_coverage=1 -handle_unstable=2 -runs=1 2>&1 | FileCheck %s --check-prefix=ZERO -ZERO-NOT: ini0() -ZERO-NOT: ini1() -ZERO-NOT: ini2() +RUN: %run %t-HandleUnstableTest -print_coverage=1 -handle_unstable=2 -runs=100000 2>&1 | FileCheck %s --check-prefix=ZERO ZERO: det0() ZERO: det1() ZERO: det2() -ZERO: det3() -ZERO: det4() +ZERO: poison0() +ZERO: poison1() +ZERO: poison2() +ZERO-NOT: ini0() +ZERO-NOT: ini1() +ZERO-NOT: ini2() +ZERO-NOT: t0() +ZERO-NOT: t1() +ZERO-NOT: t2() + +; PoisonUnstable +RUN: %run %t-HandleUnstableTest -print_coverage=1 -handle_unstable=3 -runs=100000 2>&1 | FileCheck %s --check-prefix=POISON +POISON: det0() +POISON: det1() +POISON: det2() +; t0 is triggered because last iteration hits poison0() and it's next iterations always hit poison0() causing the input to collect t0() as well. +POISON: t0() +POISON: poison0() +POISON: poison1() +POISON: poison2() +POISON-NOT: {{\b}}t1() +POISON-NOT: {{\b}}t2() +POISON-NOT: ini0() +POISON-NOT: ini1() +POISON-NOT: ini2() Index: test/fuzzer/handle_unstable_minunstable.test =================================================================== --- test/fuzzer/handle_unstable_minunstable.test +++ /dev/null @@ -1,12 +0,0 @@ -RUN: %cpp_compiler %S/PrintUnstableStatsTest.cpp -o %t-HandleUnstableMinUnstableTest -RUN: %run %t-HandleUnstableMinUnstableTest -print_coverage=1 -handle_unstable=1 -runs=100000 2>&1 | FileCheck %s --check-prefix=UNSTABLE -UNSTABLE-NOT: ini0() -UNSTABLE-NOT: ini1() -UNSTABLE-NOT: ini2() - -RUN: %run %t-HandleUnstableMinUnstableTest -print_coverage=1 -runs=100000 2>&1 | FileCheck %s --check-prefix=NORMAL -NORMAL-DAG: ini0() -NORMAL-DAG: ini1() -NORMAL-DAG: ini2() - -