|  | // Copyright (c) 2019 Google LLC | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #include <cassert> | 
|  | #include <cerrno> | 
|  | #include <cstring> | 
|  | #include <fstream> | 
|  | #include <memory> | 
|  | #include <random> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  |  | 
|  | #include "source/fuzz/force_render_red.h" | 
|  | #include "source/fuzz/fuzzer.h" | 
|  | #include "source/fuzz/fuzzer_util.h" | 
|  | #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" | 
|  | #include "source/fuzz/pseudo_random_generator.h" | 
|  | #include "source/fuzz/replayer.h" | 
|  | #include "source/fuzz/shrinker.h" | 
|  | #include "source/opt/build_module.h" | 
|  | #include "source/opt/ir_context.h" | 
|  | #include "source/opt/log.h" | 
|  | #include "source/spirv_fuzzer_options.h" | 
|  | #include "source/util/make_unique.h" | 
|  | #include "source/util/string_utils.h" | 
|  | #include "tools/io.h" | 
|  | #include "tools/util/cli_consumer.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | enum class FuzzingTarget { kSpirv, kWgsl }; | 
|  |  | 
|  | // Check that the std::system function can actually be used. | 
|  | bool CheckExecuteCommand() { | 
|  | int res = std::system(nullptr); | 
|  | return res != 0; | 
|  | } | 
|  |  | 
|  | // Execute a command using the shell. | 
|  | // Returns true if and only if the command's exit status was 0. | 
|  | bool ExecuteCommand(const std::string& command) { | 
|  | errno = 0; | 
|  | int status = std::system(command.c_str()); | 
|  | assert(errno == 0 && "failed to execute command"); | 
|  | // The result returned by 'system' is implementation-defined, but is | 
|  | // usually the case that the returned value is 0 when the command's exit | 
|  | // code was 0.  We are assuming that here, and that's all we depend on. | 
|  | return status == 0; | 
|  | } | 
|  |  | 
|  | // Status and actions to perform after parsing command-line arguments. | 
|  | enum class FuzzActions { | 
|  | FORCE_RENDER_RED,  // Turn the shader into a form such that it is guaranteed | 
|  | // to render a red image. | 
|  | FUZZ,    // Run the fuzzer to apply transformations in a randomized fashion. | 
|  | REPLAY,  // Replay an existing sequence of transformations. | 
|  | SHRINK,  // Shrink an existing sequence of transformations with respect to an | 
|  | // interestingness function. | 
|  | STOP     // Do nothing. | 
|  | }; | 
|  |  | 
|  | struct FuzzStatus { | 
|  | FuzzActions action; | 
|  | int code; | 
|  | }; | 
|  |  | 
|  | void PrintUsage(const char* program) { | 
|  | // NOTE: Please maintain flags in lexicographical order. | 
|  | printf( | 
|  | R"(%s - Fuzzes an equivalent SPIR-V binary based on a given binary. | 
|  |  | 
|  | USAGE: %s [options] <input.spv> -o <output.spv> \ | 
|  | --donors=<donors.txt> | 
|  | USAGE: %s [options] <input.spv> -o <output.spv> \ | 
|  | --shrink=<input.transformations> -- <interestingness_test> [args...] | 
|  |  | 
|  | The SPIR-V binary is read from <input.spv>.  If <input.facts> is also present, | 
|  | facts about the SPIR-V binary are read from this file. | 
|  |  | 
|  | The transformed SPIR-V binary is written to <output.spv>.  Human-readable and | 
|  | binary representations of the transformations that were applied are written to | 
|  | <output.transformations_json> and <output.transformations>, respectively. | 
|  |  | 
|  | When passing --shrink=<input.transformations> an <interestingness_test> | 
|  | must also be provided; this is the path to a script that returns 0 if and only | 
|  | if a given SPIR-V binary is interesting.  The SPIR-V binary will be passed to | 
|  | the script as an argument after any other provided arguments [args...].  The | 
|  | "--" characters are optional but denote that all arguments that follow are | 
|  | positional arguments and thus will be forwarded to the interestingness script, | 
|  | and not parsed by %s. | 
|  |  | 
|  | NOTE: The fuzzer is a work in progress. | 
|  |  | 
|  | Options (in lexicographical order): | 
|  |  | 
|  | -h, --help | 
|  | Print this help. | 
|  | --donors= | 
|  | File specifying a series of donor files, one per line.  Must be | 
|  | provided if the tool is invoked in fuzzing mode; incompatible | 
|  | with replay and shrink modes.  The file should be empty if no | 
|  | donors are to be used. | 
|  | --enable-all-passes | 
|  | By default, spirv-fuzz follows the philosophy of "swarm testing" | 
|  | (Groce et al., 2012): only a subset of fuzzer passes are enabled | 
|  | on any given fuzzer run, with the subset being chosen randomly. | 
|  | This flag instead forces *all* fuzzer passes to be enabled.  When | 
|  | running spirv-fuzz many times this is likely to produce *less* | 
|  | diverse fuzzed modules than when swarm testing is used.  The | 
|  | purpose of the flag is to allow that hypothesis to be tested. | 
|  | --force-render-red | 
|  | Transforms the input shader into a shader that writes red to the | 
|  | output buffer, and then captures the original shader as the body | 
|  | of a conditional with a dynamically false guard.  Exploits input | 
|  | facts to make the guard non-obviously false.  This option is a | 
|  | helper for massaging crash-inducing tests into a runnable | 
|  | format; it does not perform any fuzzing. | 
|  | --fuzzer-pass-validation | 
|  | Run the validator after applying each fuzzer pass during | 
|  | fuzzing.  Aborts fuzzing early if an invalid binary is created. | 
|  | Useful for debugging spirv-fuzz. | 
|  | --repeated-pass-strategy= | 
|  | Available strategies are: | 
|  | - looped (the default): a sequence of fuzzer passes is chosen at | 
|  | the start of fuzzing, via randomly choosing enabled passes, and | 
|  | augmenting these choices with fuzzer passes that it is | 
|  | recommended to run subsequently.  Fuzzing then involves | 
|  | repeatedly applying this fixed sequence of passes. | 
|  | - random: each time a fuzzer pass is requested, this strategy | 
|  | either provides one at random from the set of enabled passes, | 
|  | or provides a pass that has been recommended based on a pass | 
|  | that was used previously. | 
|  | - simple: each time a fuzzer pass is requested, one is provided | 
|  | at random from the set of enabled passes. | 
|  | --fuzzing-target= | 
|  | This option will adjust probabilities of applying certain | 
|  | transformations s.t. the module always remains valid according | 
|  | to the semantics of some fuzzing target. Available targets: | 
|  | - spir-v - module is valid according to the SPIR-V spec. | 
|  | - wgsl - module is valid according to the WGSL spec. | 
|  | --replay | 
|  | File from which to read a sequence of transformations to replay | 
|  | (instead of fuzzing) | 
|  | --replay-range= | 
|  | Signed 32-bit integer.  If set to a positive value N, only the | 
|  | first N transformations will be applied during replay.  If set to | 
|  | a negative value -N, all but the final N transformations will be | 
|  | applied during replay.  If set to 0 (the default), all | 
|  | transformations will be applied during replay.  Ignored unless | 
|  | --replay is used. | 
|  | --replay-validation | 
|  | Run the validator after applying each transformation during | 
|  | replay (including the replay that occurs during shrinking). | 
|  | Aborts if an invalid binary is created.  Useful for debugging | 
|  | spirv-fuzz. | 
|  | --seed= | 
|  | Unsigned 32-bit integer seed to control random number | 
|  | generation. | 
|  | --shrink= | 
|  | File from which to read a sequence of transformations to shrink | 
|  | (instead of fuzzing) | 
|  | --shrinker-step-limit= | 
|  | Unsigned 32-bit integer specifying maximum number of steps the | 
|  | shrinker will take before giving up.  Ignored unless --shrink | 
|  | is used. | 
|  | --shrinker-temp-file-prefix= | 
|  | Specifies a temporary file prefix that will be used to output | 
|  | temporary shader files during shrinking.  A number and .spv | 
|  | extension will be added.  The default is "temp_", which will | 
|  | cause files like "temp_0001.spv" to be output to the current | 
|  | directory.  Ignored unless --shrink is used. | 
|  | --version | 
|  | Display fuzzer version information. | 
|  |  | 
|  | Supported validator options are as follows. See `spirv-val --help` for details. | 
|  | --before-hlsl-legalization | 
|  | --relax-block-layout | 
|  | --relax-logical-pointer | 
|  | --relax-struct-store | 
|  | --scalar-block-layout | 
|  | --skip-block-layout | 
|  | )", | 
|  | program, program, program, program); | 
|  | } | 
|  |  | 
|  | // Message consumer for this tool.  Used to emit diagnostics during | 
|  | // initialization and setup. Note that |source| and |position| are irrelevant | 
|  | // here because we are still not processing a SPIR-V input file. | 
|  | void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/, | 
|  | const spv_position_t& /*position*/, const char* message) { | 
|  | if (level == SPV_MSG_ERROR) { | 
|  | fprintf(stderr, "error: "); | 
|  | } | 
|  | fprintf(stderr, "%s\n", message); | 
|  | } | 
|  |  | 
|  | FuzzStatus 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::fuzz::RepeatedPassStrategy* repeated_pass_strategy, | 
|  | FuzzingTarget* fuzzing_target, spvtools::FuzzerOptions* fuzzer_options, | 
|  | spvtools::ValidatorOptions* validator_options) { | 
|  | uint32_t positional_arg_index = 0; | 
|  | bool only_positional_arguments_remain = false; | 
|  | bool force_render_red = false; | 
|  |  | 
|  | *repeated_pass_strategy = | 
|  | spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations; | 
|  |  | 
|  | for (int argi = 1; argi < argc; ++argi) { | 
|  | const char* cur_arg = argv[argi]; | 
|  | if ('-' == cur_arg[0] && !only_positional_arguments_remain) { | 
|  | if (0 == strcmp(cur_arg, "--version")) { | 
|  | spvtools::Logf(FuzzDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n", | 
|  | spvSoftwareVersionDetailsString()); | 
|  | return {FuzzActions::STOP, 0}; | 
|  | } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) { | 
|  | PrintUsage(argv[0]); | 
|  | return {FuzzActions::STOP, 0}; | 
|  | } else if (0 == strcmp(cur_arg, "-o")) { | 
|  | if (out_binary_file->empty() && argi + 1 < argc) { | 
|  | *out_binary_file = std::string(argv[++argi]); | 
|  | } else { | 
|  | PrintUsage(argv[0]); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  | } else if (0 == strncmp(cur_arg, "--donors=", sizeof("--donors=") - 1)) { | 
|  | const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); | 
|  | *donors_file = std::string(split_flag.second); | 
|  | } else if (0 == strncmp(cur_arg, "--enable-all-passes", | 
|  | sizeof("--enable-all-passes") - 1)) { | 
|  | fuzzer_options->enable_all_passes(); | 
|  | } else if (0 == strncmp(cur_arg, "--force-render-red", | 
|  | sizeof("--force-render-red") - 1)) { | 
|  | force_render_red = true; | 
|  | } else if (0 == strncmp(cur_arg, "--fuzzer-pass-validation", | 
|  | sizeof("--fuzzer-pass-validation") - 1)) { | 
|  | fuzzer_options->enable_fuzzer_pass_validation(); | 
|  | } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) { | 
|  | const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); | 
|  | *replay_transformations_file = std::string(split_flag.second); | 
|  | } else if (0 == strncmp(cur_arg, "--repeated-pass-strategy=", | 
|  | sizeof("--repeated-pass-strategy=") - 1)) { | 
|  | std::string strategy = spvtools::utils::SplitFlagArgs(cur_arg).second; | 
|  | if (strategy == "looped") { | 
|  | *repeated_pass_strategy = | 
|  | spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations; | 
|  | } else if (strategy == "random") { | 
|  | *repeated_pass_strategy = | 
|  | spvtools::fuzz::RepeatedPassStrategy::kRandomWithRecommendations; | 
|  | } else if (strategy == "simple") { | 
|  | *repeated_pass_strategy = | 
|  | spvtools::fuzz::RepeatedPassStrategy::kSimple; | 
|  | } else { | 
|  | std::stringstream ss; | 
|  | ss << "Unknown repeated pass strategy '" << strategy << "'" | 
|  | << std::endl; | 
|  | ss << "Valid options are 'looped', 'random' and 'simple'."; | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str()); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  | } else if (0 == strncmp(cur_arg, "--fuzzing-target=", | 
|  | sizeof("--fuzzing-target=") - 1)) { | 
|  | std::string target = spvtools::utils::SplitFlagArgs(cur_arg).second; | 
|  | if (target == "spir-v") { | 
|  | *fuzzing_target = FuzzingTarget::kSpirv; | 
|  | } else if (target == "wgsl") { | 
|  | *fuzzing_target = FuzzingTarget::kWgsl; | 
|  | } else { | 
|  | std::stringstream ss; | 
|  | ss << "Unknown fuzzing target '" << target << "'" << std::endl; | 
|  | ss << "Valid options are 'spir-v' and 'wgsl'."; | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str()); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  | } else if (0 == strncmp(cur_arg, "--replay-range=", | 
|  | sizeof("--replay-range=") - 1)) { | 
|  | const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); | 
|  | char* end = nullptr; | 
|  | errno = 0; | 
|  | const auto replay_range = | 
|  | static_cast<int32_t>(strtol(split_flag.second.c_str(), &end, 10)); | 
|  | assert(end != split_flag.second.c_str() && errno == 0); | 
|  | fuzzer_options->set_replay_range(replay_range); | 
|  | } else if (0 == strncmp(cur_arg, "--replay-validation", | 
|  | sizeof("--replay-validation") - 1)) { | 
|  | fuzzer_options->enable_replay_validation(); | 
|  | } else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) { | 
|  | const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); | 
|  | *shrink_transformations_file = std::string(split_flag.second); | 
|  | } else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) { | 
|  | const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); | 
|  | char* end = nullptr; | 
|  | errno = 0; | 
|  | const auto seed = | 
|  | static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10)); | 
|  | assert(end != split_flag.second.c_str() && errno == 0); | 
|  | fuzzer_options->set_random_seed(seed); | 
|  | } else if (0 == strncmp(cur_arg, "--shrinker-step-limit=", | 
|  | sizeof("--shrinker-step-limit=") - 1)) { | 
|  | const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); | 
|  | char* end = nullptr; | 
|  | errno = 0; | 
|  | const auto step_limit = | 
|  | static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10)); | 
|  | assert(end != split_flag.second.c_str() && errno == 0); | 
|  | fuzzer_options->set_shrinker_step_limit(step_limit); | 
|  | } else if (0 == strncmp(cur_arg, "--shrinker-temp-file-prefix=", | 
|  | sizeof("--shrinker-temp-file-prefix=") - 1)) { | 
|  | const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); | 
|  | *shrink_temp_file_prefix = std::string(split_flag.second); | 
|  | } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) { | 
|  | validator_options->SetBeforeHlslLegalization(true); | 
|  | } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) { | 
|  | validator_options->SetRelaxLogicalPointer(true); | 
|  | } else if (0 == strcmp(cur_arg, "--relax-block-layout")) { | 
|  | validator_options->SetRelaxBlockLayout(true); | 
|  | } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) { | 
|  | validator_options->SetScalarBlockLayout(true); | 
|  | } else if (0 == strcmp(cur_arg, "--skip-block-layout")) { | 
|  | validator_options->SetSkipBlockLayout(true); | 
|  | } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { | 
|  | validator_options->SetRelaxStructStore(true); | 
|  | } else if (0 == strcmp(cur_arg, "--")) { | 
|  | only_positional_arguments_remain = true; | 
|  | } else { | 
|  | std::stringstream ss; | 
|  | ss << "Unrecognized argument: " << cur_arg << std::endl; | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str()); | 
|  | PrintUsage(argv[0]); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  | } else if (positional_arg_index == 0) { | 
|  | // Binary input file name | 
|  | assert(in_binary_file->empty()); | 
|  | *in_binary_file = std::string(cur_arg); | 
|  | positional_arg_index++; | 
|  | } else { | 
|  | interestingness_test->push_back(std::string(cur_arg)); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (in_binary_file->empty()) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, "No input file specified"); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  |  | 
|  | if (out_binary_file->empty()) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, "-o required"); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  |  | 
|  | auto const_fuzzer_options = | 
|  | static_cast<spv_const_fuzzer_options>(*fuzzer_options); | 
|  | if (force_render_red) { | 
|  | if (!replay_transformations_file->empty() || | 
|  | !shrink_transformations_file->empty() || | 
|  | const_fuzzer_options->replay_validation_enabled) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, | 
|  | "The --force-render-red argument cannot be used with any " | 
|  | "other arguments except -o."); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  | return {FuzzActions::FORCE_RENDER_RED, 0}; | 
|  | } | 
|  |  | 
|  | if (replay_transformations_file->empty() && | 
|  | shrink_transformations_file->empty() && | 
|  | static_cast<spv_const_fuzzer_options>(*fuzzer_options) | 
|  | ->replay_validation_enabled) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, | 
|  | "The --replay-validation argument can only be used with " | 
|  | "one of the --replay or --shrink arguments."); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  |  | 
|  | if (shrink_transformations_file->empty() && !interestingness_test->empty()) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, | 
|  | "Too many positional arguments specified; extra positional " | 
|  | "arguments are used as the interestingness function, which " | 
|  | "are only valid with the --shrink option."); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  |  | 
|  | if (!shrink_transformations_file->empty() && interestingness_test->empty()) { | 
|  | spvtools::Error( | 
|  | FuzzDiagnostic, nullptr, {}, | 
|  | "The --shrink option requires an interestingness function."); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  |  | 
|  | if (!replay_transformations_file->empty() || | 
|  | !shrink_transformations_file->empty()) { | 
|  | // Donors should not be provided when replaying or shrinking: they only make | 
|  | // sense during fuzzing. | 
|  | if (!donors_file->empty()) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, | 
|  | "The --donors argument is not compatible with --replay " | 
|  | "nor --shrink."); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!replay_transformations_file->empty()) { | 
|  | // A replay transformations file was given, thus the tool is being invoked | 
|  | // in replay mode. | 
|  | if (!shrink_transformations_file->empty()) { | 
|  | spvtools::Error( | 
|  | FuzzDiagnostic, nullptr, {}, | 
|  | "The --replay and --shrink arguments are mutually exclusive."); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  | return {FuzzActions::REPLAY, 0}; | 
|  | } | 
|  |  | 
|  | if (!shrink_transformations_file->empty()) { | 
|  | // The tool is being invoked in shrink mode. | 
|  | assert(!interestingness_test->empty() && | 
|  | "An error should have been raised if --shrink was provided without " | 
|  | "an interestingness test."); | 
|  | return {FuzzActions::SHRINK, 0}; | 
|  | } | 
|  |  | 
|  | // The tool is being invoked in fuzz mode. | 
|  | if (donors_file->empty()) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, | 
|  | "Fuzzing requires that the --donors option is used."); | 
|  | return {FuzzActions::STOP, 1}; | 
|  | } | 
|  | return {FuzzActions::FUZZ, 0}; | 
|  | } | 
|  |  | 
|  | bool ParseTransformations( | 
|  | const std::string& transformations_file, | 
|  | spvtools::fuzz::protobufs::TransformationSequence* transformations) { | 
|  | std::ifstream transformations_stream; | 
|  | transformations_stream.open(transformations_file, | 
|  | std::ios::in | std::ios::binary); | 
|  | auto parse_success = | 
|  | transformations->ParseFromIstream(&transformations_stream); | 
|  | transformations_stream.close(); | 
|  | if (!parse_success) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, | 
|  | ("Error reading transformations from file '" + | 
|  | transformations_file + "'") | 
|  | .c_str()); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool 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) { | 
|  | spvtools::fuzz::protobufs::TransformationSequence transformation_sequence; | 
|  | if (!ParseTransformations(replay_transformations_file, | 
|  | &transformation_sequence)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32_t num_transformations_to_apply; | 
|  | if (fuzzer_options->replay_range > 0) { | 
|  | // We have a positive replay range, N.  We would like transformations | 
|  | // [0, N), truncated to the number of available transformations if N is too | 
|  | // large. | 
|  | num_transformations_to_apply = static_cast<uint32_t>( | 
|  | std::min(fuzzer_options->replay_range, | 
|  | transformation_sequence.transformation_size())); | 
|  | } else { | 
|  | // We have non-positive replay range, -N (where N may be 0).  We would like | 
|  | // transformations [0, num_transformations - N), or no transformations if N | 
|  | // is too large. | 
|  | num_transformations_to_apply = static_cast<uint32_t>( | 
|  | std::max(0, transformation_sequence.transformation_size() + | 
|  | fuzzer_options->replay_range)); | 
|  | } | 
|  |  | 
|  | auto replay_result = | 
|  | spvtools::fuzz::Replayer( | 
|  | target_env, spvtools::utils::CLIMessageConsumer, binary_in, | 
|  | initial_facts, transformation_sequence, num_transformations_to_apply, | 
|  | fuzzer_options->replay_validation_enabled, validator_options) | 
|  | .Run(); | 
|  | replay_result.transformed_module->module()->ToBinary(binary_out, false); | 
|  | *transformations_applied = std::move(replay_result.applied_transformations); | 
|  | return replay_result.status == | 
|  | spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete; | 
|  | } | 
|  |  | 
|  | bool 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) { | 
|  | spvtools::fuzz::protobufs::TransformationSequence transformation_sequence; | 
|  | if (!ParseTransformations(shrink_transformations_file, | 
|  | &transformation_sequence)) { | 
|  | return false; | 
|  | } | 
|  | assert(!interestingness_command.empty() && | 
|  | "An error should have been raised because the interestingness_command " | 
|  | "is empty."); | 
|  | std::stringstream joined; | 
|  | joined << interestingness_command[0]; | 
|  | for (size_t i = 1, size = interestingness_command.size(); i < size; ++i) { | 
|  | joined << " " << interestingness_command[i]; | 
|  | } | 
|  | std::string interestingness_command_joined = joined.str(); | 
|  |  | 
|  | spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function = | 
|  | [interestingness_command_joined, shrink_temp_file_prefix]( | 
|  | std::vector<uint32_t> binary, uint32_t reductions_applied) -> bool { | 
|  | std::stringstream ss; | 
|  | ss << shrink_temp_file_prefix << std::setw(4) << std::setfill('0') | 
|  | << reductions_applied << ".spv"; | 
|  | const auto spv_file = ss.str(); | 
|  | const std::string command = interestingness_command_joined + " " + spv_file; | 
|  | auto write_file_succeeded = | 
|  | WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size()); | 
|  | (void)(write_file_succeeded); | 
|  | assert(write_file_succeeded); | 
|  | return ExecuteCommand(command); | 
|  | }; | 
|  |  | 
|  | auto shrink_result = | 
|  | spvtools::fuzz::Shrinker( | 
|  | target_env, spvtools::utils::CLIMessageConsumer, binary_in, | 
|  | initial_facts, transformation_sequence, interestingness_function, | 
|  | fuzzer_options->shrinker_step_limit, | 
|  | fuzzer_options->replay_validation_enabled, validator_options) | 
|  | .Run(); | 
|  |  | 
|  | *binary_out = std::move(shrink_result.transformed_binary); | 
|  | *transformations_applied = std::move(shrink_result.applied_transformations); | 
|  | return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete == | 
|  | shrink_result.status || | 
|  | spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached == | 
|  | shrink_result.status; | 
|  | } | 
|  |  | 
|  | bool 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, | 
|  | spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy, | 
|  | FuzzingTarget fuzzing_target, std::vector<uint32_t>* binary_out, | 
|  | spvtools::fuzz::protobufs::TransformationSequence* | 
|  | transformations_applied) { | 
|  | auto message_consumer = spvtools::utils::CLIMessageConsumer; | 
|  |  | 
|  | std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donor_suppliers; | 
|  |  | 
|  | std::ifstream donors_file(donors); | 
|  | if (!donors_file) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error opening donors file"); | 
|  | return false; | 
|  | } | 
|  | std::string donor_filename; | 
|  | while (std::getline(donors_file, donor_filename)) { | 
|  | donor_suppliers.emplace_back( | 
|  | [donor_filename, message_consumer, | 
|  | target_env]() -> std::unique_ptr<spvtools::opt::IRContext> { | 
|  | std::vector<uint32_t> donor_binary; | 
|  | if (!ReadBinaryFile<uint32_t>(donor_filename.c_str(), | 
|  | &donor_binary)) { | 
|  | return nullptr; | 
|  | } | 
|  | return spvtools::BuildModule(target_env, message_consumer, | 
|  | donor_binary.data(), | 
|  | donor_binary.size()); | 
|  | }); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<spvtools::opt::IRContext> ir_context; | 
|  | if (!spvtools::fuzz::fuzzerutil::BuildIRContext(target_env, message_consumer, | 
|  | binary_in, validator_options, | 
|  | &ir_context)) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, "Initial binary is invalid"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | assert((fuzzing_target == FuzzingTarget::kWgsl || | 
|  | fuzzing_target == FuzzingTarget::kSpirv) && | 
|  | "Not all fuzzing targets are handled"); | 
|  | auto fuzzer_context = spvtools::MakeUnique<spvtools::fuzz::FuzzerContext>( | 
|  | spvtools::MakeUnique<spvtools::fuzz::PseudoRandomGenerator>( | 
|  | fuzzer_options->has_random_seed | 
|  | ? fuzzer_options->random_seed | 
|  | : static_cast<uint32_t>(std::random_device()())), | 
|  | spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()), | 
|  | fuzzing_target == FuzzingTarget::kWgsl); | 
|  |  | 
|  | auto transformation_context = | 
|  | spvtools::MakeUnique<spvtools::fuzz::TransformationContext>( | 
|  | spvtools::MakeUnique<spvtools::fuzz::FactManager>(ir_context.get()), | 
|  | validator_options); | 
|  | transformation_context->GetFactManager()->AddInitialFacts(message_consumer, | 
|  | initial_facts); | 
|  |  | 
|  | spvtools::fuzz::Fuzzer fuzzer( | 
|  | std::move(ir_context), std::move(transformation_context), | 
|  | std::move(fuzzer_context), message_consumer, donor_suppliers, | 
|  | fuzzer_options->all_passes_enabled, repeated_pass_strategy, | 
|  | fuzzer_options->fuzzer_pass_validation_enabled, validator_options, false); | 
|  | auto fuzz_result = fuzzer.Run(0); | 
|  | if (fuzz_result.status == | 
|  | spvtools::fuzz::Fuzzer::Status::kFuzzerPassLedToInvalidModule) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | fuzzer.GetIRContext()->module()->ToBinary(binary_out, true); | 
|  | *transformations_applied = fuzzer.GetTransformationSequence(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Dumps |binary| to file |filename|. Useful for interactive debugging. | 
|  | void DumpShader(const std::vector<uint32_t>& binary, const char* filename) { | 
|  | auto write_file_succeeded = | 
|  | WriteFile(filename, "wb", &binary[0], binary.size()); | 
|  | if (!write_file_succeeded) { | 
|  | std::cerr << "Failed to dump shader" << std::endl; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Dumps the SPIRV-V module in |context| to file |filename|. Useful for | 
|  | // interactive debugging. | 
|  | void DumpShader(spvtools::opt::IRContext* context, const char* filename) { | 
|  | std::vector<uint32_t> binary; | 
|  | context->module()->ToBinary(&binary, false); | 
|  | DumpShader(binary, filename); | 
|  | } | 
|  |  | 
|  | // Dumps |transformations| to file |filename| in binary format. Useful for | 
|  | // interactive debugging. | 
|  | void DumpTransformationsBinary( | 
|  | const spvtools::fuzz::protobufs::TransformationSequence& transformations, | 
|  | const char* filename) { | 
|  | std::ofstream transformations_file; | 
|  | transformations_file.open(filename, std::ios::out | std::ios::binary); | 
|  | transformations.SerializeToOstream(&transformations_file); | 
|  | transformations_file.close(); | 
|  | } | 
|  |  | 
|  | // Dumps |transformations| to file |filename| in JSON format. Useful for | 
|  | // interactive debugging. | 
|  | void DumpTransformationsJson( | 
|  | const spvtools::fuzz::protobufs::TransformationSequence& transformations, | 
|  | const char* filename) { | 
|  | std::string json_string; | 
|  | auto json_options = google::protobuf::util::JsonOptions(); | 
|  | json_options.add_whitespace = true; | 
|  | auto json_generation_status = google::protobuf::util::MessageToJsonString( | 
|  | transformations, &json_string, json_options); | 
|  | if (json_generation_status.ok()) { | 
|  | std::ofstream transformations_json_file(filename); | 
|  | transformations_json_file << json_string; | 
|  | transformations_json_file.close(); | 
|  | } | 
|  | } | 
|  |  | 
|  | const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3; | 
|  |  | 
|  | int main(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 = "temp_"; | 
|  | spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy; | 
|  | auto fuzzing_target = FuzzingTarget::kSpirv; | 
|  |  | 
|  | spvtools::FuzzerOptions fuzzer_options; | 
|  | spvtools::ValidatorOptions validator_options; | 
|  |  | 
|  | FuzzStatus status = | 
|  | ParseFlags(argc, argv, &in_binary_file, &out_binary_file, &donors_file, | 
|  | &replay_transformations_file, &interestingness_test, | 
|  | &shrink_transformations_file, &shrink_temp_file_prefix, | 
|  | &repeated_pass_strategy, &fuzzing_target, &fuzzer_options, | 
|  | &validator_options); | 
|  |  | 
|  | if (status.action == FuzzActions::STOP) { | 
|  | return status.code; | 
|  | } | 
|  |  | 
|  | std::vector<uint32_t> binary_in; | 
|  | if (!ReadBinaryFile<uint32_t>(in_binary_file.c_str(), &binary_in)) { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | spvtools::fuzz::protobufs::FactSequence initial_facts; | 
|  |  | 
|  | // If not found, dot_pos will be std::string::npos, which can be used in | 
|  | // substr to mean "the end of the string"; there is no need to check the | 
|  | // result. | 
|  | size_t dot_pos = in_binary_file.rfind('.'); | 
|  | std::string in_facts_file = in_binary_file.substr(0, dot_pos) + ".facts"; | 
|  | std::ifstream facts_input(in_facts_file); | 
|  | if (facts_input) { | 
|  | std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)), | 
|  | std::istreambuf_iterator<char>()); | 
|  | facts_input.close(); | 
|  | if (!google::protobuf::util::JsonStringToMessage(facts_json_string, | 
|  | &initial_facts) | 
|  | .ok()) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data"); | 
|  | return 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::vector<uint32_t> binary_out; | 
|  | spvtools::fuzz::protobufs::TransformationSequence transformations_applied; | 
|  |  | 
|  | spv_target_env target_env = kDefaultEnvironment; | 
|  |  | 
|  | switch (status.action) { | 
|  | case FuzzActions::FORCE_RENDER_RED: | 
|  | if (!spvtools::fuzz::ForceRenderRed( | 
|  | target_env, validator_options, binary_in, initial_facts, | 
|  | spvtools::utils::CLIMessageConsumer, &binary_out)) { | 
|  | return 1; | 
|  | } | 
|  | break; | 
|  | case FuzzActions::FUZZ: | 
|  | if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in, | 
|  | initial_facts, donors_file, repeated_pass_strategy, | 
|  | fuzzing_target, &binary_out, &transformations_applied)) { | 
|  | return 1; | 
|  | } | 
|  | break; | 
|  | case FuzzActions::REPLAY: | 
|  | if (!Replay(target_env, fuzzer_options, validator_options, binary_in, | 
|  | initial_facts, replay_transformations_file, &binary_out, | 
|  | &transformations_applied)) { | 
|  | return 1; | 
|  | } | 
|  | break; | 
|  | case FuzzActions::SHRINK: { | 
|  | if (!CheckExecuteCommand()) { | 
|  | std::cerr << "could not find shell interpreter for executing a command" | 
|  | << std::endl; | 
|  | return 1; | 
|  | } | 
|  | if (!Shrink(target_env, fuzzer_options, validator_options, binary_in, | 
|  | initial_facts, shrink_transformations_file, | 
|  | shrink_temp_file_prefix, interestingness_test, &binary_out, | 
|  | &transformations_applied)) { | 
|  | return 1; | 
|  | } | 
|  | } break; | 
|  | default: | 
|  | assert(false && "Unknown fuzzer action."); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(), | 
|  | binary_out.size())) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error writing out binary"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (status.action != FuzzActions::FORCE_RENDER_RED) { | 
|  | // If not found, dot_pos will be std::string::npos, which can be used in | 
|  | // substr to mean "the end of the string"; there is no need to check the | 
|  | // result. | 
|  | dot_pos = out_binary_file.rfind('.'); | 
|  | std::string output_file_prefix = out_binary_file.substr(0, dot_pos); | 
|  | std::ofstream transformations_file; | 
|  | transformations_file.open(output_file_prefix + ".transformations", | 
|  | std::ios::out | std::ios::binary); | 
|  | bool success = | 
|  | transformations_applied.SerializeToOstream(&transformations_file); | 
|  | transformations_file.close(); | 
|  | if (!success) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, | 
|  | "Error writing out transformations binary"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | std::string json_string; | 
|  | auto json_options = google::protobuf::util::JsonOptions(); | 
|  | json_options.add_whitespace = true; | 
|  | auto json_generation_status = google::protobuf::util::MessageToJsonString( | 
|  | transformations_applied, &json_string, json_options); | 
|  | if (!json_generation_status.ok()) { | 
|  | spvtools::Error(FuzzDiagnostic, nullptr, {}, | 
|  | "Error writing out transformations in JSON format"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | std::ofstream transformations_json_file(output_file_prefix + | 
|  | ".transformations_json"); | 
|  | transformations_json_file << json_string; | 
|  | transformations_json_file.close(); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } |