blob: 18c072a5f8c3b878008dcb08f7a6edf138c182dd [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 <cassert>
#include <memory>
#include <string>
#include <vector>
#include "fuzzers/random_generator.h"
#include "fuzzers/tint_common_fuzzer.h"
#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
#include "fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h"
#include "fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
#include "fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
#include "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
#include "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
#include "spirv-tools/libspirv.hpp"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
namespace {
struct Context {
FuzzerCliParams params;
std::unique_ptr<MutatorCache> mutator_cache;
};
Context* context = nullptr;
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
auto params = ParseFuzzerCliParams(argc, *argv);
auto mutator_cache =
params.mutator_cache_size
? std::make_unique<MutatorCache>(params.mutator_cache_size)
: nullptr;
context = new Context{std::move(params), std::move(mutator_cache)};
OverrideCliParams(context->params);
return 0;
}
std::unique_ptr<Mutator> CreateMutator(const std::vector<uint32_t>& binary,
unsigned seed) {
std::vector<MutatorType> types;
types.reserve(3);
// Determine which mutator we will be using for `binary` at random.
auto cli_mutator_type = context->params.mutator_type;
if ((MutatorType::kFuzz & cli_mutator_type) == MutatorType::kFuzz) {
types.push_back(MutatorType::kFuzz);
}
if ((MutatorType::kReduce & cli_mutator_type) == MutatorType::kReduce) {
types.push_back(MutatorType::kReduce);
}
if ((MutatorType::kOpt & cli_mutator_type) == MutatorType::kOpt) {
types.push_back(MutatorType::kOpt);
}
assert(!types.empty() && "At least one mutator type must be specified");
RandomGenerator generator(seed);
auto mutator_type = types[generator.GetUInt64(types.size())];
const auto& mutator_params = context->params.mutator_params;
switch (mutator_type) {
case MutatorType::kFuzz:
return std::make_unique<SpirvFuzzMutator>(
mutator_params.target_env, binary, seed, mutator_params.donors,
mutator_params.enable_all_fuzzer_passes,
mutator_params.repeated_pass_strategy,
mutator_params.validate_after_each_fuzzer_pass,
mutator_params.transformation_batch_size);
case MutatorType::kReduce:
return std::make_unique<SpirvReduceMutator>(
mutator_params.target_env, binary, seed,
mutator_params.reduction_batch_size,
mutator_params.enable_all_reduce_passes,
mutator_params.validate_after_each_reduce_pass);
case MutatorType::kOpt:
return std::make_unique<SpirvOptMutator>(
mutator_params.target_env, seed, binary,
mutator_params.validate_after_each_opt_pass,
mutator_params.opt_batch_size);
default:
assert(false && "All mutator types must be handled above");
return nullptr;
}
}
void CLIMessageConsumer(spv_message_level_t level,
const char*,
const spv_position_t& position,
const char* message) {
switch (level) {
case SPV_MSG_FATAL:
case SPV_MSG_INTERNAL_ERROR:
case SPV_MSG_ERROR:
std::cerr << "error: line " << position.index << ": " << message
<< std::endl;
break;
case SPV_MSG_WARNING:
std::cout << "warning: line " << position.index << ": " << message
<< std::endl;
break;
case SPV_MSG_INFO:
std::cout << "info: line " << position.index << ": " << message
<< std::endl;
break;
default:
break;
}
}
bool IsValid(const std::vector<uint32_t>& binary) {
spvtools::SpirvTools tools(context->params.mutator_params.target_env);
tools.SetMessageConsumer(CLIMessageConsumer);
return tools.IsValid() && tools.Validate(binary.data(), binary.size(),
spvtools::ValidatorOptions());
}
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
size_t size,
size_t max_size,
unsigned seed) {
if ((size % sizeof(uint32_t)) != 0) {
// A valid SPIR-V binary's size must be a multiple of the size of a 32-bit
// word, and the SPIR-V Tools fuzzer is only designed to work with valid
// binaries.
return 0;
}
std::vector<uint32_t> binary(size / sizeof(uint32_t));
std::memcpy(binary.data(), data, size);
MutatorCache dummy_cache(1);
auto* mutator_cache = context->mutator_cache.get();
if (!mutator_cache) {
// Use a placeholder cache if the user has decided not to use a real cache.
// The placeholder cache will be destroyed when we return from this function
// but it will save us from writing all the `if (mutator_cache)` below.
mutator_cache = &dummy_cache;
}
if (!mutator_cache->Get(binary)) {
// This is an unknown binary, so its validity must be checked before
// proceeding.
if (!IsValid(binary)) {
return 0;
}
// Assign a mutator to the binary if it doesn't have one yet.
mutator_cache->Put(binary, CreateMutator(binary, seed));
}
auto* mutator = mutator_cache->Get(binary);
assert(mutator && "Mutator must be present in the cache");
auto result = mutator->Mutate();
if (result.GetStatus() == Mutator::Status::kInvalid) {
// The binary is invalid - log the error and remove the mutator from the
// cache.
util::LogMutatorError(*mutator, context->params.error_dir);
mutator_cache->Remove(binary);
return 0;
}
if (!result.IsChanged()) {
// The mutator didn't change the binary this time. This could be due to the
// fact that we've reached the number of mutations we can apply (e.g. the
// number of transformations in spirv-fuzz) or the mutator was just unlucky.
// Either way, there is no harm in destroying mutator and maybe trying again
// later (i.e. if libfuzzer decides to do so).
mutator_cache->Remove(binary);
return 0;
}
// At this point the binary is valid and was changed by the mutator.
auto mutated = mutator->GetBinary();
auto mutated_bytes_size = mutated.size() * sizeof(uint32_t);
if (mutated_bytes_size > max_size) {
// The binary is too big. It's unlikely that we'll reduce its size by
// applying the mutator one more time.
mutator_cache->Remove(binary);
return 0;
}
if (result.GetStatus() == Mutator::Status::kComplete) {
// Reassign the mutator to the mutated binary in the cache so that we can
// access later.
mutator_cache->Put(mutated, mutator_cache->Remove(binary));
} else {
// If the binary is valid and was changed but is not `kComplete`, then the
// mutator has reached some limit on the number of mutations.
mutator_cache->Remove(binary);
}
std::memcpy(data, mutated.data(), mutated_bytes_size);
return mutated_bytes_size;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size == 0) {
return 0;
}
if ((size % sizeof(uint32_t)) != 0) {
// The SPIR-V Tools fuzzer has been designed to work with valid
// SPIR-V binaries, whose sizes should be multiples of the size of a 32-bit
// word.
return 0;
}
CommonFuzzer spv_to_wgsl(InputFormat::kSpv, OutputFormat::kWGSL);
spv_to_wgsl.EnableInspector();
spv_to_wgsl.Run(data, size);
if (spv_to_wgsl.HasErrors()) {
auto error = spv_to_wgsl.Diagnostics().str();
util::LogSpvError(error, data, size,
context ? context->params.error_dir : "");
return 0;
}
const auto& wgsl = spv_to_wgsl.GetGeneratedWgsl();
std::pair<FuzzingTarget, OutputFormat> targets[] = {
{FuzzingTarget::kHlsl, OutputFormat::kHLSL},
{FuzzingTarget::kMsl, OutputFormat::kMSL},
{FuzzingTarget::kSpv, OutputFormat::kSpv},
{FuzzingTarget::kWgsl, OutputFormat::kWGSL}};
for (auto target : targets) {
if ((target.first & context->params.fuzzing_target) != target.first) {
continue;
}
CommonFuzzer fuzzer(InputFormat::kWGSL, target.second);
fuzzer.EnableInspector();
fuzzer.Run(reinterpret_cast<const uint8_t*>(wgsl.data()), wgsl.size());
if (fuzzer.HasErrors()) {
auto error = spv_to_wgsl.Diagnostics().str();
util::LogWgslError(error, data, size, wgsl, target.second,
context->params.error_dir);
}
}
return 0;
}
} // namespace
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint