commit ca00020372841cf2b3ae8774b1d0f8facfef3d1a
Author: Jesse Schwartzentruber <truber@mozilla.com>
Date:   Mon Mar 1 15:47:38 2021 -0500

    [libfuzzer] In most cases, return instead of exit().

diff --git a/FuzzerDataFlowTrace.cpp b/FuzzerDataFlowTrace.cpp
index c9210c78a063..61fdde855539 100644
--- a/FuzzerDataFlowTrace.cpp
+++ b/FuzzerDataFlowTrace.cpp
@@ -103,9 +103,11 @@ std::vector<double> BlockCoverage::FunctionWeights(size_t NumFunctions) const {
   return Res;
 }
 
-void DataFlowTrace::ReadCoverage(const std::string &DirPath) {
+int DataFlowTrace::ReadCoverage(const std::string &DirPath) {
   std::vector<SizedFile> Files;
-  GetSizedFilesFromDir(DirPath, &Files);
+  int Res = GetSizedFilesFromDir(DirPath, &Files);
+  if (Res != 0)
+    return Res;
   for (auto &SF : Files) {
     auto Name = Basename(SF.File);
     if (Name == kFunctionsTxt) continue;
@@ -113,6 +115,7 @@ void DataFlowTrace::ReadCoverage(const std::string &DirPath) {
     std::ifstream IF(SF.File);
     Coverage.AppendCoverage(IF);
   }
+  return 0;
 }
 
 static void DFTStringAppendToVector(std::vector<uint8_t> *DFT,
@@ -158,12 +161,14 @@ static bool ParseDFTLine(const std::string &Line, size_t *FunctionNum,
   return true;
 }
 
-bool DataFlowTrace::Init(const std::string &DirPath, std::string *FocusFunction,
+int DataFlowTrace::Init(const std::string &DirPath, std::string *FocusFunction,
                          std::vector<SizedFile> &CorporaFiles, Random &Rand) {
-  if (DirPath.empty()) return false;
+  if (DirPath.empty()) return 0;
   Printf("INFO: DataFlowTrace: reading from '%s'\n", DirPath.c_str());
   std::vector<SizedFile> Files;
-  GetSizedFilesFromDir(DirPath, &Files);
+  int Res = GetSizedFilesFromDir(DirPath, &Files);
+  if (Res != 0)
+    return Res;
   std::string L;
   size_t FocusFuncIdx = SIZE_MAX;
   std::vector<std::string> FunctionNames;
@@ -182,14 +187,16 @@ bool DataFlowTrace::Init(const std::string &DirPath, std::string *FocusFunction,
       FocusFuncIdx = NumFunctions - 1;
   }
   if (!NumFunctions)
-    return false;
+    return 0;
 
   if (*FocusFunction == "auto") {
     // AUTOFOCUS works like this:
     // * reads the coverage data from the DFT files.
     // * assigns weights to functions based on coverage.
     // * chooses a random function according to the weights.
-    ReadCoverage(DirPath);
+    Res = ReadCoverage(DirPath);
+    if (Res != 0)
+      return Res;
     auto Weights = Coverage.FunctionWeights(NumFunctions);
     std::vector<double> Intervals(NumFunctions + 1);
     std::iota(Intervals.begin(), Intervals.end(), 0);
@@ -211,7 +218,7 @@ bool DataFlowTrace::Init(const std::string &DirPath, std::string *FocusFunction,
   }
 
   if (!NumFunctions || FocusFuncIdx == SIZE_MAX || Files.size() <= 1)
-    return false;
+    return 0;
 
   // Read traces.
   size_t NumTraceFiles = 0;
@@ -230,8 +237,10 @@ bool DataFlowTrace::Init(const std::string &DirPath, std::string *FocusFunction,
           FunctionNum == FocusFuncIdx) {
         NumTracesWithFocusFunction++;
 
-        if (FunctionNum >= NumFunctions)
-          return ParseError("N is greater than the number of functions", L);
+        if (FunctionNum >= NumFunctions) {
+          ParseError("N is greater than the number of functions", L);
+          return 0;
+        }
         Traces[Name] = DFTStringToVector(DFTString);
         // Print just a few small traces.
         if (NumTracesWithFocusFunction <= 3 && DFTString.size() <= 16)
@@ -243,7 +252,7 @@ bool DataFlowTrace::Init(const std::string &DirPath, std::string *FocusFunction,
   Printf("INFO: DataFlowTrace: %zd trace files, %zd functions, "
          "%zd traces with focus function\n",
          NumTraceFiles, NumFunctions, NumTracesWithFocusFunction);
-  return NumTraceFiles > 0;
+  return 0;
 }
 
 int CollectDataFlow(const std::string &DFTBinary, const std::string &DirPath,
diff --git a/FuzzerDataFlowTrace.h b/FuzzerDataFlowTrace.h
index 054dce1bdcb6..4f0e23cfd340 100644
--- a/FuzzerDataFlowTrace.h
+++ b/FuzzerDataFlowTrace.h
@@ -115,8 +115,8 @@ private:
 
 class DataFlowTrace {
  public:
-  void ReadCoverage(const std::string &DirPath);
-  bool Init(const std::string &DirPath, std::string *FocusFunction,
+  int ReadCoverage(const std::string &DirPath);
+  int Init(const std::string &DirPath, std::string *FocusFunction,
             std::vector<SizedFile> &CorporaFiles, Random &Rand);
   void Clear() { Traces.clear(); }
   const std::vector<uint8_t> *Get(const std::string &InputSha1) const {
diff --git a/FuzzerDriver.cpp b/FuzzerDriver.cpp
index ff43cb031dff..93d576757eab 100644
--- a/FuzzerDriver.cpp
+++ b/FuzzerDriver.cpp
@@ -365,7 +365,7 @@ int CleanseCrashInput(const std::vector<std::string> &Args,
   if (Inputs->size() != 1 || !Flags.exact_artifact_path) {
     Printf("ERROR: -cleanse_crash should be given one input file and"
           " -exact_artifact_path\n");
-    exit(1);
+    return 1;
   }
   std::string InputFilePath = Inputs->at(0);
   std::string OutputFilePath = Flags.exact_artifact_path;
@@ -419,7 +419,7 @@ int MinimizeCrashInput(const std::vector<std::string> &Args,
                        const FuzzingOptions &Options) {
   if (Inputs->size() != 1) {
     Printf("ERROR: -minimize_crash should be given one input file\n");
-    exit(1);
+    return 1;
   }
   std::string InputFilePath = Inputs->at(0);
   Command BaseCmd(Args);
@@ -450,7 +450,7 @@ int MinimizeCrashInput(const std::vector<std::string> &Args,
     bool Success = ExecuteCommand(Cmd, &CmdOutput);
     if (Success) {
       Printf("ERROR: the input %s did not crash\n", CurrentFilePath.c_str());
-      exit(1);
+      return 1;
     }
     Printf("CRASH_MIN: '%s' (%zd bytes) caused a crash. Will try to minimize "
            "it further\n",
@@ -505,42 +505,53 @@ int MinimizeCrashInputInternalStep(Fuzzer *F, InputCorpus *Corpus) {
   Printf("INFO: Starting MinimizeCrashInputInternalStep: %zd\n", U.size());
   if (U.size() < 2) {
     Printf("INFO: The input is small enough, exiting\n");
-    exit(0);
+    return 0;
   }
   F->SetMaxInputLen(U.size());
   F->SetMaxMutationLen(U.size() - 1);
   F->MinimizeCrashLoop(U);
   Printf("INFO: Done MinimizeCrashInputInternalStep, no crashes found\n");
-  exit(0);
+  return 0;
 }
 
-void Merge(Fuzzer *F, FuzzingOptions &Options,
+int Merge(Fuzzer *F, FuzzingOptions &Options,
            const std::vector<std::string> &Args,
            const std::vector<std::string> &Corpora, const char *CFPathOrNull) {
   if (Corpora.size() < 2) {
     Printf("INFO: Merge requires two or more corpus dirs\n");
-    exit(0);
+    return 0;
   }
 
   std::vector<SizedFile> OldCorpus, NewCorpus;
-  GetSizedFilesFromDir(Corpora[0], &OldCorpus);
-  for (size_t i = 1; i < Corpora.size(); i++)
-    GetSizedFilesFromDir(Corpora[i], &NewCorpus);
+  int Res = GetSizedFilesFromDir(Corpora[0], &OldCorpus);
+  if (Res != 0)
+    return Res;
+  for (size_t i = 1; i < Corpora.size(); i++) {
+    Res = GetSizedFilesFromDir(Corpora[i], &NewCorpus);
+    if (Res != 0)
+      return Res;
+  }
   std::sort(OldCorpus.begin(), OldCorpus.end());
   std::sort(NewCorpus.begin(), NewCorpus.end());
 
   std::string CFPath = CFPathOrNull ? CFPathOrNull : TempPath("Merge", ".txt");
   std::vector<std::string> NewFiles;
   std::set<uint32_t> NewFeatures, NewCov;
-  CrashResistantMerge(Args, OldCorpus, NewCorpus, &NewFiles, {}, &NewFeatures,
+  Res = CrashResistantMerge(Args, OldCorpus, NewCorpus, &NewFiles, {}, &NewFeatures,
                       {}, &NewCov, CFPath, true, Flags.set_cover_merge);
+  if (Res != 0)
+    return Res;
+
+  if (F->isGracefulExitRequested())
+    return 0;
+
   for (auto &Path : NewFiles)
     F->WriteToOutputCorpus(FileToVector(Path, Options.MaxLen));
   // We are done, delete the control file if it was a temporary one.
   if (!Flags.merge_control_file)
     RemoveFile(CFPath);
 
-  exit(0);
+  return 0;
 }
 
 int AnalyzeDictionary(Fuzzer *F, const std::vector<Unit> &Dict,
@@ -620,7 +631,7 @@ std::vector<std::string> ParseSeedInputs(const char *seed_inputs) {
     SeedInputs = Flags.seed_inputs; // seed_inputs contains the list.
   if (SeedInputs.empty()) {
     Printf("seed_inputs is empty or @file does not exist.\n");
-    exit(1);
+    return Files;
   }
   // Parse SeedInputs.
   size_t comma_pos = 0;
@@ -664,7 +675,7 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
   ProgName = new std::string(Args[0]);
   if (Argv0 != *ProgName) {
     Printf("ERROR: argv[0] has been modified in LLVMFuzzerInitialize\n");
-    exit(1);
+    return 1;
   }
   ParseFlags(Args, EF);
   if (Flags.help) {
@@ -879,24 +890,23 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
            "***       executed the target code on a fixed set of inputs.\n"
            "***\n");
     F->PrintFinalStats();
-    exit(0);
+    return 0;
   }
 
   Options.ForkCorpusGroups = Flags.fork_corpus_groups;
   if (Flags.fork)
-    FuzzWithFork(F->GetMD().GetRand(), Options, Args, *Inputs, Flags.fork);
+    return FuzzWithFork(F->GetMD().GetRand(), Options, Args, *Inputs, Flags.fork);
 
   if (Flags.merge || Flags.set_cover_merge)
-    Merge(F, Options, Args, *Inputs, Flags.merge_control_file);
+    return Merge(F, Options, Args, *Inputs, Flags.merge_control_file);
 
   if (Flags.merge_inner) {
     const size_t kDefaultMaxMergeLen = 1 << 20;
     if (Options.MaxLen == 0)
       F->SetMaxInputLen(kDefaultMaxMergeLen);
     assert(Flags.merge_control_file);
-    F->CrashResistantMergeInternalStep(Flags.merge_control_file,
+    return F->CrashResistantMergeInternalStep(Flags.merge_control_file,
                                        !strncmp(Flags.merge_inner, "2", 1));
-    exit(0);
   }
 
   if (Flags.analyze_dict) {
@@ -914,21 +924,24 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
     }
     if (AnalyzeDictionary(F, Dictionary, InitialCorpus)) {
       Printf("Dictionary analysis failed\n");
-      exit(1);
+      return 1;
     }
     Printf("Dictionary analysis succeeded\n");
-    exit(0);
+    return 0;
   }
 
   auto CorporaFiles = ReadCorpora(*Inputs, ParseSeedInputs(Flags.seed_inputs));
-  F->Loop(CorporaFiles);
+  int Res = F->Loop(CorporaFiles);
+  if (Res != 0) return Res;
+  if (F->isGracefulExitRequested())
+    return 0;
 
   if (Flags.verbosity)
     Printf("Done %zd runs in %zd second(s)\n", F->getTotalNumberOfRuns(),
            F->secondsSinceProcessStartUp());
   F->PrintFinalStats();
 
-  exit(0);  // Don't let F destroy itself.
+  return 0;  // Don't let F destroy itself.
 }
 
 extern "C" ATTRIBUTE_INTERFACE int
diff --git a/FuzzerFork.cpp b/FuzzerFork.cpp
index e544cd846e4d..33900a9b84c4 100644
--- a/FuzzerFork.cpp
+++ b/FuzzerFork.cpp
@@ -196,14 +196,16 @@ struct GlobalEnv {
     return Job;
   }
 
-  void RunOneMergeJob(FuzzJob *Job) {
+  int RunOneMergeJob(FuzzJob *Job) {
     auto Stats = ParseFinalStatsFromLog(Job->LogPath);
     NumRuns += Stats.number_of_executed_units;
 
     std::vector<SizedFile> TempFiles, MergeCandidates;
     // Read all newly created inputs and their feature sets.
     // Choose only those inputs that have new features.
-    GetSizedFilesFromDir(Job->CorpusDir, &TempFiles);
+    int Res = GetSizedFilesFromDir(Job->CorpusDir, &TempFiles);
+    if (Res != 0)
+      return Res;
     std::sort(TempFiles.begin(), TempFiles.end());
     for (auto &F : TempFiles) {
       auto FeatureFile = F.File;
@@ -226,7 +228,7 @@ struct GlobalEnv {
            Stats.average_exec_per_sec, NumOOMs, NumTimeouts, NumCrashes,
            secondsSinceProcessStartUp(), Job->JobId, Job->DftTimeInSeconds);
 
-    if (MergeCandidates.empty()) return;
+    if (MergeCandidates.empty()) return 0;
 
     std::vector<std::string> FilesToAdd;
     std::set<uint32_t> NewFeatures, NewCov;
@@ -235,6 +237,8 @@ struct GlobalEnv {
     CrashResistantMerge(Args, {}, MergeCandidates, &FilesToAdd, Features,
                         &NewFeatures, Cov, &NewCov, Job->CFPath, false,
                         IsSetCoverMerge);
+    if (Fuzzer::isGracefulExitRequested())
+      return 0;
     for (auto &Path : FilesToAdd) {
       auto U = FileToVector(Path);
       auto NewPath = DirPlusFile(MainCorpusDir, Hash(U));
@@ -257,6 +261,7 @@ struct GlobalEnv {
         if (TPC.PcIsFuncEntry(TE))
           PrintPC("  NEW_FUNC: %p %F %L\n", "",
                   TPC.GetNextInstructionPc(TE->PC));
+    return 0;
   }
 
   void CollectDFT(const std::string &InputPath) {
@@ -309,7 +314,7 @@ void WorkerThread(JobQueue *FuzzQ, JobQueue *MergeQ) {
 }
 
 // This is just a skeleton of an experimental -fork=1 feature.
-void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
+int FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
                   const std::vector<std::string> &Args,
                   const std::vector<std::string> &CorpusDirs, int NumJobs) {
   Printf("INFO: -fork=%d: fuzzing in separate process(s)\n", NumJobs);
@@ -324,8 +329,12 @@ void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
   Env.Group = Options.ForkCorpusGroups;
 
   std::vector<SizedFile> SeedFiles;
-  for (auto &Dir : CorpusDirs)
-    GetSizedFilesFromDir(Dir, &SeedFiles);
+  int Res;
+  for (auto &Dir : CorpusDirs) {
+    Res = GetSizedFilesFromDir(Dir, &SeedFiles);
+    if (Res != 0)
+      return Res;
+  }
   std::sort(SeedFiles.begin(), SeedFiles.end());
   Env.TempDir = TempPath("FuzzWithFork", ".dir");
   Env.DFTDir = DirPlusFile(Env.TempDir, "DFT");
@@ -345,14 +354,19 @@ void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
   } else {
     auto CFPath = DirPlusFile(Env.TempDir, "merge.txt");
     std::set<uint32_t> NewFeatures, NewCov;
-    CrashResistantMerge(Env.Args, {}, SeedFiles, &Env.Files, Env.Features,
+    Res = CrashResistantMerge(Env.Args, {}, SeedFiles, &Env.Files, Env.Features,
                         &NewFeatures, Env.Cov, &NewCov, CFPath,
                         /*Verbose=*/false, /*IsSetCoverMerge=*/false);
+    if (Res != 0)
+      return Res;
     Env.Features.insert(NewFeatures.begin(), NewFeatures.end());
     Env.Cov.insert(NewCov.begin(), NewCov.end());
     RemoveFile(CFPath);
   }
 
+  if (Fuzzer::isGracefulExitRequested())
+    return 0;
+
   if (Env.Group) {
     for (auto &path : Env.Files)
       Env.FilesSizes.push_back(FileSize(path));
@@ -391,9 +405,14 @@ void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
       StopJobs();
       break;
     }
-    Fuzzer::MaybeExitGracefully();
+    if (Fuzzer::MaybeExitGracefully())
+      return 0;
 
-    Env.RunOneMergeJob(Job.get());
+    Res = Env.RunOneMergeJob(Job.get());
+    if (Res != 0)
+      return Res;
+    if (Fuzzer::isGracefulExitRequested())
+      return 0;
 
     // merge the corpus .
     JobExecuted++;
@@ -488,7 +507,7 @@ void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
   // Use the exit code from the last child process.
   Printf("INFO: exiting: %d time: %zds\n", ExitCode,
          Env.secondsSinceProcessStartUp());
-  exit(ExitCode);
+  return ExitCode;
 }
 
 } // namespace fuzzer
diff --git a/FuzzerFork.h b/FuzzerFork.h
index fc3e9d636cbc..520e40af6f20 100644
--- a/FuzzerFork.h
+++ b/FuzzerFork.h
@@ -16,7 +16,7 @@
 #include <string>
 
 namespace fuzzer {
-void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
+int FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
                   const std::vector<std::string> &Args,
                   const std::vector<std::string> &CorpusDirs, int NumJobs);
 } // namespace fuzzer
diff --git a/FuzzerIO.cpp b/FuzzerIO.cpp
index 8d5b3ae3e1ae..0c77dba7e992 100644
--- a/FuzzerIO.cpp
+++ b/FuzzerIO.cpp
@@ -103,7 +103,9 @@ void ReadDirToVectorOfUnits(const char *Path, std::vector<Unit> *V, long *Epoch,
                             std::vector<std::string> *VPaths) {
   long E = Epoch ? *Epoch : 0;
   std::vector<std::string> Files;
-  ListFilesInDirRecursive(Path, Epoch, &Files, /*TopDir*/true);
+  int Res = ListFilesInDirRecursive(Path, Epoch, &Files, /*TopDir*/true);
+  if (ExitOnError && Res != 0)
+    exit(Res);
   size_t NumLoaded = 0;
   for (size_t i = 0; i < Files.size(); i++) {
     auto &X = Files[i];
@@ -120,12 +122,15 @@ void ReadDirToVectorOfUnits(const char *Path, std::vector<Unit> *V, long *Epoch,
   }
 }
 
-void GetSizedFilesFromDir(const std::string &Dir, std::vector<SizedFile> *V) {
+int GetSizedFilesFromDir(const std::string &Dir, std::vector<SizedFile> *V) {
   std::vector<std::string> Files;
-  ListFilesInDirRecursive(Dir, 0, &Files, /*TopDir*/true);
+  int Res = ListFilesInDirRecursive(Dir, 0, &Files, /*TopDir*/true);
+  if (Res != 0)
+    return Res;
   for (auto &File : Files)
     if (size_t Size = FileSize(File))
       V->push_back({File, Size});
+  return 0;
 }
 
 std::string DirPlusFile(const std::string &DirPath,
diff --git a/FuzzerIO.h b/FuzzerIO.h
index 874caad1baed..659996e6eb36 100644
--- a/FuzzerIO.h
+++ b/FuzzerIO.h
@@ -70,7 +70,7 @@ bool IsFile(const std::string &Path);
 bool IsDirectory(const std::string &Path);
 size_t FileSize(const std::string &Path);
 
-void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
+int ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
                              std::vector<std::string> *V, bool TopDir);
 
 bool MkDirRecursive(const std::string &Dir);
@@ -90,7 +90,7 @@ struct SizedFile {
   bool operator<(const SizedFile &B) const { return Size < B.Size; }
 };
 
-void GetSizedFilesFromDir(const std::string &Dir, std::vector<SizedFile> *V);
+int GetSizedFilesFromDir(const std::string &Dir, std::vector<SizedFile> *V);
 
 char GetSeparator();
 bool IsSeparator(char C);
diff --git a/FuzzerIOPosix.cpp b/FuzzerIOPosix.cpp
index f145dddcbb29..353743ebdb16 100644
--- a/FuzzerIOPosix.cpp
+++ b/FuzzerIOPosix.cpp
@@ -53,16 +53,16 @@ std::string Basename(const std::string &Path) {
   return Path.substr(Pos + 1);
 }
 
-void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
+int ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
                              std::vector<std::string> *V, bool TopDir) {
   auto E = GetEpoch(Dir);
   if (Epoch)
-    if (E && *Epoch >= E) return;
+    if (E && *Epoch >= E) return 0;
 
   DIR *D = opendir(Dir.c_str());
   if (!D) {
     Printf("%s: %s; exiting\n", strerror(errno), Dir.c_str());
-    exit(1);
+    return 1;
   }
   while (auto E = readdir(D)) {
     std::string Path = DirPlusFile(Dir, E->d_name);
@@ -71,12 +71,16 @@ void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
       V->push_back(Path);
     else if ((E->d_type == DT_DIR ||
              (E->d_type == DT_UNKNOWN && IsDirectory(Path))) &&
-             *E->d_name != '.')
-      ListFilesInDirRecursive(Path, Epoch, V, false);
+             *E->d_name != '.') {
+      int Res = ListFilesInDirRecursive(Path, Epoch, V, false);
+      if (Res != 0)
+        return Res;
+    }
   }
   closedir(D);
   if (Epoch && TopDir)
     *Epoch = E;
+  return 0;
 }
 
 void IterateDirRecursive(const std::string &Dir,
diff --git a/FuzzerIOWindows.cpp b/FuzzerIOWindows.cpp
index 6771fc173c91..9cf6062def03 100644
--- a/FuzzerIOWindows.cpp
+++ b/FuzzerIOWindows.cpp
@@ -110,11 +110,11 @@ size_t FileSize(const std::string &Path) {
   return size.QuadPart;
 }
 
-void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
+int ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
                              std::vector<std::string> *V, bool TopDir) {
   auto E = GetEpoch(Dir);
   if (Epoch)
-    if (E && *Epoch >= E) return;
+    if (E && *Epoch >= E) return 0;
 
   std::string Path(Dir);
   assert(!Path.empty());
@@ -128,9 +128,9 @@ void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
   if (FindHandle == INVALID_HANDLE_VALUE)
   {
     if (GetLastError() == ERROR_FILE_NOT_FOUND)
-      return;
+      return 0;
     Printf("No such file or directory: %s; exiting\n", Dir.c_str());
-    exit(1);
+    return 1;
   }
 
   do {
@@ -143,7 +143,9 @@ void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
                                FindInfo.cFileName[1] == '.'))
         continue;
 
-      ListFilesInDirRecursive(FileName, Epoch, V, false);
+      int Res = ListFilesInDirRecursive(FileName, Epoch, V, false);
+      if (Res != 0)
+        return Res;
     }
     else if (IsFile(FileName, FindInfo.dwFileAttributes))
       V->push_back(FileName);
@@ -157,6 +159,7 @@ void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
 
   if (Epoch && TopDir)
     *Epoch = E;
+  return 0;
 }
 
 void IterateDirRecursive(const std::string &Dir,
diff --git a/FuzzerInternal.h b/FuzzerInternal.h
index 88504705137a..a9d97ef590ee 100644
--- a/FuzzerInternal.h
+++ b/FuzzerInternal.h
@@ -34,8 +34,8 @@ public:
   Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD,
          const FuzzingOptions &Options);
   ~Fuzzer() = delete;
-  void Loop(std::vector<SizedFile> &CorporaFiles);
-  void ReadAndExecuteSeedCorpora(std::vector<SizedFile> &CorporaFiles);
+  int Loop(std::vector<SizedFile> &CorporaFiles);
+  int ReadAndExecuteSeedCorpora(std::vector<SizedFile> &CorporaFiles);
   void MinimizeCrashLoop(const Unit &U);
   void RereadOutputCorpus(size_t MaxSize);
 
@@ -64,6 +64,9 @@ public:
   static void StaticFileSizeExceedCallback();
   static void StaticGracefulExitCallback();
 
+  static void GracefullyExit();
+  static bool isGracefulExitRequested();
+
   // Executes the target callback on {Data, Size} once.
   // Returns false if the input was rejected by the target (target returned -1),
   // and true otherwise.
@@ -75,7 +78,7 @@ public:
 
   // Merge Corpora[1:] into Corpora[0].
   void Merge(const std::vector<std::string> &Corpora);
-  void CrashResistantMergeInternalStep(const std::string &ControlFilePath,
+  int CrashResistantMergeInternalStep(const std::string &ControlFilePath,
                                        bool IsSetCoverMerge);
   MutationDispatcher &GetMD() { return MD; }
   void PrintFinalStats();
@@ -89,7 +92,7 @@ public:
                                bool DuringInitialCorpusExecution);
 
   void HandleMalloc(size_t Size);
-  static void MaybeExitGracefully();
+  static bool MaybeExitGracefully();
   static int InterruptExitCode();
   std::string WriteToOutputCorpus(const Unit &U);
 
@@ -99,7 +102,7 @@ private:
   void ExitCallback();
   void CrashOnOverwrittenData();
   void InterruptCallback();
-  void MutateAndTestOne();
+  bool MutateAndTestOne();
   void PurgeAllocator();
   void ReportNewCoverage(InputInfo *II, const Unit &U);
   void PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size);
diff --git a/FuzzerLoop.cpp b/FuzzerLoop.cpp
index 263140c99f57..eff687380f15 100644
--- a/FuzzerLoop.cpp
+++ b/FuzzerLoop.cpp
@@ -252,12 +252,20 @@ void Fuzzer::ExitCallback() {
   _Exit(Options.ErrorExitCode);
 }
 
-void Fuzzer::MaybeExitGracefully() {
-  if (!F->GracefulExitRequested) return;
+bool Fuzzer::MaybeExitGracefully() {
+  if (!F->GracefulExitRequested) return false;
   Printf("==%lu== INFO: libFuzzer: exiting as requested\n", GetPid());
   RmDirRecursive(TempPath("FuzzWithFork", ".dir"));
   F->PrintFinalStats();
-  _Exit(0);
+  return true;
+}
+
+void Fuzzer::GracefullyExit() {
+  F->GracefulExitRequested = true;
+}
+
+bool Fuzzer::isGracefulExitRequested() {
+  return F->GracefulExitRequested;
 }
 
 int Fuzzer::InterruptExitCode() {
@@ -719,7 +727,7 @@ void Fuzzer::TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size,
   }
 }
 
-void Fuzzer::MutateAndTestOne() {
+bool Fuzzer::MutateAndTestOne() {
   MD.StartMutationSequence();
 
   auto &II = Corpus.ChooseUnitToMutate(MD.GetRand());
@@ -744,7 +752,7 @@ void Fuzzer::MutateAndTestOne() {
   for (int i = 0; i < Options.MutateDepth; i++) {
     if (TotalNumberOfRuns >= Options.MaxNumberOfRuns)
       break;
-    MaybeExitGracefully();
+    if (MaybeExitGracefully()) return true;
     size_t NewSize = 0;
     if (II.HasFocusFunction && !II.DataFlowTraceForFocusFunction.empty() &&
         Size <= CurrentMaxMutationLen)
@@ -778,6 +786,7 @@ void Fuzzer::MutateAndTestOne() {
   }
 
   II.NeedsEnergyUpdate = true;
+  return false;
 }
 
 void Fuzzer::PurgeAllocator() {
@@ -795,7 +804,7 @@ void Fuzzer::PurgeAllocator() {
   LastAllocatorPurgeAttemptTime = system_clock::now();
 }
 
-void Fuzzer::ReadAndExecuteSeedCorpora(std::vector<SizedFile> &CorporaFiles) {
+int Fuzzer::ReadAndExecuteSeedCorpora(std::vector<SizedFile> &CorporaFiles) {
   const size_t kMaxSaneLen = 1 << 20;
   const size_t kMinDefaultLen = 4096;
   size_t MaxSize = 0;
@@ -865,14 +874,21 @@ void Fuzzer::ReadAndExecuteSeedCorpora(std::vector<SizedFile> &CorporaFiles) {
                        /*TimeOfUnit=*/duration_cast<microseconds>(0s), {0}, DFT,
                        /*BaseII*/ nullptr);
   }
+  return 0;
 }
 
-void Fuzzer::Loop(std::vector<SizedFile> &CorporaFiles) {
+int Fuzzer::Loop(std::vector<SizedFile> &CorporaFiles) {
   auto FocusFunctionOrAuto = Options.FocusFunction;
-  DFT.Init(Options.DataFlowTrace, &FocusFunctionOrAuto, CorporaFiles,
+  int Res = DFT.Init(Options.DataFlowTrace, &FocusFunctionOrAuto, CorporaFiles,
            MD.GetRand());
-  TPC.SetFocusFunction(FocusFunctionOrAuto);
-  ReadAndExecuteSeedCorpora(CorporaFiles);
+  if (Res != 0)
+    return Res;
+  Res = TPC.SetFocusFunction(FocusFunctionOrAuto);
+  if (Res != 0)
+    return Res;
+  Res = ReadAndExecuteSeedCorpora(CorporaFiles);
+  if (Res != 0)
+    return Res;
   DFT.Clear();  // No need for DFT any more.
   TPC.SetPrintNewPCs(Options.PrintNewCovPcs);
   TPC.SetPrintNewFuncs(Options.PrintNewCovFuncs);
@@ -910,13 +926,15 @@ void Fuzzer::Loop(std::vector<SizedFile> &CorporaFiles) {
     }
 
     // Perform several mutations and runs.
-    MutateAndTestOne();
+    if (MutateAndTestOne())
+      return 0;
 
     PurgeAllocator();
   }
 
   PrintStats("DONE  ", "\n");
   MD.PrintRecommendedDictionary();
+  return 0;
 }
 
 void Fuzzer::MinimizeCrashLoop(const Unit &U) {
diff --git a/FuzzerMerge.cpp b/FuzzerMerge.cpp
index 69e71135a3e4..30ecc796b41d 100644
--- a/FuzzerMerge.cpp
+++ b/FuzzerMerge.cpp
@@ -28,11 +28,12 @@ bool Merger::Parse(const std::string &Str, bool ParseCoverage) {
   return Parse(SS, ParseCoverage);
 }
 
-void Merger::ParseOrExit(std::istream &IS, bool ParseCoverage) {
+int Merger::ParseOrExit(std::istream &IS, bool ParseCoverage) {
   if (!Parse(IS, ParseCoverage)) {
     Printf("MERGE: failed to parse the control file (unexpected error)\n");
-    exit(1);
+    return 1;
   }
+  return 0;
 }
 
 // The control file example:
@@ -201,12 +202,14 @@ std::set<uint32_t> Merger::AllFeatures() const {
 }
 
 // Inner process. May crash if the target crashes.
-void Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath,
+int Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath,
                                              bool IsSetCoverMerge) {
   Printf("MERGE-INNER: using the control file '%s'\n", CFPath.c_str());
   Merger M;
   std::ifstream IF(CFPath);
-  M.ParseOrExit(IF, false);
+  int Res = M.ParseOrExit(IF, false);
+  if (Res != 0)
+    return Res;
   IF.close();
   if (!M.LastFailure.empty())
     Printf("MERGE-INNER: '%s' caused a failure at the previous merge step\n",
@@ -224,7 +227,8 @@ void Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath,
   };
   std::set<const TracePC::PCTableEntry *> AllPCs;
   for (size_t i = M.FirstNotProcessedFile; i < M.Files.size(); i++) {
-    Fuzzer::MaybeExitGracefully();
+    if (Fuzzer::MaybeExitGracefully())
+      return 0;
     auto U = FileToVector(M.Files[i].Name);
     if (U.size() > MaxInputLen) {
       U.resize(MaxInputLen);
@@ -270,6 +274,7 @@ void Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath,
     OF.flush();
   }
   PrintStatsWrapper("DONE  ");
+  return 0;
 }
 
 // Merges all corpora into the first corpus. A file is added into
@@ -393,11 +398,12 @@ size_t Merger::SetCoverMerge(const std::set<uint32_t> &InitialFeatures,
   return NewFeatures->size();
 }
 
-static size_t
+static int
 WriteNewControlFile(const std::string &CFPath,
                     const std::vector<SizedFile> &OldCorpus,
                     const std::vector<SizedFile> &NewCorpus,
                     const std::vector<MergeFileInfo> &KnownFiles) {
+                    size_t &NumFiles) {
   std::unordered_set<std::string> FilesToSkip;
   for (auto &SF: KnownFiles)
     FilesToSkip.insert(SF.Name);
@@ -423,14 +429,15 @@ WriteNewControlFile(const std::string &CFPath,
   if (!ControlFile) {
     Printf("MERGE-OUTER: failed to write to the control file: %s\n",
            CFPath.c_str());
-    exit(1);
+    return 1;
   }
 
-  return FilesToUse.size();
+  NumFiles = FilesToUse.size();
+  return 0;
 }
 
 // Outer process. Does not call the target code and thus should not fail.
-void CrashResistantMerge(const std::vector<std::string> &Args,
+int CrashResistantMerge(const std::vector<std::string> &Args,
                          const std::vector<SizedFile> &OldCorpus,
                          const std::vector<SizedFile> &NewCorpus,
                          std::vector<std::string> *NewFiles,
@@ -440,8 +447,9 @@ void CrashResistantMerge(const std::vector<std::string> &Args,
                          std::set<uint32_t> *NewCov, const std::string &CFPath,
                          bool V, /*Verbose*/
                          bool IsSetCoverMerge) {
-  if (NewCorpus.empty() && OldCorpus.empty()) return;  // Nothing to merge.
+  if (NewCorpus.empty() && OldCorpus.empty()) return 0;  // Nothing to merge.
   size_t NumAttempts = 0;
+  int Res;
   std::vector<MergeFileInfo> KnownFiles;
   if (FileSize(CFPath)) {
     VPrintf(V, "MERGE-OUTER: non-empty control file provided: '%s'\n",
@@ -462,7 +470,8 @@ void CrashResistantMerge(const std::vector<std::string> &Args,
           VPrintf(
               V,
               "MERGE-OUTER: nothing to do, merge has been completed before\n");
-          exit(0);
+          Fuzzer::GracefullyExit();
+          return 0;
         }
 
         // Number of input files likely changed, start merge from scratch, but
@@ -487,7 +496,9 @@ void CrashResistantMerge(const std::vector<std::string> &Args,
             "%zd files, %zd in the initial corpus, %zd processed earlier\n",
             OldCorpus.size() + NewCorpus.size(), OldCorpus.size(),
             KnownFiles.size());
-    NumAttempts = WriteNewControlFile(CFPath, OldCorpus, NewCorpus, KnownFiles);
+    Res = WriteNewControlFile(CFPath, OldCorpus, NewCorpus, KnownFiles, NumAttempts);
+    if (Res != 0)
+      return Res;
   }
 
   // Execute the inner process until it passes.
@@ -498,7 +509,8 @@ void CrashResistantMerge(const std::vector<std::string> &Args,
   BaseCmd.removeFlag("fork");
   BaseCmd.removeFlag("collect_data_flow");
   for (size_t Attempt = 1; Attempt <= NumAttempts; Attempt++) {
-    Fuzzer::MaybeExitGracefully();
+    if (Fuzzer::MaybeExitGracefully())
+      return 0;
     VPrintf(V, "MERGE-OUTER: attempt %zd\n", Attempt);
     Command Cmd(BaseCmd);
     Cmd.addFlag("merge_control_file", CFPath);
@@ -522,7 +534,9 @@ void CrashResistantMerge(const std::vector<std::string> &Args,
   VPrintf(V, "MERGE-OUTER: the control file has %zd bytes\n",
           (size_t)IF.tellg());
   IF.seekg(0, IF.beg);
-  M.ParseOrExit(IF, true);
+  Res = M.ParseOrExit(IF, true);
+  if (Res != 0)
+    return Res;
   IF.close();
   VPrintf(V,
           "MERGE-OUTER: consumed %zdMb (%zdMb rss) to parse the control file\n",
@@ -536,6 +550,7 @@ void CrashResistantMerge(const std::vector<std::string> &Args,
   VPrintf(V, "MERGE-OUTER: %zd new files with %zd new features added; "
           "%zd new coverage edges\n",
          NewFiles->size(), NewFeatures->size(), NewCov->size());
+  return 0;
 }
 
 } // namespace fuzzer
diff --git a/FuzzerMerge.h b/FuzzerMerge.h
index 42f798e1da18..1b5f6f69b0dd 100644
--- a/FuzzerMerge.h
+++ b/FuzzerMerge.h
@@ -64,7 +64,7 @@ struct Merger {
 
   bool Parse(std::istream &IS, bool ParseCoverage);
   bool Parse(const std::string &Str, bool ParseCoverage);
-  void ParseOrExit(std::istream &IS, bool ParseCoverage);
+  int ParseOrExit(std::istream &IS, bool ParseCoverage);
   size_t Merge(const std::set<uint32_t> &InitialFeatures,
                std::set<uint32_t> *NewFeatures,
                const std::set<uint32_t> &InitialCov, std::set<uint32_t> *NewCov,
@@ -78,7 +78,7 @@ struct Merger {
   std::set<uint32_t> AllFeatures() const;
 };
 
-void CrashResistantMerge(const std::vector<std::string> &Args,
+int CrashResistantMerge(const std::vector<std::string> &Args,
                          const std::vector<SizedFile> &OldCorpus,
                          const std::vector<SizedFile> &NewCorpus,
                          std::vector<std::string> *NewFiles,
diff --git a/FuzzerTracePC.cpp b/FuzzerTracePC.cpp
index 7bd1a0870c59..d083e725a26b 100644
--- a/FuzzerTracePC.cpp
+++ b/FuzzerTracePC.cpp
@@ -251,13 +251,13 @@ void TracePC::IterateCoveredFunctions(CallBack CB) {
   }
 }
 
-void TracePC::SetFocusFunction(const std::string &FuncName) {
+int TracePC::SetFocusFunction(const std::string &FuncName) {
   // This function should be called once.
   assert(!FocusFunctionCounterPtr);
   // "auto" is not a valid function name. If this function is called with "auto"
   // that means the auto focus functionality failed.
   if (FuncName.empty() || FuncName == "auto")
-    return;
+    return 0;
   for (size_t M = 0; M < NumModules; M++) {
     auto &PCTE = ModulePCTable[M];
     size_t N = PCTE.Stop - PCTE.Start;
@@ -269,13 +269,13 @@ void TracePC::SetFocusFunction(const std::string &FuncName) {
       if (FuncName != Name) continue;
       Printf("INFO: Focus function is set to '%s'\n", Name.c_str());
       FocusFunctionCounterPtr = Modules[M].Start() + I;
-      return;
+      return 0;
     }
   }
 
   Printf("ERROR: Failed to set focus function. Make sure the function name is "
          "valid (%s) and symbolization is enabled.\n", FuncName.c_str());
-  exit(1);
+  return 1;
 }
 
 bool TracePC::ObservedFocusFunction() {
diff --git a/FuzzerTracePC.h b/FuzzerTracePC.h
index af1f9d81e950..507ea6a4abfc 100644
--- a/FuzzerTracePC.h
+++ b/FuzzerTracePC.h
@@ -116,7 +116,7 @@ class TracePC {
       CB(PC);
   }
 
-  void SetFocusFunction(const std::string &FuncName);
+  int SetFocusFunction(const std::string &FuncName);
   bool ObservedFocusFunction();
 
   struct PCTableEntry {
