• 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 Supported validator options are as follows. See `spirv-val --help` for details.
149   --before-hlsl-legalization
150   --relax-block-layout
151   --relax-logical-pointer
152   --relax-struct-store
153   --scalar-block-layout
154   --skip-block-layout
155 )",
156       program, program, program, program);
157 }
158 
159 // Message consumer for this tool.  Used to emit diagnostics during
160 // initialization and setup. Note that |source| and |position| are irrelevant
161 // 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)162 void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/,
163                     const spv_position_t& /*position*/, const char* message) {
164   if (level == SPV_MSG_ERROR) {
165     fprintf(stderr, "error: ");
166   }
167   fprintf(stderr, "%s\n", message);
168 }
169 
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,spvtools::ValidatorOptions * validator_options)170 FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
171                       std::string* out_binary_file, std::string* donors_file,
172                       std::string* replay_transformations_file,
173                       std::vector<std::string>* interestingness_test,
174                       std::string* shrink_transformations_file,
175                       std::string* shrink_temp_file_prefix,
176                       spvtools::FuzzerOptions* fuzzer_options,
177                       spvtools::ValidatorOptions* validator_options) {
178   uint32_t positional_arg_index = 0;
179   bool only_positional_arguments_remain = false;
180   bool force_render_red = false;
181 
182   for (int argi = 1; argi < argc; ++argi) {
183     const char* cur_arg = argv[argi];
184     if ('-' == cur_arg[0] && !only_positional_arguments_remain) {
185       if (0 == strcmp(cur_arg, "--version")) {
186         spvtools::Logf(FuzzDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
187                        spvSoftwareVersionDetailsString());
188         return {FuzzActions::STOP, 0};
189       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
190         PrintUsage(argv[0]);
191         return {FuzzActions::STOP, 0};
192       } else if (0 == strcmp(cur_arg, "-o")) {
193         if (out_binary_file->empty() && argi + 1 < argc) {
194           *out_binary_file = std::string(argv[++argi]);
195         } else {
196           PrintUsage(argv[0]);
197           return {FuzzActions::STOP, 1};
198         }
199       } else if (0 == strncmp(cur_arg, "--donors=", sizeof("--donors=") - 1)) {
200         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
201         *donors_file = std::string(split_flag.second);
202       } else if (0 == strncmp(cur_arg, "--force-render-red",
203                               sizeof("--force-render-red") - 1)) {
204         force_render_red = true;
205       } else if (0 == strncmp(cur_arg, "--fuzzer-pass-validation",
206                               sizeof("--fuzzer-pass-validation") - 1)) {
207         fuzzer_options->enable_fuzzer_pass_validation();
208       } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) {
209         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
210         *replay_transformations_file = std::string(split_flag.second);
211       } else if (0 == strncmp(cur_arg, "--replay-validation",
212                               sizeof("--replay-validation") - 1)) {
213         fuzzer_options->enable_replay_validation();
214       } else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) {
215         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
216         *shrink_transformations_file = std::string(split_flag.second);
217       } else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) {
218         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
219         char* end = nullptr;
220         errno = 0;
221         const auto seed =
222             static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
223         assert(end != split_flag.second.c_str() && errno == 0);
224         fuzzer_options->set_random_seed(seed);
225       } else if (0 == strncmp(cur_arg, "--shrinker-step-limit=",
226                               sizeof("--shrinker-step-limit=") - 1)) {
227         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
228         char* end = nullptr;
229         errno = 0;
230         const auto step_limit =
231             static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
232         assert(end != split_flag.second.c_str() && errno == 0);
233         fuzzer_options->set_shrinker_step_limit(step_limit);
234       } else if (0 == strncmp(cur_arg, "--shrinker-temp-file-prefix=",
235                               sizeof("--shrinker-temp-file-prefix=") - 1)) {
236         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
237         *shrink_temp_file_prefix = std::string(split_flag.second);
238       } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
239         validator_options->SetBeforeHlslLegalization(true);
240       } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
241         validator_options->SetRelaxLogicalPointer(true);
242       } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
243         validator_options->SetRelaxBlockLayout(true);
244       } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
245         validator_options->SetScalarBlockLayout(true);
246       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
247         validator_options->SetSkipBlockLayout(true);
248       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
249         validator_options->SetRelaxStructStore(true);
250       } else if (0 == strcmp(cur_arg, "--")) {
251         only_positional_arguments_remain = true;
252       } else {
253         std::stringstream ss;
254         ss << "Unrecognized argument: " << cur_arg << std::endl;
255         spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str());
256         PrintUsage(argv[0]);
257         return {FuzzActions::STOP, 1};
258       }
259     } else if (positional_arg_index == 0) {
260       // Binary input file name
261       assert(in_binary_file->empty());
262       *in_binary_file = std::string(cur_arg);
263       positional_arg_index++;
264     } else {
265       interestingness_test->push_back(std::string(cur_arg));
266     }
267   }
268 
269   if (in_binary_file->empty()) {
270     spvtools::Error(FuzzDiagnostic, nullptr, {}, "No input file specified");
271     return {FuzzActions::STOP, 1};
272   }
273 
274   if (out_binary_file->empty()) {
275     spvtools::Error(FuzzDiagnostic, nullptr, {}, "-o required");
276     return {FuzzActions::STOP, 1};
277   }
278 
279   auto const_fuzzer_options =
280       static_cast<spv_const_fuzzer_options>(*fuzzer_options);
281   if (force_render_red) {
282     if (!replay_transformations_file->empty() ||
283         !shrink_transformations_file->empty() ||
284         const_fuzzer_options->replay_validation_enabled) {
285       spvtools::Error(FuzzDiagnostic, nullptr, {},
286                       "The --force-render-red argument cannot be used with any "
287                       "other arguments except -o.");
288       return {FuzzActions::STOP, 1};
289     }
290     return {FuzzActions::FORCE_RENDER_RED, 0};
291   }
292 
293   if (replay_transformations_file->empty() &&
294       shrink_transformations_file->empty() &&
295       static_cast<spv_const_fuzzer_options>(*fuzzer_options)
296           ->replay_validation_enabled) {
297     spvtools::Error(FuzzDiagnostic, nullptr, {},
298                     "The --replay-validation argument can only be used with "
299                     "one of the --replay or --shrink arguments.");
300     return {FuzzActions::STOP, 1};
301   }
302 
303   if (shrink_transformations_file->empty() && !interestingness_test->empty()) {
304     spvtools::Error(FuzzDiagnostic, nullptr, {},
305                     "Too many positional arguments specified; extra positional "
306                     "arguments are used as the interestingness function, which "
307                     "are only valid with the --shrink option.");
308     return {FuzzActions::STOP, 1};
309   }
310 
311   if (!shrink_transformations_file->empty() && interestingness_test->empty()) {
312     spvtools::Error(
313         FuzzDiagnostic, nullptr, {},
314         "The --shrink option requires an interestingness function.");
315     return {FuzzActions::STOP, 1};
316   }
317 
318   if (!replay_transformations_file->empty() ||
319       !shrink_transformations_file->empty()) {
320     // Donors should not be provided when replaying or shrinking: they only make
321     // sense during fuzzing.
322     if (!donors_file->empty()) {
323       spvtools::Error(FuzzDiagnostic, nullptr, {},
324                       "The --donors argument is not compatible with --replay "
325                       "nor --shrink.");
326       return {FuzzActions::STOP, 1};
327     }
328   }
329 
330   if (!replay_transformations_file->empty()) {
331     // A replay transformations file was given, thus the tool is being invoked
332     // in replay mode.
333     if (!shrink_transformations_file->empty()) {
334       spvtools::Error(
335           FuzzDiagnostic, nullptr, {},
336           "The --replay and --shrink arguments are mutually exclusive.");
337       return {FuzzActions::STOP, 1};
338     }
339     return {FuzzActions::REPLAY, 0};
340   }
341 
342   if (!shrink_transformations_file->empty()) {
343     // The tool is being invoked in shrink mode.
344     assert(!interestingness_test->empty() &&
345            "An error should have been raised if --shrink was provided without "
346            "an interestingness test.");
347     return {FuzzActions::SHRINK, 0};
348   }
349 
350   // The tool is being invoked in fuzz mode.
351   if (donors_file->empty()) {
352     spvtools::Error(FuzzDiagnostic, nullptr, {},
353                     "Fuzzing requires that the --donors option is used.");
354     return {FuzzActions::STOP, 1};
355   }
356   return {FuzzActions::FUZZ, 0};
357 }
358 
ParseTransformations(const std::string & transformations_file,spvtools::fuzz::protobufs::TransformationSequence * transformations)359 bool ParseTransformations(
360     const std::string& transformations_file,
361     spvtools::fuzz::protobufs::TransformationSequence* transformations) {
362   std::ifstream transformations_stream;
363   transformations_stream.open(transformations_file,
364                               std::ios::in | std::ios::binary);
365   auto parse_success =
366       transformations->ParseFromIstream(&transformations_stream);
367   transformations_stream.close();
368   if (!parse_success) {
369     spvtools::Error(FuzzDiagnostic, nullptr, {},
370                     ("Error reading transformations from file '" +
371                      transformations_file + "'")
372                         .c_str());
373     return false;
374   }
375   return true;
376 }
377 
Replay(const spv_target_env & target_env,spv_const_fuzzer_options fuzzer_options,spv_validator_options validator_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)378 bool Replay(const spv_target_env& target_env,
379             spv_const_fuzzer_options fuzzer_options,
380             spv_validator_options validator_options,
381             const std::vector<uint32_t>& binary_in,
382             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
383             const std::string& replay_transformations_file,
384             std::vector<uint32_t>* binary_out,
385             spvtools::fuzz::protobufs::TransformationSequence*
386                 transformations_applied) {
387   spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
388   if (!ParseTransformations(replay_transformations_file,
389                             &transformation_sequence)) {
390     return false;
391   }
392   spvtools::fuzz::Replayer replayer(
393       target_env, fuzzer_options->replay_validation_enabled, validator_options);
394   replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
395   auto replay_result_status =
396       replayer.Run(binary_in, initial_facts, transformation_sequence,
397                    binary_out, transformations_applied);
398   return !(replay_result_status !=
399            spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
400 }
401 
Shrink(const spv_target_env & target_env,spv_const_fuzzer_options fuzzer_options,spv_validator_options validator_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)402 bool Shrink(const spv_target_env& target_env,
403             spv_const_fuzzer_options fuzzer_options,
404             spv_validator_options validator_options,
405             const std::vector<uint32_t>& binary_in,
406             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
407             const std::string& shrink_transformations_file,
408             const std::string& shrink_temp_file_prefix,
409             const std::vector<std::string>& interestingness_command,
410             std::vector<uint32_t>* binary_out,
411             spvtools::fuzz::protobufs::TransformationSequence*
412                 transformations_applied) {
413   spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
414   if (!ParseTransformations(shrink_transformations_file,
415                             &transformation_sequence)) {
416     return false;
417   }
418   spvtools::fuzz::Shrinker shrinker(
419       target_env, fuzzer_options->shrinker_step_limit,
420       fuzzer_options->replay_validation_enabled, validator_options);
421   shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
422 
423   assert(!interestingness_command.empty() &&
424          "An error should have been raised because the interestingness_command "
425          "is empty.");
426   std::stringstream joined;
427   joined << interestingness_command[0];
428   for (size_t i = 1, size = interestingness_command.size(); i < size; ++i) {
429     joined << " " << interestingness_command[i];
430   }
431   std::string interestingness_command_joined = joined.str();
432 
433   spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function =
434       [interestingness_command_joined, shrink_temp_file_prefix](
435           std::vector<uint32_t> binary, uint32_t reductions_applied) -> bool {
436     std::stringstream ss;
437     ss << shrink_temp_file_prefix << std::setw(4) << std::setfill('0')
438        << reductions_applied << ".spv";
439     const auto spv_file = ss.str();
440     const std::string command = interestingness_command_joined + " " + spv_file;
441     auto write_file_succeeded =
442         WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
443     (void)(write_file_succeeded);
444     assert(write_file_succeeded);
445     return ExecuteCommand(command);
446   };
447 
448   auto shrink_result_status = shrinker.Run(
449       binary_in, initial_facts, transformation_sequence,
450       interestingness_function, binary_out, transformations_applied);
451   return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete ==
452              shrink_result_status ||
453          spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached ==
454              shrink_result_status;
455 }
456 
Fuzz(const spv_target_env & target_env,spv_const_fuzzer_options fuzzer_options,spv_validator_options validator_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)457 bool Fuzz(const spv_target_env& target_env,
458           spv_const_fuzzer_options fuzzer_options,
459           spv_validator_options validator_options,
460           const std::vector<uint32_t>& binary_in,
461           const spvtools::fuzz::protobufs::FactSequence& initial_facts,
462           const std::string& donors, std::vector<uint32_t>* binary_out,
463           spvtools::fuzz::protobufs::TransformationSequence*
464               transformations_applied) {
465   auto message_consumer = spvtools::utils::CLIMessageConsumer;
466 
467   std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donor_suppliers;
468 
469   std::ifstream donors_file(donors);
470   if (!donors_file) {
471     spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error opening donors file");
472     return false;
473   }
474   std::string donor_filename;
475   while (std::getline(donors_file, donor_filename)) {
476     donor_suppliers.emplace_back(
477         [donor_filename, message_consumer,
478          target_env]() -> std::unique_ptr<spvtools::opt::IRContext> {
479           std::vector<uint32_t> donor_binary;
480           if (!ReadFile<uint32_t>(donor_filename.c_str(), "rb",
481                                   &donor_binary)) {
482             return nullptr;
483           }
484           return spvtools::BuildModule(target_env, message_consumer,
485                                        donor_binary.data(),
486                                        donor_binary.size());
487         });
488   }
489 
490   spvtools::fuzz::Fuzzer fuzzer(
491       target_env,
492       fuzzer_options->has_random_seed
493           ? fuzzer_options->random_seed
494           : static_cast<uint32_t>(std::random_device()()),
495       fuzzer_options->fuzzer_pass_validation_enabled, validator_options);
496   fuzzer.SetMessageConsumer(message_consumer);
497   auto fuzz_result_status =
498       fuzzer.Run(binary_in, initial_facts, donor_suppliers, binary_out,
499                  transformations_applied);
500   if (fuzz_result_status !=
501       spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
502     spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
503     return false;
504   }
505   return true;
506 }
507 
508 }  // namespace
509 
510 // Dumps |binary| to file |filename|. Useful for interactive debugging.
DumpShader(const std::vector<uint32_t> & binary,const char * filename)511 void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
512   auto write_file_succeeded =
513       WriteFile(filename, "wb", &binary[0], binary.size());
514   if (!write_file_succeeded) {
515     std::cerr << "Failed to dump shader" << std::endl;
516   }
517 }
518 
519 // Dumps the SPIRV-V module in |context| to file |filename|. Useful for
520 // interactive debugging.
DumpShader(spvtools::opt::IRContext * context,const char * filename)521 void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
522   std::vector<uint32_t> binary;
523   context->module()->ToBinary(&binary, false);
524   DumpShader(binary, filename);
525 }
526 
527 const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
528 
main(int argc,const char ** argv)529 int main(int argc, const char** argv) {
530   std::string in_binary_file;
531   std::string out_binary_file;
532   std::string donors_file;
533   std::string replay_transformations_file;
534   std::vector<std::string> interestingness_test;
535   std::string shrink_transformations_file;
536   std::string shrink_temp_file_prefix = "temp_";
537 
538   spvtools::FuzzerOptions fuzzer_options;
539   spvtools::ValidatorOptions validator_options;
540 
541   FuzzStatus status =
542       ParseFlags(argc, argv, &in_binary_file, &out_binary_file, &donors_file,
543                  &replay_transformations_file, &interestingness_test,
544                  &shrink_transformations_file, &shrink_temp_file_prefix,
545                  &fuzzer_options, &validator_options);
546 
547   if (status.action == FuzzActions::STOP) {
548     return status.code;
549   }
550 
551   std::vector<uint32_t> binary_in;
552   if (!ReadFile<uint32_t>(in_binary_file.c_str(), "rb", &binary_in)) {
553     return 1;
554   }
555 
556   spvtools::fuzz::protobufs::FactSequence initial_facts;
557 
558   // If not found, dot_pos will be std::string::npos, which can be used in
559   // substr to mean "the end of the string"; there is no need to check the
560   // result.
561   size_t dot_pos = in_binary_file.rfind('.');
562   std::string in_facts_file = in_binary_file.substr(0, dot_pos) + ".facts";
563   std::ifstream facts_input(in_facts_file);
564   if (facts_input) {
565     std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)),
566                                   std::istreambuf_iterator<char>());
567     facts_input.close();
568     if (google::protobuf::util::Status::OK !=
569         google::protobuf::util::JsonStringToMessage(facts_json_string,
570                                                     &initial_facts)) {
571       spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data");
572       return 1;
573     }
574   }
575 
576   std::vector<uint32_t> binary_out;
577   spvtools::fuzz::protobufs::TransformationSequence transformations_applied;
578 
579   spv_target_env target_env = kDefaultEnvironment;
580 
581   switch (status.action) {
582     case FuzzActions::FORCE_RENDER_RED:
583       if (!spvtools::fuzz::ForceRenderRed(target_env, validator_options,
584                                           binary_in, initial_facts,
585                                           &binary_out)) {
586         return 1;
587       }
588       break;
589     case FuzzActions::FUZZ:
590       if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in,
591                 initial_facts, donors_file, &binary_out,
592                 &transformations_applied)) {
593         return 1;
594       }
595       break;
596     case FuzzActions::REPLAY:
597       if (!Replay(target_env, fuzzer_options, validator_options, binary_in,
598                   initial_facts, replay_transformations_file, &binary_out,
599                   &transformations_applied)) {
600         return 1;
601       }
602       break;
603     case FuzzActions::SHRINK: {
604       if (!CheckExecuteCommand()) {
605         std::cerr << "could not find shell interpreter for executing a command"
606                   << std::endl;
607         return 1;
608       }
609       if (!Shrink(target_env, fuzzer_options, validator_options, binary_in,
610                   initial_facts, shrink_transformations_file,
611                   shrink_temp_file_prefix, interestingness_test, &binary_out,
612                   &transformations_applied)) {
613         return 1;
614       }
615     } break;
616     default:
617       assert(false && "Unknown fuzzer action.");
618       break;
619   }
620 
621   if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
622                            binary_out.size())) {
623     spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error writing out binary");
624     return 1;
625   }
626 
627   if (status.action != FuzzActions::FORCE_RENDER_RED) {
628     // If not found, dot_pos will be std::string::npos, which can be used in
629     // substr to mean "the end of the string"; there is no need to check the
630     // result.
631     dot_pos = out_binary_file.rfind('.');
632     std::string output_file_prefix = out_binary_file.substr(0, dot_pos);
633     std::ofstream transformations_file;
634     transformations_file.open(output_file_prefix + ".transformations",
635                               std::ios::out | std::ios::binary);
636     bool success =
637         transformations_applied.SerializeToOstream(&transformations_file);
638     transformations_file.close();
639     if (!success) {
640       spvtools::Error(FuzzDiagnostic, nullptr, {},
641                       "Error writing out transformations binary");
642       return 1;
643     }
644 
645     std::string json_string;
646     auto json_options = google::protobuf::util::JsonOptions();
647     json_options.add_whitespace = true;
648     auto json_generation_status = google::protobuf::util::MessageToJsonString(
649         transformations_applied, &json_string, json_options);
650     if (json_generation_status != google::protobuf::util::Status::OK) {
651       spvtools::Error(FuzzDiagnostic, nullptr, {},
652                       "Error writing out transformations in JSON format");
653       return 1;
654     }
655 
656     std::ofstream transformations_json_file(output_file_prefix +
657                                             ".transformations_json");
658     transformations_json_file << json_string;
659     transformations_json_file.close();
660   }
661 
662   return 0;
663 }
664