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