blob: b94242152a4b91df048c6b6112d1864bd45c0fff [file] [log] [blame]
// 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