| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820 |
- // 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 };
- // 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(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::JsonPrintOptions();
- 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(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 (!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::JsonPrintOptions();
- 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;
- }
|