| // Copyright 2021 The Tint Authors. |
| // |
| // 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 "fuzzers/tint_spirv_tools_fuzzer/cli.h" |
| |
| #include <fstream> |
| #include <limits> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "fuzzers/tint_spirv_tools_fuzzer/util.h" |
| #include "source/opt/build_module.h" |
| |
| namespace tint { |
| namespace fuzzers { |
| namespace spvtools_fuzzer { |
| namespace { |
| |
| const char* const kMutatorParameters = R"( |
| Mutators' parameters: |
| |
| -tint_donors= |
| A path to the text file with a list of paths to the |
| SPIR-V donor files. Check out the doc for the spirv-fuzz |
| to learn more about donor binaries. Donors are not used |
| by default. |
| |
| -tint_enable_all_fuzzer_passes= |
| Whether to use all fuzzer passes or a randomly selected subset |
| of them. This must be one of `true` or `false` (without `). |
| By default it's `false`. |
| |
| -tint_enable_all_reduce_passes= |
| Whether to use all reduction passes or a randomly selected subset |
| of them. This must be one of `true` or `false` (without `). |
| By default it's `false`. |
| |
| -tint_opt_batch_size= |
| The maximum number of spirv-opt optimizations that |
| will be applied in a single mutation session (i.e. |
| a call to LLVMFuzzerCustomMutator). This must fit in |
| uint32_t. By default it's 6. |
| |
| -tint_reduction_batch_size= |
| The maximum number of spirv-reduce reductions that |
| will be applied in a single mutation session (i.e. |
| a call to LLVMFuzzerCustomMutator). This must fit in |
| uint32_t. By default it's 3. |
| |
| -tint_repeated_pass_strategy= |
| The strategy that will be used to recommend the next fuzzer |
| pass. This must be one of `simple`, `looped` or `random` |
| (without `). By default it's `simple`. Check out the doc for |
| spirv-fuzz to learn more. |
| |
| -tint_transformation_batch_size= |
| The maximum number of spirv-fuzz transformations |
| that will be applied during a single mutation |
| session (i.e. a call to LLVMFuzzerCustomMutator). |
| This must fit in uint32_t. By default it's 3. |
| |
| -tint_validate_after_each_fuzzer_pass= |
| Whether to validate SPIR-V binary after each fuzzer pass. |
| This must be one of `true` or `false` (without `). |
| By default it's `true`. Switch this to `false` if you experience |
| bad performance. |
| |
| -tint_validate_after_each_opt_pass= |
| Whether to validate SPIR-V binary after each optimization pass. |
| This must be one of `true` or `false` (without `). |
| By default it's `true`. Switch this to `false` if you experience |
| bad performance. |
| |
| -tint_validate_after_each_reduce_pass= |
| Whether to validate SPIR-V binary after each reduction pass. |
| This must be one of `true` or `false` (without `). |
| By default it's `true`. Switch this to `false` if you experience |
| bad performance. |
| )"; |
| |
| const char* const kFuzzerHelpMessage = R"( |
| This fuzzer uses SPIR-V binaries to fuzz the Tint compiler. It uses SPIRV-Tools |
| to mutate those binaries. The fuzzer works on a corpus of SPIR-V shaders. |
| For each shader from the corpus it uses one of `spirv-fuzz`, `spirv-reduce` or |
| `spirv-opt` to mutate it and then runs the shader through the Tint compiler in |
| two steps: |
| - Converts the mutated shader to WGSL. |
| - Converts WGSL to some target language specified in the CLI arguments. |
| |
| Below is a list of all supported parameters for this fuzzer. You may want to |
| run it with -help=1 to check out libfuzzer parameters. |
| |
| Fuzzer parameters: |
| |
| -tint_error_dir |
| The directory that will be used to output invalid SPIR-V |
| binaries to. This is especially useful during debugging |
| mutators. The directory must have the following subdirectories: |
| - spv/ - will be used to output errors, produced during |
| the conversion from the SPIR-V to WGSL. |
| - wgsl/ - will be used to output errors, produced during |
| the conversion from the WGSL to `--fuzzing_target`. |
| - mutator/ - will be used to output errors, produced by |
| the mutators. |
| By default invalid files are not printed out. |
| |
| -tint_fuzzing_target |
| The type of backend to target during fuzzing. This must |
| be one or a combination of `wgsl`, `spv`, `msl` or `hlsl` |
| (without `) separated by commas. By default it's |
| `wgsl,spv,msl,hlsl`. |
| |
| -tint_help |
| Show this message. Note that there is also a -help=1 |
| parameter that will display libfuzzer's help message. |
| |
| -tint_mutator_cache_size= |
| The maximum size of the cache that stores |
| mutation sessions. This must fit in uint32_t. |
| By default it's 20. |
| |
| -tint_mutator_type= |
| Determines types of the mutators to run. This must be one or |
| a combination of `fuzz`, `opt`, `reduce` (without `) separated by |
| comma. If a combination is specified, each element in the |
| combination will have an equal chance of mutating a SPIR-V |
| binary during a mutation session (i.e. if no mutator exists |
| for that binary in the mutator cache). By default, the |
| parameter's value is `fuzz,opt,reduce`. |
| )"; |
| |
| const char* const kMutatorDebuggerHelpMessage = R"( |
| This tool is used to debug *mutators*. It uses CLI arguments similar to the |
| ones used by the fuzzer. To debug some mutator you just need to specify the |
| mutator type, the seed and the path to the SPIR-V binary that triggered the |
| error. This tool will run the mutator on the binary until the error is |
| produced or the mutator returns `kLimitReached`. |
| |
| Note that this is different from debugging the fuzzer by specifying input |
| files to test. The difference is that the latter will not execute any |
| mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this |
| tool is useful when one of the SPIRV-Tools mutators crashes or produces an |
| invalid binary in LLVMFuzzerCustomMutator. |
| |
| Debugger parameters: |
| |
| --help |
| Show this message. |
| |
| --mutator_type= |
| Determines the type of the mutator to debug. This must be |
| one of `fuzz`, `reduce` or `opt` (without `). This parameter |
| is REQUIRED. |
| |
| --original_binary= |
| The path to the SPIR-V binary that the faulty mutator was |
| initialized with. This will be dumped on errors by the fuzzer |
| if `--error_dir` is specified. This parameter is REQUIRED. |
| |
| --seed= |
| The seed for the random number generator that was used to |
| initialize the mutator. This value is usually printed to |
| the console when the mutator produces an invalid binary. |
| It is also dumped into the log file if `--error_dir` is |
| specified. This must fit in uint32_t. This parameter is |
| REQUIRED. |
| )"; |
| |
| void PrintHelpMessage(const char* help_message) { |
| std::cout << help_message << std::endl << kMutatorParameters << std::endl; |
| } |
| |
| [[noreturn]] void InvalidParameter(const char* help_message, |
| const char* param) { |
| std::cout << "Invalid value for " << param << std::endl; |
| PrintHelpMessage(help_message); |
| exit(1); |
| } |
| |
| bool ParseUint32(const char* param, uint32_t* out) { |
| uint64_t value = static_cast<uint64_t>(strtoul(param, nullptr, 10)); |
| if (value > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) { |
| return false; |
| } |
| *out = static_cast<uint32_t>(value); |
| return true; |
| } |
| |
| std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> ParseDonors( |
| const char* file_name) { |
| std::ifstream fin(file_name); |
| if (!fin) { |
| std::cout << "Can't open donors list file: " << file_name << std::endl; |
| exit(1); |
| } |
| |
| std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> result; |
| for (std::string donor_file_name; fin >> donor_file_name;) { |
| if (!std::ifstream(donor_file_name)) { |
| std::cout << "Can't open donor file: " << donor_file_name << std::endl; |
| exit(1); |
| } |
| |
| result.emplace_back([donor_file_name] { |
| std::vector<uint32_t> binary; |
| if (!util::ReadBinary(donor_file_name, &binary)) { |
| std::cout << "Failed to read donor from: " << donor_file_name |
| << std::endl; |
| exit(1); |
| } |
| return spvtools::BuildModule( |
| kDefaultTargetEnv, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer, |
| binary.data(), binary.size()); |
| }); |
| } |
| |
| return result; |
| } |
| |
| bool ParseRepeatedPassStrategy(const char* param, |
| spvtools::fuzz::RepeatedPassStrategy* out) { |
| if (!strcmp(param, "simple")) { |
| *out = spvtools::fuzz::RepeatedPassStrategy::kSimple; |
| } else if (!strcmp(param, "looped")) { |
| *out = spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations; |
| } else if (!strcmp(param, "random")) { |
| *out = spvtools::fuzz::RepeatedPassStrategy::kRandomWithRecommendations; |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| bool ParseBool(const char* param, bool* out) { |
| if (!strcmp(param, "true")) { |
| *out = true; |
| } else if (!strcmp(param, "false")) { |
| *out = false; |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| bool ParseMutatorType(const char* param, MutatorType* out) { |
| if (!strcmp(param, "fuzz")) { |
| *out = MutatorType::kFuzz; |
| } else if (!strcmp(param, "opt")) { |
| *out = MutatorType::kOpt; |
| } else if (!strcmp(param, "reduce")) { |
| *out = MutatorType::kReduce; |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| bool ParseFuzzingTarget(const char* param, FuzzingTarget* out) { |
| if (!strcmp(param, "wgsl")) { |
| *out = FuzzingTarget::kWgsl; |
| } else if (!strcmp(param, "spv")) { |
| *out = FuzzingTarget::kSpv; |
| } else if (!strcmp(param, "msl")) { |
| *out = FuzzingTarget::kMsl; |
| } else if (!strcmp(param, "hlsl")) { |
| *out = FuzzingTarget::kHlsl; |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| bool HasPrefix(const char* str, const char* prefix) { |
| return strncmp(str, prefix, strlen(prefix)) == 0; |
| } |
| |
| bool ParseMutatorCliParam(const char* param, |
| const char* help_message, |
| MutatorCliParams* out) { |
| if (HasPrefix(param, "-tint_transformation_batch_size=")) { |
| if (!ParseUint32(param + sizeof("-tint_transformation_batch_size=") - 1, |
| &out->transformation_batch_size)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_reduction_batch_size=")) { |
| if (!ParseUint32(param + sizeof("-tint_reduction_batch_size=") - 1, |
| &out->reduction_batch_size)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_opt_batch_size=")) { |
| if (!ParseUint32(param + sizeof("-tint_opt_batch_size=") - 1, |
| &out->opt_batch_size)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_donors=")) { |
| out->donors = ParseDonors(param + sizeof("-tint_donors=") - 1); |
| } else if (HasPrefix(param, "-tint_repeated_pass_strategy=")) { |
| if (!ParseRepeatedPassStrategy( |
| param + sizeof("-tint_repeated_pass_strategy=") - 1, |
| &out->repeated_pass_strategy)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_enable_all_fuzzer_passes=")) { |
| if (!ParseBool(param + sizeof("-tint_enable_all_fuzzer_passes=") - 1, |
| &out->enable_all_fuzzer_passes)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_enable_all_reduce_passes=")) { |
| if (!ParseBool(param + sizeof("-tint_enable_all_reduce_passes=") - 1, |
| &out->enable_all_reduce_passes)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_validate_after_each_opt_pass=")) { |
| if (!ParseBool(param + sizeof("-tint_validate_after_each_opt_pass=") - 1, |
| &out->validate_after_each_opt_pass)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_validate_after_each_fuzzer_pass=")) { |
| if (!ParseBool(param + sizeof("-tint_validate_after_each_fuzzer_pass=") - 1, |
| &out->validate_after_each_fuzzer_pass)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_validate_after_each_reduce_pass=")) { |
| if (!ParseBool(param + sizeof("-tint_validate_after_each_reduce_pass=") - 1, |
| &out->validate_after_each_reduce_pass)) { |
| InvalidParameter(help_message, param); |
| } |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| FuzzerCliParams ParseFuzzerCliParams(int* argc, char** argv) { |
| FuzzerCliParams cli_params; |
| const auto* help_message = kFuzzerHelpMessage; |
| auto help = false; |
| |
| for (int i = *argc - 1; i > 0; --i) { |
| auto param = argv[i]; |
| auto recognized_param = true; |
| |
| if (HasPrefix(param, "-tint_mutator_cache_size=")) { |
| if (!ParseUint32(param + sizeof("-tint_mutator_cache_size=") - 1, |
| &cli_params.mutator_cache_size)) { |
| InvalidParameter(help_message, param); |
| } |
| } else if (HasPrefix(param, "-tint_mutator_type=")) { |
| auto result = MutatorType::kNone; |
| |
| std::stringstream ss(param + sizeof("-tint_mutator_type=") - 1); |
| for (std::string value; std::getline(ss, value, ',');) { |
| auto out = MutatorType::kNone; |
| if (!ParseMutatorType(value.c_str(), &out)) { |
| InvalidParameter(help_message, param); |
| } |
| result = result | out; |
| } |
| |
| if (result == MutatorType::kNone) { |
| InvalidParameter(help_message, param); |
| } |
| |
| cli_params.mutator_type = result; |
| } else if (HasPrefix(param, "-tint_fuzzing_target=")) { |
| auto result = FuzzingTarget::kNone; |
| |
| std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1); |
| for (std::string value; std::getline(ss, value, ',');) { |
| auto tmp = FuzzingTarget::kNone; |
| if (!ParseFuzzingTarget(value.c_str(), &tmp)) { |
| InvalidParameter(help_message, param); |
| } |
| result = result | tmp; |
| } |
| |
| if (result == FuzzingTarget::kNone) { |
| InvalidParameter(help_message, param); |
| } |
| |
| cli_params.fuzzing_target = result; |
| } else if (HasPrefix(param, "-tint_error_dir=")) { |
| cli_params.error_dir = param + sizeof("-tint_error_dir=") - 1; |
| } else if (!strcmp(param, "-tint_help")) { |
| help = true; |
| } else { |
| recognized_param = ParseMutatorCliParam(param, help_message, |
| &cli_params.mutator_params); |
| } |
| |
| if (recognized_param) { |
| // Remove the recognized parameter from the list of all parameters by |
| // swapping it with the last one. This will suppress warnings in the |
| // libFuzzer about unrecognized parameters. By default, libFuzzer thinks |
| // that all user-defined parameters start with two dashes. However, we are |
| // forced to use a single one to make the fuzzer compatible with the |
| // ClusterFuzz. |
| std::swap(argv[i], argv[*argc - 1]); |
| *argc -= 1; |
| } |
| } |
| |
| if (help) { |
| PrintHelpMessage(help_message); |
| exit(0); |
| } |
| |
| return cli_params; |
| } |
| |
| MutatorDebuggerCliParams ParseMutatorDebuggerCliParams( |
| int argc, |
| const char* const* argv) { |
| MutatorDebuggerCliParams cli_params; |
| bool seed_param_present = false; |
| bool original_binary_param_present = false; |
| bool mutator_type_param_present = false; |
| const auto* help_message = kMutatorDebuggerHelpMessage; |
| auto help = false; |
| |
| for (int i = 0; i < argc; ++i) { |
| auto param = argv[i]; |
| ParseMutatorCliParam(param, help_message, &cli_params.mutator_params); |
| |
| if (HasPrefix(param, "--mutator_type=")) { |
| if (!ParseMutatorType(param + sizeof("--mutator_type=") - 1, |
| &cli_params.mutator_type)) { |
| InvalidParameter(help_message, param); |
| } |
| mutator_type_param_present = true; |
| } else if (HasPrefix(param, "--original_binary=")) { |
| if (!util::ReadBinary(param + sizeof("--original_binary=") - 1, |
| &cli_params.original_binary)) { |
| InvalidParameter(help_message, param); |
| } |
| original_binary_param_present = true; |
| } else if (HasPrefix(param, "--seed=")) { |
| if (!ParseUint32(param + sizeof("--seed=") - 1, &cli_params.seed)) { |
| InvalidParameter(help_message, param); |
| } |
| seed_param_present = true; |
| } else if (!strcmp(param, "--help")) { |
| help = true; |
| } |
| } |
| |
| if (help) { |
| PrintHelpMessage(help_message); |
| exit(0); |
| } |
| |
| std::pair<bool, const char*> required_params[] = { |
| {seed_param_present, "--seed"}, |
| {original_binary_param_present, "--original_binary"}, |
| {mutator_type_param_present, "--mutator_type"}}; |
| |
| for (auto required_param : required_params) { |
| if (!required_param.first) { |
| std::cout << required_param.second << " is missing" << std::endl; |
| exit(1); |
| } |
| } |
| |
| return cli_params; |
| } |
| |
| } // namespace spvtools_fuzzer |
| } // namespace fuzzers |
| } // namespace tint |