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