• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <cassert>
16 #include <cerrno>
17 #include <cstring>
18 #include <fstream>
19 #include <functional>
20 #include <random>
21 #include <sstream>
22 #include <string>
23 
24 #include "source/fuzz/force_render_red.h"
25 #include "source/fuzz/fuzzer.h"
26 #include "source/fuzz/fuzzer_util.h"
27 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
28 #include "source/fuzz/replayer.h"
29 #include "source/fuzz/shrinker.h"
30 #include "source/opt/build_module.h"
31 #include "source/opt/ir_context.h"
32 #include "source/opt/log.h"
33 #include "source/spirv_fuzzer_options.h"
34 #include "source/util/string_utils.h"
35 #include "tools/io.h"
36 #include "tools/util/cli_consumer.h"
37 
38 namespace {
39 
40 // Check that the std::system function can actually be used.
CheckExecuteCommand()41 bool CheckExecuteCommand() {
42   int res = std::system(nullptr);
43   return res != 0;
44 }
45 
46 // Execute a command using the shell.
47 // Returns true if and only if the command's exit status was 0.
ExecuteCommand(const std::string & command)48 bool ExecuteCommand(const std::string& command) {
49   errno = 0;
50   int status = std::system(command.c_str());
51   assert(errno == 0 && "failed to execute command");
52   // The result returned by 'system' is implementation-defined, but is
53   // usually the case that the returned value is 0 when the command's exit
54   // code was 0.  We are assuming that here, and that's all we depend on.
55   return status == 0;
56 }
57 
58 // Status and actions to perform after parsing command-line arguments.
59 enum class FuzzActions {
60   FORCE_RENDER_RED,  // Turn the shader into a form such that it is guaranteed
61                      // to render a red image.
62   FUZZ,    // Run the fuzzer to apply transformations in a randomized fashion.
63   REPLAY,  // Replay an existing sequence of transformations.
64   SHRINK,  // Shrink an existing sequence of transformations with respect to an
65            // interestingness function.
66   STOP     // Do nothing.
67 };
68 
69 struct FuzzStatus {
70   FuzzActions action;
71   int code;
72 };
73 
PrintUsage(const char * program)74 void PrintUsage(const char* program) {
75   // NOTE: Please maintain flags in lexicographical order.
76   printf(
77       R"(%s - Fuzzes an equivalent SPIR-V binary based on a given binary.
78 
79 USAGE: %s [options] <input.spv> -o <output.spv> \
80   --donors=<donors.txt>
81 USAGE: %s [options] <input.spv> -o <output.spv> \
82   --shrink=<input.transformations> -- <interestingness_test> [args...]
83 
84 The SPIR-V binary is read from <input.spv>.  If <input.facts> is also present,
85 facts about the SPIR-V binary are read from this file.
86 
87 The transformed SPIR-V binary is written to <output.spv>.  Human-readable and
88 binary representations of the transformations that were applied are written to
89 <output.transformations_json> and <output.transformations>, respectively.
90 
91 When passing --shrink=<input.transformations> an <interestingness_test>
92 must also be provided; this is the path to a script that returns 0 if and only
93 if a given SPIR-V binary is interesting.  The SPIR-V binary will be passed to
94 the script as an argument after any other provided arguments [args...].  The
95 "--" characters are optional but denote that all arguments that follow are
96 positional arguments and thus will be forwarded to the interestingness script,
97 and not parsed by %s.
98 
99 NOTE: The fuzzer is a work in progress.
100 
101 Options (in lexicographical order):
102 
103   -h, --help
104                Print this help.
105   --donors=
106                File specifying a series of donor files, one per line.  Must be
107                provided if the tool is invoked in fuzzing mode; incompatible
108                with replay and shrink modes.  The file should be empty if no
109                donors are to be used.
110   --force-render-red
111                Transforms the input shader into a shader that writes red to the
112                output buffer, and then captures the original shader as the body
113                of a conditional with a dynamically false guard.  Exploits input
114                facts to make the guard non-obviously false.  This option is a
115                helper for massaging crash-inducing tests into a runnable
116                format; it does not perform any fuzzing.
117   --fuzzer-pass-validation
118                Run the validator after applying each fuzzer pass during
119                fuzzing.  Aborts fuzzing early if an invalid binary is created.
120                Useful for debugging spirv-fuzz.
121   --replay
122                File from which to read a sequence of transformations to replay
123                (instead of fuzzing)
124   --seed=
125                Unsigned 32-bit integer seed to control random number
126                generation.
127   --shrink=
128                File from which to read a sequence of transformations to shrink
129                (instead of fuzzing)
130   --shrinker-step-limit=
131                Unsigned 32-bit integer specifying maximum number of steps the
132                shrinker will take before giving up.  Ignored unless --shrink
133                is used.
134   --shrinker-temp-file-prefix=
135                Specifies a temporary file prefix that will be used to output
136                temporary shader files during shrinking.  A number and .spv
137                extension will be added.  The default is "temp_", which will
138                cause files like "temp_0001.spv" to be output to the current
139                directory.  Ignored unless --shrink is used.
140   --replay-validation
141                Run the validator after applying each transformation during
142                replay (including the replay that occurs during shrinking).
143                Aborts if an invalid binary is created.  Useful for debugging
144                spirv-fuzz.
145   --version
146                Display fuzzer version information.
147 
148 )",
149       program, program, program, program);
150 }
151 
152 // Message consumer for this tool.  Used to emit diagnostics during
153 // initialization and setup. Note that |source| and |position| are irrelevant
154 // here because we are still not processing a SPIR-V input file.
FuzzDiagnostic(spv_message_level_t level,const char *,const spv_position_t &,const char * message)155 void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/,
156                     const spv_position_t& /*position*/, const char* message) {
157   if (level == SPV_MSG_ERROR) {
158     fprintf(stderr, "error: ");
159   }
160   fprintf(stderr, "%s\n", message);
161 }
162 
ParseFlags(int argc,const char ** argv,std::string * in_binary_file,std::string * out_binary_file,std::string * donors_file,std::string * replay_transformations_file,std::vector<std::string> * interestingness_test,std::string * shrink_transformations_file,std::string * shrink_temp_file_prefix,spvtools::FuzzerOptions * fuzzer_options)163 FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
164                       std::string* out_binary_file, std::string* donors_file,
165                       std::string* replay_transformations_file,
166                       std::vector<std::string>* interestingness_test,
167                       std::string* shrink_transformations_file,
168                       std::string* shrink_temp_file_prefix,
169                       spvtools::FuzzerOptions* fuzzer_options) {
170   uint32_t positional_arg_index = 0;
171   bool only_positional_arguments_remain = false;
172   bool force_render_red = false;
173 
174   for (int argi = 1; argi < argc; ++argi) {
175     const char* cur_arg = argv[argi];
176     if ('-' == cur_arg[0] && !only_positional_arguments_remain) {
177       if (0 == strcmp(cur_arg, "--version")) {
178         spvtools::Logf(FuzzDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
179                        spvSoftwareVersionDetailsString());
180         return {FuzzActions::STOP, 0};
181       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
182         PrintUsage(argv[0]);
183         return {FuzzActions::STOP, 0};
184       } else if (0 == strcmp(cur_arg, "-o")) {
185         if (out_binary_file->empty() && argi + 1 < argc) {
186           *out_binary_file = std::string(argv[++argi]);
187         } else {
188           PrintUsage(argv[0]);
189           return {FuzzActions::STOP, 1};
190         }
191       } else if (0 == strncmp(cur_arg, "--donors=", sizeof("--donors=") - 1)) {
192         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
193         *donors_file = std::string(split_flag.second);
194       } else if (0 == strncmp(cur_arg, "--force-render-red",
195                               sizeof("--force-render-red") - 1)) {
196         force_render_red = true;
197       } else if (0 == strncmp(cur_arg, "--fuzzer-pass-validation",
198                               sizeof("--fuzzer-pass-validation") - 1)) {
199         fuzzer_options->enable_fuzzer_pass_validation();
200       } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) {
201         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
202         *replay_transformations_file = std::string(split_flag.second);
203       } else if (0 == strncmp(cur_arg, "--replay-validation",
204                               sizeof("--replay-validation") - 1)) {
205         fuzzer_options->enable_replay_validation();
206       } else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) {
207         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
208         *shrink_transformations_file = std::string(split_flag.second);
209       } else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) {
210         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
211         char* end = nullptr;
212         errno = 0;
213         const auto seed =
214             static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
215         assert(end != split_flag.second.c_str() && errno == 0);
216         fuzzer_options->set_random_seed(seed);
217       } else if (0 == strncmp(cur_arg, "--shrinker-step-limit=",
218                               sizeof("--shrinker-step-limit=") - 1)) {
219         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
220         char* end = nullptr;
221         errno = 0;
222         const auto step_limit =
223             static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
224         assert(end != split_flag.second.c_str() && errno == 0);
225         fuzzer_options->set_shrinker_step_limit(step_limit);
226       } else if (0 == strncmp(cur_arg, "--shrinker-temp-file-prefix=",
227                               sizeof("--shrinker-temp-file-prefix=") - 1)) {
228         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
229         *shrink_temp_file_prefix = std::string(split_flag.second);
230       } else if (0 == strcmp(cur_arg, "--")) {
231         only_positional_arguments_remain = true;
232       } else {
233         std::stringstream ss;
234         ss << "Unrecognized argument: " << cur_arg << std::endl;
235         spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str());
236         PrintUsage(argv[0]);
237         return {FuzzActions::STOP, 1};
238       }
239     } else if (positional_arg_index == 0) {
240       // Binary input file name
241       assert(in_binary_file->empty());
242       *in_binary_file = std::string(cur_arg);
243       positional_arg_index++;
244     } else {
245       interestingness_test->push_back(std::string(cur_arg));
246     }
247   }
248 
249   if (in_binary_file->empty()) {
250     spvtools::Error(FuzzDiagnostic, nullptr, {}, "No input file specified");
251     return {FuzzActions::STOP, 1};
252   }
253 
254   if (out_binary_file->empty()) {
255     spvtools::Error(FuzzDiagnostic, nullptr, {}, "-o required");
256     return {FuzzActions::STOP, 1};
257   }
258 
259   auto const_fuzzer_options =
260       static_cast<spv_const_fuzzer_options>(*fuzzer_options);
261   if (force_render_red) {
262     if (!replay_transformations_file->empty() ||
263         !shrink_transformations_file->empty() ||
264         const_fuzzer_options->replay_validation_enabled) {
265       spvtools::Error(FuzzDiagnostic, nullptr, {},
266                       "The --force-render-red argument cannot be used with any "
267                       "other arguments except -o.");
268       return {FuzzActions::STOP, 1};
269     }
270     return {FuzzActions::FORCE_RENDER_RED, 0};
271   }
272 
273   if (replay_transformations_file->empty() &&
274       shrink_transformations_file->empty() &&
275       static_cast<spv_const_fuzzer_options>(*fuzzer_options)
276           ->replay_validation_enabled) {
277     spvtools::Error(FuzzDiagnostic, nullptr, {},
278                     "The --replay-validation argument can only be used with "
279                     "one of the --replay or --shrink arguments.");
280     return {FuzzActions::STOP, 1};
281   }
282 
283   if (shrink_transformations_file->empty() && !interestingness_test->empty()) {
284     spvtools::Error(FuzzDiagnostic, nullptr, {},
285                     "Too many positional arguments specified; extra positional "
286                     "arguments are used as the interestingness function, which "
287                     "are only valid with the --shrink option.");
288     return {FuzzActions::STOP, 1};
289   }
290 
291   if (!shrink_transformations_file->empty() && interestingness_test->empty()) {
292     spvtools::Error(
293         FuzzDiagnostic, nullptr, {},
294         "The --shrink option requires an interestingness function.");
295     return {FuzzActions::STOP, 1};
296   }
297 
298   if (!replay_transformations_file->empty() ||
299       !shrink_transformations_file->empty()) {
300     // Donors should not be provided when replaying or shrinking: they only make
301     // sense during fuzzing.
302     if (!donors_file->empty()) {
303       spvtools::Error(FuzzDiagnostic, nullptr, {},
304                       "The --donors argument is not compatible with --replay "
305                       "nor --shrink.");
306       return {FuzzActions::STOP, 1};
307     }
308   }
309 
310   if (!replay_transformations_file->empty()) {
311     // A replay transformations file was given, thus the tool is being invoked
312     // in replay mode.
313     if (!shrink_transformations_file->empty()) {
314       spvtools::Error(
315           FuzzDiagnostic, nullptr, {},
316           "The --replay and --shrink arguments are mutually exclusive.");
317       return {FuzzActions::STOP, 1};
318     }
319     return {FuzzActions::REPLAY, 0};
320   }
321 
322   if (!shrink_transformations_file->empty()) {
323     // The tool is being invoked in shrink mode.
324     assert(!interestingness_test->empty() &&
325            "An error should have been raised if --shrink was provided without "
326            "an interestingness test.");
327     return {FuzzActions::SHRINK, 0};
328   }
329 
330   // The tool is being invoked in fuzz mode.
331   if (donors_file->empty()) {
332     spvtools::Error(FuzzDiagnostic, nullptr, {},
333                     "Fuzzing requires that the --donors option is used.");
334     return {FuzzActions::STOP, 1};
335   }
336   return {FuzzActions::FUZZ, 0};
337 }
338 
ParseTransformations(const std::string & transformations_file,spvtools::fuzz::protobufs::TransformationSequence * transformations)339 bool ParseTransformations(
340     const std::string& transformations_file,
341     spvtools::fuzz::protobufs::TransformationSequence* transformations) {
342   std::ifstream transformations_stream;
343   transformations_stream.open(transformations_file,
344                               std::ios::in | std::ios::binary);
345   auto parse_success =
346       transformations->ParseFromIstream(&transformations_stream);
347   transformations_stream.close();
348   if (!parse_success) {
349     spvtools::Error(FuzzDiagnostic, nullptr, {},
350                     ("Error reading transformations from file '" +
351                      transformations_file + "'")
352                         .c_str());
353     return false;
354   }
355   return true;
356 }
357 
Replay(const spv_target_env & target_env,spv_const_fuzzer_options fuzzer_options,const std::vector<uint32_t> & binary_in,const spvtools::fuzz::protobufs::FactSequence & initial_facts,const std::string & replay_transformations_file,std::vector<uint32_t> * binary_out,spvtools::fuzz::protobufs::TransformationSequence * transformations_applied)358 bool Replay(const spv_target_env& target_env,
359             spv_const_fuzzer_options fuzzer_options,
360             const std::vector<uint32_t>& binary_in,
361             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
362             const std::string& replay_transformations_file,
363             std::vector<uint32_t>* binary_out,
364             spvtools::fuzz::protobufs::TransformationSequence*
365                 transformations_applied) {
366   spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
367   if (!ParseTransformations(replay_transformations_file,
368                             &transformation_sequence)) {
369     return false;
370   }
371   spvtools::fuzz::Replayer replayer(target_env,
372                                     fuzzer_options->replay_validation_enabled);
373   replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
374   auto replay_result_status =
375       replayer.Run(binary_in, initial_facts, transformation_sequence,
376                    binary_out, transformations_applied);
377   return !(replay_result_status !=
378            spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
379 }
380 
Shrink(const spv_target_env & target_env,spv_const_fuzzer_options fuzzer_options,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 * transformations_applied)381 bool Shrink(const spv_target_env& target_env,
382             spv_const_fuzzer_options fuzzer_options,
383             const std::vector<uint32_t>& binary_in,
384             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
385             const std::string& shrink_transformations_file,
386             const std::string& shrink_temp_file_prefix,
387             const std::vector<std::string>& interestingness_command,
388             std::vector<uint32_t>* binary_out,
389             spvtools::fuzz::protobufs::TransformationSequence*
390                 transformations_applied) {
391   spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
392   if (!ParseTransformations(shrink_transformations_file,
393                             &transformation_sequence)) {
394     return false;
395   }
396   spvtools::fuzz::Shrinker shrinker(target_env,
397                                     fuzzer_options->shrinker_step_limit,
398                                     fuzzer_options->replay_validation_enabled);
399   shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
400 
401   assert(!interestingness_command.empty() &&
402          "An error should have been raised because the interestingness_command "
403          "is empty.");
404   std::stringstream joined;
405   joined << interestingness_command[0];
406   for (size_t i = 1, size = interestingness_command.size(); i < size; ++i) {
407     joined << " " << interestingness_command[i];
408   }
409   std::string interestingness_command_joined = joined.str();
410 
411   spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function =
412       [interestingness_command_joined, shrink_temp_file_prefix](
413           std::vector<uint32_t> binary, uint32_t reductions_applied) -> bool {
414     std::stringstream ss;
415     ss << shrink_temp_file_prefix << std::setw(4) << std::setfill('0')
416        << reductions_applied << ".spv";
417     const auto spv_file = ss.str();
418     const std::string command = interestingness_command_joined + " " + spv_file;
419     auto write_file_succeeded =
420         WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
421     (void)(write_file_succeeded);
422     assert(write_file_succeeded);
423     return ExecuteCommand(command);
424   };
425 
426   auto shrink_result_status = shrinker.Run(
427       binary_in, initial_facts, transformation_sequence,
428       interestingness_function, binary_out, transformations_applied);
429   return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete ==
430              shrink_result_status ||
431          spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached ==
432              shrink_result_status;
433 }
434 
Fuzz(const spv_target_env & target_env,spv_const_fuzzer_options fuzzer_options,const std::vector<uint32_t> & binary_in,const spvtools::fuzz::protobufs::FactSequence & initial_facts,const std::string & donors,std::vector<uint32_t> * binary_out,spvtools::fuzz::protobufs::TransformationSequence * transformations_applied)435 bool Fuzz(const spv_target_env& target_env,
436           spv_const_fuzzer_options fuzzer_options,
437           const std::vector<uint32_t>& binary_in,
438           const spvtools::fuzz::protobufs::FactSequence& initial_facts,
439           const std::string& donors, std::vector<uint32_t>* binary_out,
440           spvtools::fuzz::protobufs::TransformationSequence*
441               transformations_applied) {
442   auto message_consumer = spvtools::utils::CLIMessageConsumer;
443 
444   std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donor_suppliers;
445 
446   std::ifstream donors_file(donors);
447   if (!donors_file) {
448     spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error opening donors file");
449     return false;
450   }
451   std::string donor_filename;
452   while (std::getline(donors_file, donor_filename)) {
453     donor_suppliers.emplace_back(
454         [donor_filename, message_consumer,
455          target_env]() -> std::unique_ptr<spvtools::opt::IRContext> {
456           std::vector<uint32_t> donor_binary;
457           if (!ReadFile<uint32_t>(donor_filename.c_str(), "rb",
458                                   &donor_binary)) {
459             return nullptr;
460           }
461           return spvtools::BuildModule(target_env, message_consumer,
462                                        donor_binary.data(),
463                                        donor_binary.size());
464         });
465   }
466 
467   spvtools::fuzz::Fuzzer fuzzer(
468       target_env,
469       fuzzer_options->has_random_seed
470           ? fuzzer_options->random_seed
471           : static_cast<uint32_t>(std::random_device()()),
472       fuzzer_options->fuzzer_pass_validation_enabled);
473   fuzzer.SetMessageConsumer(message_consumer);
474   auto fuzz_result_status =
475       fuzzer.Run(binary_in, initial_facts, donor_suppliers, binary_out,
476                  transformations_applied);
477   if (fuzz_result_status !=
478       spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
479     spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
480     return false;
481   }
482   return true;
483 }
484 
485 }  // namespace
486 
487 // Dumps |binary| to file |filename|. Useful for interactive debugging.
DumpShader(const std::vector<uint32_t> & binary,const char * filename)488 void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
489   auto write_file_succeeded =
490       WriteFile(filename, "wb", &binary[0], binary.size());
491   if (!write_file_succeeded) {
492     std::cerr << "Failed to dump shader" << std::endl;
493   }
494 }
495 
496 // Dumps the SPIRV-V module in |context| to file |filename|. Useful for
497 // interactive debugging.
DumpShader(spvtools::opt::IRContext * context,const char * filename)498 void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
499   std::vector<uint32_t> binary;
500   context->module()->ToBinary(&binary, false);
501   DumpShader(binary, filename);
502 }
503 
504 const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
505 
main(int argc,const char ** argv)506 int main(int argc, const char** argv) {
507   std::string in_binary_file;
508   std::string out_binary_file;
509   std::string donors_file;
510   std::string replay_transformations_file;
511   std::vector<std::string> interestingness_test;
512   std::string shrink_transformations_file;
513   std::string shrink_temp_file_prefix = "temp_";
514 
515   spvtools::FuzzerOptions fuzzer_options;
516 
517   FuzzStatus status = ParseFlags(
518       argc, argv, &in_binary_file, &out_binary_file, &donors_file,
519       &replay_transformations_file, &interestingness_test,
520       &shrink_transformations_file, &shrink_temp_file_prefix, &fuzzer_options);
521 
522   if (status.action == FuzzActions::STOP) {
523     return status.code;
524   }
525 
526   std::vector<uint32_t> binary_in;
527   if (!ReadFile<uint32_t>(in_binary_file.c_str(), "rb", &binary_in)) {
528     return 1;
529   }
530 
531   spvtools::fuzz::protobufs::FactSequence initial_facts;
532 
533   // If not found, dot_pos will be std::string::npos, which can be used in
534   // substr to mean "the end of the string"; there is no need to check the
535   // result.
536   size_t dot_pos = in_binary_file.rfind('.');
537   std::string in_facts_file = in_binary_file.substr(0, dot_pos) + ".facts";
538   std::ifstream facts_input(in_facts_file);
539   if (facts_input) {
540     std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)),
541                                   std::istreambuf_iterator<char>());
542     facts_input.close();
543     if (google::protobuf::util::Status::OK !=
544         google::protobuf::util::JsonStringToMessage(facts_json_string,
545                                                     &initial_facts)) {
546       spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data");
547       return 1;
548     }
549   }
550 
551   std::vector<uint32_t> binary_out;
552   spvtools::fuzz::protobufs::TransformationSequence transformations_applied;
553 
554   spv_target_env target_env = kDefaultEnvironment;
555 
556   switch (status.action) {
557     case FuzzActions::FORCE_RENDER_RED:
558       if (!spvtools::fuzz::ForceRenderRed(target_env, binary_in, initial_facts,
559                                           &binary_out)) {
560         return 1;
561       }
562       break;
563     case FuzzActions::FUZZ:
564       if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
565                 donors_file, &binary_out, &transformations_applied)) {
566         return 1;
567       }
568       break;
569     case FuzzActions::REPLAY:
570       if (!Replay(target_env, fuzzer_options, binary_in, initial_facts,
571                   replay_transformations_file, &binary_out,
572                   &transformations_applied)) {
573         return 1;
574       }
575       break;
576     case FuzzActions::SHRINK: {
577       if (!CheckExecuteCommand()) {
578         std::cerr << "could not find shell interpreter for executing a command"
579                   << std::endl;
580         return 1;
581       }
582       if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
583                   shrink_transformations_file, shrink_temp_file_prefix,
584                   interestingness_test, &binary_out,
585                   &transformations_applied)) {
586         return 1;
587       }
588     } break;
589     default:
590       assert(false && "Unknown fuzzer action.");
591       break;
592   }
593 
594   if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
595                            binary_out.size())) {
596     spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error writing out binary");
597     return 1;
598   }
599 
600   if (status.action != FuzzActions::FORCE_RENDER_RED) {
601     // If not found, dot_pos will be std::string::npos, which can be used in
602     // substr to mean "the end of the string"; there is no need to check the
603     // result.
604     dot_pos = out_binary_file.rfind('.');
605     std::string output_file_prefix = out_binary_file.substr(0, dot_pos);
606     std::ofstream transformations_file;
607     transformations_file.open(output_file_prefix + ".transformations",
608                               std::ios::out | std::ios::binary);
609     bool success =
610         transformations_applied.SerializeToOstream(&transformations_file);
611     transformations_file.close();
612     if (!success) {
613       spvtools::Error(FuzzDiagnostic, nullptr, {},
614                       "Error writing out transformations binary");
615       return 1;
616     }
617 
618     std::string json_string;
619     auto json_options = google::protobuf::util::JsonOptions();
620     json_options.add_whitespace = true;
621     auto json_generation_status = google::protobuf::util::MessageToJsonString(
622         transformations_applied, &json_string, json_options);
623     if (json_generation_status != google::protobuf::util::Status::OK) {
624       spvtools::Error(FuzzDiagnostic, nullptr, {},
625                       "Error writing out transformations in JSON format");
626       return 1;
627     }
628 
629     std::ofstream transformations_json_file(output_file_prefix +
630                                             ".transformations_json");
631     transformations_json_file << json_string;
632     transformations_json_file.close();
633   }
634 
635   return 0;
636 }
637