fuzz: add shrinker-temp-file-prefix

And relax naming requirements of input and output files.
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 278fda8..d40f844 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -110,6 +110,12 @@
                Unsigned 32-bit integer specifying maximum number of steps the
                shrinker will take before giving up.  Ignored unless --shrink
                is used.
+  --shrinker-temp-file-prefix
+               Specifies a temporary file prefix that will be used to output
+               temporary shader files during shrinking.  A number and .spv
+               extension will be added.  The default is "temp_", which will
+               cause files like "temp_0001.spv" to be output to the current
+               directory.  Ignored unless --shrink is used.
   --replay-validation
                Run the validator after applying each transformation during
                replay (including the replay that occurs during shrinking).
@@ -133,18 +139,12 @@
   fprintf(stderr, "%s\n", message);
 }
 
-bool EndsWithSpv(const std::string& filename) {
-  std::string dot_spv = ".spv";
-  return filename.length() >= dot_spv.length() &&
-         0 == filename.compare(filename.length() - dot_spv.length(),
-                               filename.length(), dot_spv);
-}
-
 FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
                       std::string* out_binary_file,
                       std::string* replay_transformations_file,
                       std::vector<std::string>* interestingness_function,
                       std::string* shrink_transformations_file,
+                      std::string* shrink_temp_file_prefix,
                       spvtools::FuzzerOptions* fuzzer_options) {
   uint32_t positional_arg_index = 0;
   bool only_positional_arguments_remain = false;
@@ -192,6 +192,10 @@
             static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
         assert(end != split_flag.second.c_str() && errno == 0);
         fuzzer_options->set_shrinker_step_limit(step_limit);
+      } else if (0 == strncmp(cur_arg, "--shrinker-temp-file-prefix=",
+                              sizeof("--shrinker-temp-file-prefix=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        *shrink_temp_file_prefix = std::string(split_flag.second);
       } else if (0 == strcmp(cur_arg, "--")) {
         only_positional_arguments_remain = true;
       } else if ('\0' == cur_arg[1]) {
@@ -215,23 +219,11 @@
     return {FuzzActions::STOP, 1};
   }
 
-  if (!EndsWithSpv(*in_binary_file)) {
-    spvtools::Error(FuzzDiagnostic, nullptr, {},
-                    "Input filename must have extension .spv");
-    return {FuzzActions::STOP, 1};
-  }
-
   if (out_binary_file->empty()) {
     spvtools::Error(FuzzDiagnostic, nullptr, {}, "-o required");
     return {FuzzActions::STOP, 1};
   }
 
-  if (!EndsWithSpv(*out_binary_file)) {
-    spvtools::Error(FuzzDiagnostic, nullptr, {},
-                    "Output filename must have extension .spv");
-    return {FuzzActions::STOP, 1};
-  }
-
   if (replay_transformations_file->empty() &&
       shrink_transformations_file->empty() &&
       static_cast<spv_const_fuzzer_options>(*fuzzer_options)
@@ -329,6 +321,7 @@
             const std::vector<uint32_t>& binary_in,
             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
             const std::string& shrink_transformations_file,
+            const std::string& shrink_temp_file_prefix,
             const std::vector<std::string>& interestingness_command,
             std::vector<uint32_t>* binary_out,
             spvtools::fuzz::protobufs::TransformationSequence*
@@ -354,11 +347,11 @@
   std::string interestingness_command_joined = joined.str();
 
   spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function =
-      [interestingness_command_joined](std::vector<uint32_t> binary,
-                                       uint32_t reductions_applied) -> bool {
+      [interestingness_command_joined, shrink_temp_file_prefix](
+          std::vector<uint32_t> binary, uint32_t reductions_applied) -> bool {
     std::stringstream ss;
-    ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
-       << ".spv";
+    ss << shrink_temp_file_prefix << std::setw(4) << std::setfill('0')
+       << reductions_applied << ".spv";
     const auto spv_file = ss.str();
     const std::string command = interestingness_command_joined + " " + spv_file;
     auto write_file_succeeded =
@@ -423,13 +416,14 @@
   std::string replay_transformations_file;
   std::vector<std::string> interestingness_function;
   std::string shrink_transformations_file;
+  std::string shrink_temp_file_prefix = "temp_";
 
   spvtools::FuzzerOptions fuzzer_options;
 
-  FuzzStatus status =
-      ParseFlags(argc, argv, &in_binary_file, &out_binary_file,
-                 &replay_transformations_file, &interestingness_function,
-                 &shrink_transformations_file, &fuzzer_options);
+  FuzzStatus status = ParseFlags(
+      argc, argv, &in_binary_file, &out_binary_file,
+      &replay_transformations_file, &interestingness_function,
+      &shrink_transformations_file, &shrink_temp_file_prefix, &fuzzer_options);
 
   if (status.action == FuzzActions::STOP) {
     return status.code;
@@ -441,10 +435,12 @@
   }
 
   spvtools::fuzz::protobufs::FactSequence initial_facts;
-  const std::string dot_spv(".spv");
-  std::string in_facts_file =
-      in_binary_file.substr(0, in_binary_file.length() - dot_spv.length()) +
-      ".facts";
+
+  // If not found, dot_pos will be std::string::npos, which can be used in
+  // substr to mean "the end of the string"; there is no need to check the
+  // result.
+  size_t dot_pos = in_binary_file.rfind('.');
+  std::string in_facts_file = in_binary_file.substr(0, dot_pos) + ".facts";
   std::ifstream facts_input(in_facts_file);
   if (facts_input) {
     std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)),
@@ -484,8 +480,9 @@
         return 1;
       }
       if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
-                  shrink_transformations_file, interestingness_function,
-                  &binary_out, &transformations_applied)) {
+                  shrink_transformations_file, shrink_temp_file_prefix,
+                  interestingness_function, &binary_out,
+                  &transformations_applied)) {
         return 1;
       }
     } break;
@@ -500,8 +497,11 @@
     return 1;
   }
 
-  std::string output_file_prefix =
-      out_binary_file.substr(0, out_binary_file.length() - dot_spv.length());
+  // If not found, dot_pos will be std::string::npos, which can be used in
+  // substr to mean "the end of the string"; there is no need to check the
+  // result.
+  dot_pos = out_binary_file.rfind('.');
+  std::string output_file_prefix = out_binary_file.substr(0, dot_pos);
   std::ofstream transformations_file;
   transformations_file.open(output_file_prefix + ".transformations",
                             std::ios::out | std::ios::binary);