Add spirv-tools fuzzer

This change adds a new tint fuzzer that uses SPIRV-Tools to fuzz SPIR-V 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 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.

The list of all supported CLI arguments and their description is in the cli.h file.

Change-Id: I95c0741b78ccc600dd9a73c371d520bdf7814352
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/41945
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Vasyl Teliman <vasniktel@gmail.com>
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: Alastair Donaldson <allydonaldson@googlemail.com>
This commit is contained in:
Vasyl Teliman 2021-06-24 18:10:46 +00:00 committed by Tint LUCI CQ
parent c3f19081f8
commit 0b3611b8c8
26 changed files with 2424 additions and 12 deletions

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ third_party/binutils
third_party/googletest
third_party/gpuweb-cts
third_party/llvm-build
third_party/protobuf
third_party/spirv-headers
third_party/spirv-tools
tools/clang

View File

@ -5,3 +5,4 @@
# of contributors, see the revision history in source control.
Google LLC
Vasyl Teliman

View File

@ -49,6 +49,7 @@ option(TINT_BUILD_MSL_WRITER "Build the MSL output writer" ON)
option(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" ON)
option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON)
option(TINT_BUILD_FUZZERS "Build fuzzers" OFF)
option(TINT_BUILD_SPIRV_TOOLS_FUZZER "Build SPIRV-Tools fuzzer" OFF)
option(TINT_BUILD_TESTS "Build tests" ${TINT_BUILD_TESTS_DEFAULT})
option(TINT_BUILD_AS_OTHER_OS "Override OS detection to force building of *_other.cc files" OFF)
@ -68,6 +69,7 @@ message(STATUS "Tint build MSL writer: ${TINT_BUILD_MSL_WRITER}")
message(STATUS "Tint build SPIR-V writer: ${TINT_BUILD_SPV_WRITER}")
message(STATUS "Tint build WGSL writer: ${TINT_BUILD_WGSL_WRITER}")
message(STATUS "Tint build fuzzers: ${TINT_BUILD_FUZZERS}")
message(STATUS "Tint build SPIRV-Tools fuzzer: ${TINT_BUILD_SPIRV_TOOLS_FUZZER}")
message(STATUS "Tint build tests: ${TINT_BUILD_TESTS}")
message(STATUS "Tint build with ASAN: ${TINT_ENABLE_ASAN}")
message(STATUS "Tint build with MSAN: ${TINT_ENABLE_MSAN}")
@ -77,12 +79,30 @@ message(STATUS "Tint build checking [chromium-style]: ${TINT_CHECK_CHROMIUM_STYL
message(STATUS "Using python3")
find_package(PythonInterp 3 REQUIRED)
if (${TINT_BUILD_SPIRV_TOOLS_FUZZER})
message(STATUS "TINT_BUILD_SPIRV_TOOLS_FUZZER is ON - setting
TINT_BUILD_FUZZERS,
TINT_BUILD_SPV_READER,
TINT_BUILD_WGSL_READER,
TINT_BUILD_WGSL_WRITER,
TINT_BUILD_HLSL_WRITER,
TINT_BUILD_MSL_WRITER,
TINT_BUILD_SPV_WRITER to ON")
set(TINT_BUILD_FUZZERS ON)
set(TINT_BUILD_SPV_READER ON)
set(TINT_BUILD_WGSL_READER ON)
set(TINT_BUILD_WGSL_WRITER ON)
set(TINT_BUILD_HLSL_WRITER ON)
set(TINT_BUILD_MSL_WRITER ON)
set(TINT_BUILD_SPV_WRITER ON)
endif()
# CMake < 3.15 sets /W3 in CMAKE_CXX_FLAGS. Remove it if it's there.
# See https://gitlab.kitware.com/cmake/cmake/-/issues/18317
if (MSVC)
if (CMAKE_CXX_FLAGS MATCHES "/W3")
string(REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
endif()
if (CMAKE_CXX_FLAGS MATCHES "/W3")
string(REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
endif()
endif()
if (${TINT_CHECK_CHROMIUM_STYLE})

4
DEPS
View File

@ -11,6 +11,7 @@ vars = {
'clang_revision': 'eb5ab41f3801e2085208204fd71a490573d72dfd',
'googletest_revision': '5c8ca58edfb304b2dd5e6061f83387470826dd87',
'gpuweb_cts_revision': '177a4faf0a7ce6f8c64b42a715c634e363912a74',
'protobuf_revision': 'fde7cf7358ec7cd69e8db9be4f1fa6a5c431386a',
'spirv_headers_revision': 'f5417a4b6633c3217c9a1bc2f0c70b1454975ba7',
'spirv_tools_revision': 'ecdd9a3e6bd384bf51d096b507291faa10f14685',
'testing_revision': '2691851e49de541c3fe42fa8692ddcdee938162f',
@ -42,6 +43,9 @@ deps = {
'third_party/googletest': Var('chromium_git') + Var('github') +
'/google/googletest.git@' + Var('googletest_revision'),
'third_party/protobuf': Var('chromium_git') + Var('github') +
'/protocolbuffers/protobuf.git@' + Var('protobuf_revision'),
}
hooks = [

View File

@ -786,7 +786,8 @@ WARN_LOGFILE =
# Note: If this tag is empty the current directory is searched.
INPUT = CODE_OF_CONDUCT.md \
src
src \
fuzzers/tint_spirv_tools_fuzzer
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

View File

@ -76,3 +76,7 @@ endif()
if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER})
add_tint_fuzzer(tint_ast_clone_fuzzer)
endif()
if (${TINT_BUILD_SPIRV_TOOLS_FUZZER})
add_subdirectory(tint_spirv_tools_fuzzer)
endif()

View File

@ -21,6 +21,7 @@
#include <vector>
#include "src/ast/module.h"
#include "src/diagnostic/formatter.h"
#include "src/program.h"
namespace tint {
@ -191,6 +192,7 @@ int CommonFuzzer::Run(const uint8_t* data, size_t size) {
}
if (!program.IsValid()) {
errors_ = diag::Formatter().format(program.Diagnostics());
return 0;
}
@ -199,58 +201,68 @@ int CommonFuzzer::Run(const uint8_t* data, size_t size) {
auto entry_points = inspector.GetEntryPoints();
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
for (auto& ep : entry_points) {
auto remapped_name = inspector.GetRemappedNameForEntryPoint(ep.name);
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
auto constant_ids = inspector.GetConstantIDs();
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
auto uniform_bindings =
inspector.GetUniformBufferResourceBindings(ep.name);
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
auto storage_bindings =
inspector.GetStorageBufferResourceBindings(ep.name);
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
auto readonly_bindings =
inspector.GetReadOnlyStorageBufferResourceBindings(ep.name);
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
auto sampler_bindings = inspector.GetSamplerResourceBindings(ep.name);
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
auto comparison_sampler_bindings =
inspector.GetComparisonSamplerResourceBindings(ep.name);
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
auto sampled_texture_bindings =
inspector.GetSampledTextureResourceBindings(ep.name);
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
auto multisampled_texture_bindings =
inspector.GetMultisampledTextureResourceBindings(ep.name);
if (inspector.has_error()) {
errors_ = inspector.error();
return 0;
}
}
@ -272,39 +284,44 @@ int CommonFuzzer::Run(const uint8_t* data, size_t size) {
program = std::move(out.program);
}
std::unique_ptr<writer::Writer> writer;
switch (output_) {
case OutputFormat::kWGSL:
#if TINT_BUILD_WGSL_WRITER
writer = std::make_unique<writer::wgsl::Generator>(&program);
writer_ = std::make_unique<writer::wgsl::Generator>(&program);
#endif // TINT_BUILD_WGSL_WRITER
break;
case OutputFormat::kSpv:
#if TINT_BUILD_SPV_WRITER
writer = std::make_unique<writer::spirv::Generator>(&program);
writer_ = std::make_unique<writer::spirv::Generator>(&program);
#endif // TINT_BUILD_SPV_WRITER
break;
case OutputFormat::kHLSL:
#if TINT_BUILD_HLSL_WRITER
writer = std::make_unique<writer::hlsl::Generator>(&program);
writer_ = std::make_unique<writer::hlsl::Generator>(&program);
#endif // TINT_BUILD_HLSL_WRITER
break;
case OutputFormat::kMSL:
#if TINT_BUILD_MSL_WRITER
writer = std::make_unique<writer::msl::Generator>(&program);
writer_ = std::make_unique<writer::msl::Generator>(&program);
#endif // TINT_BUILD_MSL_WRITER
break;
case OutputFormat::kNone:
break;
}
if (writer) {
writer->Generate();
if (writer_) {
if (!writer_->Generate()) {
errors_ = writer_->error();
return 0;
}
}
return 0;
}
const writer::Writer* CommonFuzzer::GetWriter() const {
return writer_.get();
}
} // namespace fuzzers
} // namespace tint

View File

@ -16,6 +16,7 @@
#define FUZZERS_TINT_COMMON_FUZZER_H_
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>
@ -108,12 +109,20 @@ class CommonFuzzer {
int Run(const uint8_t* data, size_t size);
const writer::Writer* GetWriter() const;
const std::string& GetErrors() const { return errors_; }
bool HasErrors() const { return !errors_.empty(); }
private:
InputFormat input_;
OutputFormat output_;
std::unique_ptr<writer::Writer> writer_;
transform::Manager* transform_manager_;
transform::DataMap transform_inputs_;
bool inspector_enabled_;
std::string errors_;
};
} // namespace fuzzers

View File

@ -0,0 +1,81 @@
# 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.
set(FUZZER_SOURCES
cli.cc
fuzzer.cc
mutator.cc
mutator_cache.cc
spirv_fuzz_mutator.cc
spirv_opt_mutator.cc
spirv_reduce_mutator.cc
util.cc)
set(FUZZER_SOURCES ${FUZZER_SOURCES}
cli.h
mutator.h
mutator_cache.h
spirv_fuzz_mutator.h
spirv_opt_mutator.h
spirv_reduce_mutator.h
util.h)
set(FUZZER_SOURCES ${FUZZER_SOURCES}
../tint_common_fuzzer.h
../tint_common_fuzzer.cc)
function(configure_spirv_tools_fuzzer_target NAME SOURCES)
add_executable(${NAME} ${SOURCES})
target_link_libraries(${NAME} SPIRV-Tools SPIRV-Tools-opt SPIRV-Tools-fuzz SPIRV-Tools-reduce)
tint_default_compile_options(${NAME})
target_compile_options(${NAME} PRIVATE
-Wno-missing-prototypes
-Wno-zero-as-null-pointer-constant
-Wno-reserved-id-macro
-Wno-sign-conversion
-Wno-extra-semi-stmt
-Wno-inconsistent-missing-destructor-override
-Wno-newline-eof
-Wno-old-style-cast
-Wno-weak-vtables
-Wno-undef)
target_include_directories(${NAME} PRIVATE
${spirv-tools_SOURCE_DIR}
${spirv-tools_BINARY_DIR})
endfunction()
configure_spirv_tools_fuzzer_target(tint_spirv_tools_fuzzer "${FUZZER_SOURCES}")
target_compile_definitions(tint_spirv_tools_fuzzer PUBLIC CUSTOM_MUTATOR)
target_compile_definitions(tint_spirv_tools_fuzzer PRIVATE TARGET_FUZZER)
target_link_libraries(tint_spirv_tools_fuzzer libtint-fuzz)
set(DEBUGGER_SOURCES
cli.cc
mutator.cc
mutator_debugger.cc
spirv_fuzz_mutator.cc
spirv_opt_mutator.cc
spirv_reduce_mutator.cc
util.cc)
set(DEBUGGER_SOURCES ${DEBUGGER_SOURCES}
cli.h
mutator.h
spirv_fuzz_mutator.h
spirv_opt_mutator.h
spirv_reduce_mutator.h
util.h)
configure_spirv_tools_fuzzer_target(tint_spirv_tools_mutator_debugger "${DEBUGGER_SOURCES}")
target_compile_definitions(tint_spirv_tools_mutator_debugger PRIVATE TARGET_DEBUGGER)

View File

@ -0,0 +1,467 @@
// 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:
--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.
--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`.
--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`.
--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.
--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.
--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.
--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.
--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.
--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.
--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:
--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.
--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`.
--help
Show this message. Note that there is also a -help=1
parameter that will display libfuzzer's help message.
--mutator_cache_size=
The maximum size of the cache that stores
mutation sessions. This must fit in uint32_t.
By default it's 20.
--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) {
auto value = strtoul(param, nullptr, 10);
if (value > 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;
}
void ParseMutatorCliParam(const char* param,
const char* help_message,
MutatorCliParams* out) {
if (HasPrefix(param, "--transformation_batch_size=")) {
if (!ParseUint32(param + sizeof("--transformation_batch_size=") - 1,
&out->transformation_batch_size)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--reduction_batch_size=")) {
if (!ParseUint32(param + sizeof("--reduction_batch_size=") - 1,
&out->reduction_batch_size)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--opt_batch_size=")) {
if (!ParseUint32(param + sizeof("--opt_batch_size=") - 1,
&out->opt_batch_size)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--donors=")) {
out->donors = ParseDonors(param + sizeof("--donors=") - 1);
} else if (HasPrefix(param, "--repeated_pass_strategy=")) {
if (!ParseRepeatedPassStrategy(
param + sizeof("--repeated_pass_strategy=") - 1,
&out->repeated_pass_strategy)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--enable_all_fuzzer_passes=")) {
if (!ParseBool(param + sizeof("--enable_all_fuzzer_passes=") - 1,
&out->enable_all_fuzzer_passes)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--enable_all_reduce_passes=")) {
if (!ParseBool(param + sizeof("--enable_all_reduce_passes=") - 1,
&out->enable_all_reduce_passes)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--validate_after_each_opt_pass=")) {
if (!ParseBool(param + sizeof("--validate_after_each_opt_pass=") - 1,
&out->validate_after_each_opt_pass)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--validate_after_each_fuzzer_pass=")) {
if (!ParseBool(param + sizeof("--validate_after_each_fuzzer_pass=") - 1,
&out->validate_after_each_fuzzer_pass)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--validate_after_each_reduce_pass=")) {
if (!ParseBool(param + sizeof("--validate_after_each_reduce_pass=") - 1,
&out->validate_after_each_reduce_pass)) {
InvalidParameter(help_message, param);
}
}
}
} // namespace
FuzzerCliParams ParseFuzzerCliParams(int argc, const char* const* argv) {
FuzzerCliParams cli_params;
const auto* help_message = kFuzzerHelpMessage;
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_cache_size=")) {
if (!ParseUint32(param + sizeof("--mutator_cache_size=") - 1,
&cli_params.mutator_cache_size)) {
InvalidParameter(help_message, param);
}
} else if (HasPrefix(param, "--mutator_type=")) {
auto result = MutatorType::kNone;
std::stringstream ss(param + sizeof("--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, "--fuzzing_target=")) {
auto result = FuzzingTarget::kNone;
std::stringstream ss(param + sizeof("--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, "--error_dir=")) {
cli_params.error_dir = param + sizeof("--error_dir=") - 1;
} else if (!strcmp(param, "--help")) {
help = true;
}
}
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

View File

@ -0,0 +1,124 @@
// 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.
#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
#include <string>
#include <vector>
#include "source/fuzz/fuzzer.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
/// Default SPIR-V environment that will be used during fuzzing.
const auto kDefaultTargetEnv = SPV_ENV_VULKAN_1_1;
/// The type of the mutator to run.
enum class MutatorType {
kNone = 0,
kFuzz = 1 << 0,
kReduce = 1 << 1,
kOpt = 1 << 2,
kAll = kFuzz | kReduce | kOpt
};
inline MutatorType operator|(MutatorType a, MutatorType b) {
return static_cast<MutatorType>(static_cast<int>(a) | static_cast<int>(b));
}
inline MutatorType operator&(MutatorType a, MutatorType b) {
return static_cast<MutatorType>(static_cast<int>(a) & static_cast<int>(b));
}
/// Shading language to target during fuzzing.
enum class FuzzingTarget {
kNone = 0,
kHlsl = 1 << 0,
kMsl = 1 << 1,
kSpv = 1 << 2,
kWgsl = 1 << 3,
kAll = kHlsl | kMsl | kSpv | kWgsl
};
inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
}
inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
}
/// These parameters are accepted by various mutators and thus they are accepted
/// by both the fuzzer and the mutator debugger.
struct MutatorCliParams {
spv_target_env target_env = kDefaultTargetEnv;
uint32_t transformation_batch_size = 3;
uint32_t reduction_batch_size = 3;
uint32_t opt_batch_size = 6;
std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donors = {};
spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy =
spvtools::fuzz::RepeatedPassStrategy::kSimple;
bool enable_all_fuzzer_passes = false;
bool enable_all_reduce_passes = false;
bool validate_after_each_opt_pass = true;
bool validate_after_each_fuzzer_pass = true;
bool validate_after_each_reduce_pass = true;
};
/// Parameters specific to the fuzzer.
struct FuzzerCliParams {
uint32_t mutator_cache_size = 20;
MutatorType mutator_type = MutatorType::kAll;
FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
std::string error_dir;
MutatorCliParams mutator_params;
};
/// Parameters specific to the mutator debugger.
struct MutatorDebuggerCliParams {
MutatorType mutator_type = MutatorType::kNone;
uint32_t seed = 0;
std::vector<uint32_t> original_binary;
MutatorCliParams mutator_params;
};
/// Parses CLI parameters for the fuzzer. This function exits with an error code
/// and a message is printed to the console if some parameter has invalid
/// format. You can pass `--help` to check out all available parameters.
///
/// @param argc - the number of parameters (identical to the `argc` in `main`
/// function).
/// @param argv - array of C strings of parameters.
/// @return the parsed parameters.
FuzzerCliParams ParseFuzzerCliParams(int argc, const char* const* argv);
/// Parses CLI parameters for the mutator debugger. This function exits with an
/// error code and a message is printed to the console if some parameter has
/// invalid format. You can pass `--help` to check out all available parameters.
///
/// @param argc - the number of parameters (identical to the `argc` in `main`
/// function).
/// @param argv - array of C strings of parameters.
/// @return the parsed parameters.
MutatorDebuggerCliParams ParseMutatorDebuggerCliParams(int argc,
const char* const* argv);
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_

View File

@ -0,0 +1,216 @@
// 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 <iostream>
#include <memory>
#include <random>
#include <string>
#include <vector>
#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/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"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
namespace {
struct Context {
const 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)};
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");
std::mt19937 rng(seed);
auto mutator_type =
types[std::uniform_int_distribution<size_t>(0, types.size() - 1)(rng)];
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;
}
}
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
size_t size,
size_t max_size,
unsigned seed) {
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 dummy cache if the user has decided not to use a real cache.
// The dummy 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)) {
// 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;
}
CommonFuzzer spv_to_wgsl(InputFormat::kSpv, OutputFormat::kWGSL);
spv_to_wgsl.EnableInspector();
spv_to_wgsl.Run(data, size);
if (spv_to_wgsl.HasErrors()) {
util::LogSpvError(spv_to_wgsl.GetErrors(), data, size,
context->params.error_dir);
return 0;
}
const auto* writer =
static_cast<const writer::wgsl::Generator*>(spv_to_wgsl.GetWriter());
assert(writer && writer->error().empty() &&
"Errors should have already been handled");
auto wgsl = writer->result();
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()) {
util::LogWgslError(fuzzer.GetErrors(), data, size, wgsl, target.second,
context->params.error_dir);
}
}
return 0;
}
} // namespace
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,34 @@
// 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/mutator.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
// We need to define constructor here so that vtable is produced in this
// translation unit (see -Wweak-vtables clang flag).
Mutator::~Mutator() = default;
Mutator::Result::Result(Status status, bool is_changed)
: status_(status), is_changed_(is_changed) {
assert((is_changed || status == Status::kStuck ||
status == Status::kLimitReached) &&
"Returning invalid result state");
}
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,108 @@
// 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.
#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
#include <cassert>
#include <cstdint>
#include <string>
#include <vector>
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
/// This is an interface that is used to define custom mutators based on the
/// SPIR-V tools.
class Mutator {
public:
/// The status of the mutation.
enum class Status {
/// Binary is valid, the limit is not reached - can mutate further.
kComplete,
/// The binary is valid, the limit of mutations has been reached -
/// can't mutate further.
kLimitReached,
/// The binary is valid, the limit is not reached but the mutator has spent
/// too much time without mutating anything - better to restart to make sure
/// we can make any progress.
kStuck,
/// The binary is invalid - this is likely a bug in the mutator - must
/// abort.
kInvalid
};
/// Represents the result of the mutation. The following states are possible:
/// - if `IsChanged() == false`, then `GetStatus()` can be either
/// `kLimitReached` or `kStuck`.
/// - otherwise, any value of `Status` is possible.
class Result {
public:
/// Constructor.
/// @param status - the status of the mutation.
/// @param is_changed - whether the module was changed during mutation.
Result(Status status, bool is_changed);
/// @return the status of the mutation.
Status GetStatus() const { return status_; }
/// @return whether the module was changed during mutation.
bool IsChanged() const { return is_changed_; }
private:
Status status_;
bool is_changed_;
};
/// Virtual destructor.
virtual ~Mutator();
/// Causes the mutator to apply a mutation. This method can be called
/// multiple times as long as the previous call didn't return
/// `Status::kInvalid`.
///
/// @return the status of the mutation (e.g. success, error etc) and whether
/// the binary was changed during mutation.
virtual Result Mutate() = 0;
/// Returns the mutated binary. The returned binary is guaranteed to be valid
/// iff the previous call to the `Mutate` method returned didn't return
/// `Status::kInvalid`.
///
/// @return the mutated SPIR-V binary. It might be identical to the original
/// binary if `Result::IsChanged` returns `false`.
virtual std::vector<uint32_t> GetBinary() const = 0;
/// Returns errors, produced by the mutator.
///
/// @param path - the directory to which the errors are printed to. No files
/// are created if the `path` is nullptr.
/// @param count - the number of the error. Files for this error will be
/// prefixed with `count`.
virtual void LogErrors(const std::string* path, uint32_t count) const = 0;
/// @return errors encountered during the mutation. The returned string is
/// if there were no errors during mutation.
virtual std::string GetErrors() const = 0;
};
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_

View File

@ -0,0 +1,78 @@
// 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/mutator_cache.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
MutatorCache::MutatorCache(size_t max_size)
: map_(), entries_(), max_size_(max_size) {
assert(max_size && "`max_size` may not be 0");
}
MutatorCache::Value::pointer MutatorCache::Get(const Key& key) {
auto it = map_.find(key);
if (it == map_.end()) {
return nullptr;
}
UpdateUsage(it);
return entries_.front().second.get();
}
void MutatorCache::Put(const Key& key, Value value) {
assert(value && "Mutator cache can't have nullptr unique_ptr");
auto it = map_.find(key);
if (it != map_.end()) {
it->second->second = std::move(value);
UpdateUsage(it);
} else {
if (map_.size() == max_size_) {
Remove(*entries_.back().first);
}
entries_.emplace_front(nullptr, std::move(value));
auto pair = map_.emplace(key, entries_.begin());
assert(pair.second && "The key must be unique");
entries_.front().first = &pair.first->first;
}
}
MutatorCache::Value MutatorCache::Remove(const Key& key) {
auto it = map_.find(key);
if (it == map_.end()) {
return nullptr;
}
auto result = std::move(it->second->second);
entries_.erase(it->second);
map_.erase(it);
return result;
}
size_t MutatorCache::KeyHash::operator()(
const std::vector<uint32_t>& vec) const {
return std::hash<std::u32string>()({vec.begin(), vec.end()});
}
void MutatorCache::UpdateUsage(Map::iterator it) {
auto entry = std::move(*it->second);
entries_.erase(it->second);
entries_.push_front(std::move(entry));
it->second = entries_.begin();
}
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,99 @@
// 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.
#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
#include <cassert>
#include <list>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
/// Implementation of a fixed size LRU cache. That is, when the number of
/// elements reaches a certain threshold, the element that wasn't used for the
/// longest period of time is removed from the cache when a new element is
/// inserted. All operations have amortized constant time complexity.
class MutatorCache {
public:
/// SPIR-V binary that is being mutated.
using Key = std::vector<uint32_t>;
/// Mutator that is used to mutate the `Key`.
using Value = std::unique_ptr<Mutator>;
/// Constructor.
/// @param max_size - the maximum number of elements the cache can store. May
/// not be equal to 0.
explicit MutatorCache(size_t max_size);
/// Retrieves a pointer to a value, associated with a given `key`.
///
/// If the key is present in the cache, its usage is updated and the
/// (non-null) pointer to the value is returned. Otherwise, `nullptr` is
/// returned.
///
/// @param key - may not exist in this cache.
/// @return non-`nullptr` pointer to a value if `key` exists in the cache.
/// @return `nullptr` if `key` doesn't exist in this cache.
Value::pointer Get(const Key& key);
/// Inserts a `key`-`value` pair into the cache.
///
/// If the `key` is already present, the `value` replaces the old value and
/// the usage of `key` is updated. If the `key` is not present, then:
/// - if the number of elements in the cache is equal to `max_size`, the
/// key-value pair, where the usage of the key wasn't updated for the
/// longest period of time, is removed from the cache.
/// - a new `key`-`value` pair is inserted into the cache.
///
/// @param key - a key.
/// @param value - may not be a `nullptr`.
void Put(const Key& key, Value value);
/// Removes `key` and an associated value from the cache.
///
/// @param key - a key.
/// @return a non-`nullptr` pointer to the removed value, associated with
/// `key`.
/// @return `nullptr` if `key` is not present in the cache.
Value Remove(const Key& key);
private:
struct KeyHash {
size_t operator()(const std::vector<uint32_t>& vec) const;
};
using Entry = std::pair<const Key*, Value>;
using Map = std::unordered_map<Key, std::list<Entry>::iterator, KeyHash>;
void UpdateUsage(Map::iterator it);
Map map_;
std::list<Entry> entries_;
const size_t max_size_;
};
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_

View File

@ -0,0 +1,84 @@
// 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 <memory>
#include <string>
#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
#include "fuzzers/tint_spirv_tools_fuzzer/mutator.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"
/// 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.
int main(int argc, const char** argv) {
auto params =
tint::fuzzers::spvtools_fuzzer::ParseMutatorDebuggerCliParams(argc, argv);
std::unique_ptr<tint::fuzzers::spvtools_fuzzer::Mutator> mutator;
const auto& mutator_params = params.mutator_params;
switch (params.mutator_type) {
case tint::fuzzers::spvtools_fuzzer::MutatorType::kFuzz:
mutator =
std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvFuzzMutator>(
mutator_params.target_env, params.original_binary, params.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);
break;
case tint::fuzzers::spvtools_fuzzer::MutatorType::kReduce:
mutator =
std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvReduceMutator>(
mutator_params.target_env, params.original_binary, params.seed,
mutator_params.reduction_batch_size,
mutator_params.enable_all_reduce_passes,
mutator_params.validate_after_each_reduce_pass);
break;
case tint::fuzzers::spvtools_fuzzer::MutatorType::kOpt:
mutator =
std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvOptMutator>(
mutator_params.target_env, params.seed, params.original_binary,
mutator_params.validate_after_each_opt_pass,
mutator_params.opt_batch_size);
break;
default:
assert(false && "All mutator types must've been handled");
return 1;
}
while (true) {
auto result = mutator->Mutate();
if (result.GetStatus() ==
tint::fuzzers::spvtools_fuzzer::Mutator::Status::kInvalid) {
std::cerr << mutator->GetErrors() << std::endl;
return 0;
}
if (result.GetStatus() ==
tint::fuzzers::spvtools_fuzzer::Mutator::Status::kLimitReached) {
break;
}
}
}

View File

@ -0,0 +1,127 @@
// 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/spirv_fuzz_mutator.h"
#include <fstream>
#include <utility>
#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
#include "source/opt/build_module.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
SpirvFuzzMutator::SpirvFuzzMutator(
spv_target_env target_env,
std::vector<uint32_t> binary,
unsigned seed,
const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors,
bool enable_all_passes,
spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
bool validate_after_each_pass,
uint32_t transformation_batch_size)
: transformation_batch_size_(transformation_batch_size),
errors_(std::make_unique<std::stringstream>()),
fuzzer_(nullptr),
validator_options_(),
original_binary_(std::move(binary)),
seed_(seed) {
auto ir_context = spvtools::BuildModule(
target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
original_binary_.data(), original_binary_.size());
assert(ir_context && "|binary| is invalid");
auto transformation_context =
std::make_unique<spvtools::fuzz::TransformationContext>(
std::make_unique<spvtools::fuzz::FactManager>(ir_context.get()),
validator_options_);
auto fuzzer_context = std::make_unique<spvtools::fuzz::FuzzerContext>(
std::make_unique<spvtools::fuzz::PseudoRandomGenerator>(seed),
spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()), false);
fuzzer_ = std::make_unique<spvtools::fuzz::Fuzzer>(
std::move(ir_context), std::move(transformation_context),
std::move(fuzzer_context), util::GetBufferMessageConsumer(errors_.get()),
donors, enable_all_passes, repeated_pass_strategy,
validate_after_each_pass, validator_options_);
}
Mutator::Result SpirvFuzzMutator::Mutate() {
// The assertion will fail in |fuzzer_->Run| if the previous fuzzing led to
// invalid module.
auto result = fuzzer_->Run(transformation_batch_size_);
switch (result.status) {
case spvtools::fuzz::Fuzzer::Status::kComplete:
return {Mutator::Status::kComplete, result.is_changed};
case spvtools::fuzz::Fuzzer::Status::kModuleTooBig:
case spvtools::fuzz::Fuzzer::Status::kTransformationLimitReached:
return {Mutator::Status::kLimitReached, result.is_changed};
case spvtools::fuzz::Fuzzer::Status::kFuzzerStuck:
return {Mutator::Status::kStuck, result.is_changed};
case spvtools::fuzz::Fuzzer::Status::kFuzzerPassLedToInvalidModule:
return {Mutator::Status::kInvalid, result.is_changed};
}
}
std::vector<uint32_t> SpirvFuzzMutator::GetBinary() const {
std::vector<uint32_t> result;
fuzzer_->GetIRContext()->module()->ToBinary(&result, true);
return result;
}
std::string SpirvFuzzMutator::GetErrors() const {
return errors_->str();
}
void SpirvFuzzMutator::LogErrors(const std::string* path,
uint32_t count) const {
auto message = GetErrors();
std::cout << count << " | SpirvFuzzMutator (seed: " << seed_ << ")"
<< std::endl;
std::cout << message << std::endl;
if (path) {
auto prefix = *path + std::to_string(count);
// Write errors to file.
std::ofstream(prefix + ".fuzzer.log") << "seed: " << seed_ << std::endl
<< message << std::endl;
// Write the invalid SPIR-V binary.
util::WriteBinary(prefix + ".fuzzer.invalid.spv", GetBinary());
// Write the original SPIR-V binary.
util::WriteBinary(prefix + ".fuzzer.original.spv", original_binary_);
// Write transformations.
google::protobuf::util::JsonOptions options;
options.add_whitespace = true;
std::string json;
google::protobuf::util::MessageToJsonString(
fuzzer_->GetTransformationSequence(), &json, options);
std::ofstream(prefix + ".fuzzer.transformations.json") << json << std::endl;
std::ofstream binary_transformations(
prefix + ".fuzzer.transformations.binary",
std::ios::binary | std::ios::out);
fuzzer_->GetTransformationSequence().SerializeToOstream(
&binary_transformations);
}
}
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,95 @@
// 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.
#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
#include "source/fuzz/fuzzer.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/pseudo_random_generator.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
/// The mutator that uses spirv-fuzz to mutate SPIR-V.
///
/// The initial `binary` must be valid according to `target_env`. All other
/// parameters (except for the `seed` which just initializes the RNG) are from
/// the `spvtools::fuzz::Fuzzer` class.
class SpirvFuzzMutator : public Mutator {
public:
/// Constructor.
/// @param target_env - the target environment for the `binary`.
/// @param binary - the SPIR-V binary. Must be valid.
/// @param seed - seed for the RNG.
/// @param donors - vector of donor suppliers.
/// @param enable_all_passes - whether to use all fuzzer passes.
/// @param repeated_pass_strategy - the strategy to use when selecting the
/// next fuzzer pass.
/// @param validate_after_each_pass - whether to validate the binary after
/// each fuzzer pass.
/// @param transformation_batch_size - the maximum number of transformations
/// that will be applied during a single call to `Mutate`. It it's equal
/// to 0 then we apply as much transformations as we can until the
/// threshold in the spvtools::fuzz::Fuzzer is reached (see the doc for
/// that class for more info).
SpirvFuzzMutator(
spv_target_env target_env,
std::vector<uint32_t> binary,
uint32_t seed,
const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors,
bool enable_all_passes,
spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
bool validate_after_each_pass,
uint32_t transformation_batch_size);
Result Mutate() override;
std::vector<uint32_t> GetBinary() const override;
void LogErrors(const std::string* path, uint32_t count) const override;
std::string GetErrors() const override;
private:
// The number of transformations that will be applied during a single call to
// the `Mutate` method. Is this only a lower bound since transformations are
// applied in batches by fuzzer passes (see docs for the
// `spvtools::fuzz::Fuzzer` for more info).
const uint32_t transformation_batch_size_;
// The errors produced by the `spvtools::fuzz::Fuzzer`.
std::unique_ptr<std::stringstream> errors_;
std::unique_ptr<spvtools::fuzz::Fuzzer> fuzzer_;
spvtools::ValidatorOptions validator_options_;
// The following fields are useful for debugging.
// The binary that the mutator is constructed with.
const std::vector<uint32_t> original_binary_;
// The seed that the mutator is constructed with.
const uint32_t seed_;
};
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_

View File

@ -0,0 +1,159 @@
// 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/spirv_opt_mutator.h"
#include <fstream>
#include <iostream>
#include <unordered_set>
#include <utility>
#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
#include "spirv-tools/optimizer.hpp"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
SpirvOptMutator::SpirvOptMutator(spv_target_env target_env,
uint32_t seed,
std::vector<uint32_t> binary,
bool validate_after_each_opt,
uint32_t opt_batch_size)
: num_executions_(0),
is_valid_(true),
target_env_(target_env),
original_binary_(std::move(binary)),
seed_(seed),
opt_passes_({"--combine-access-chains",
"--loop-unroll",
"--merge-blocks",
"--cfg-cleanup",
"--eliminate-dead-functions",
"--merge-return",
"--wrap-opkill",
"--eliminate-dead-code-aggressive",
"--if-conversion",
"--eliminate-local-single-store",
"--eliminate-local-single-block",
"--eliminate-dead-branches",
"--scalar-replacement=0",
"--eliminate-dead-inserts",
"--eliminate-dead-members",
"--simplify-instructions",
"--private-to-local",
"--ssa-rewrite",
"--ccp",
"--reduce-load-size",
"--vector-dce",
"--scalar-replacement=100",
"--inline-entry-points-exhaustive",
"--redundancy-elimination",
"--convert-local-access-chains",
"--copy-propagate-arrays",
"--fix-storage-class"}),
optimized_binary_(),
validate_after_each_opt_(validate_after_each_opt),
opt_batch_size_(opt_batch_size),
rng_(seed) {
assert(spvtools::SpirvTools(target_env).Validate(original_binary_) &&
"Initial binary is invalid");
assert(!opt_passes_.empty() && "Must be at least one pass");
}
SpirvOptMutator::Result SpirvOptMutator::Mutate() {
assert(is_valid_ && "The optimizer is not longer valid");
const uint32_t kMaxNumExecutions = 100;
const uint32_t kMaxNumStuck = 10;
if (num_executions_ == kMaxNumExecutions) {
// We've applied this mutator many times already. Indicate to the user that
// it might be better to try a different mutator.
return {Status::kLimitReached, false};
}
num_executions_++;
// Get the input binary. If this is the first time we run this mutator, use
// the `original_binary_`. Otherwise, one of the following will be true:
// - the `optimized_binary_` is not empty.
// - the previous call to the `Mutate` method returned `kStuck`.
auto binary = num_executions_ == 1 ? original_binary_ : optimized_binary_;
optimized_binary_.clear();
assert(!binary.empty() && "Can't run the optimizer on an empty binary");
// Number of times spirv-opt wasn't able to produce any new result.
uint32_t num_stuck = 0;
do {
// Randomly select `opt_batch_size` optimization passes. If `opt_batch_size`
// is equal to 0, we will use the number of passes equal to the number of
// all available passes.
auto num_of_passes = opt_batch_size_ ? opt_batch_size_ : opt_passes_.size();
std::vector<std::string> passes;
while (passes.size() < num_of_passes) {
auto idx = std::uniform_int_distribution<size_t>(
0, opt_passes_.size() - 1)(rng_);
passes.push_back(opt_passes_[idx]);
}
// Run the `binary` into the `optimized_binary_`.
spvtools::Optimizer optimizer(target_env_);
optimizer.SetMessageConsumer(util::GetBufferMessageConsumer(&errors_));
optimizer.SetValidateAfterAll(validate_after_each_opt_);
optimizer.RegisterPassesFromFlags(passes);
if (!optimizer.Run(binary.data(), binary.size(), &optimized_binary_)) {
is_valid_ = false;
return {Status::kInvalid, true};
}
} while (optimized_binary_.empty() && ++num_stuck < kMaxNumStuck);
return {optimized_binary_.empty() ? Status::kStuck : Status::kComplete,
!optimized_binary_.empty()};
}
std::vector<uint32_t> SpirvOptMutator::GetBinary() const {
return optimized_binary_;
}
std::string SpirvOptMutator::GetErrors() const {
return errors_.str();
}
void SpirvOptMutator::LogErrors(const std::string* path, uint32_t count) const {
auto message = GetErrors();
std::cout << count << " | SpirvOptMutator (seed: " << seed_ << ")"
<< std::endl;
std::cout << message << std::endl;
if (path) {
auto prefix = *path + std::to_string(count);
// Write errors to file.
std::ofstream(prefix + ".opt.log") << "seed: " << seed_ << std::endl
<< message << std::endl;
// Write the invalid SPIR-V binary.
util::WriteBinary(prefix + ".opt.invalid.spv", optimized_binary_);
// Write the original SPIR-V binary.
util::WriteBinary(prefix + ".opt.original.spv", original_binary_);
}
}
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,96 @@
// 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.
#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
#include <random>
#include <sstream>
#include <string>
#include <vector>
#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
#include "spirv-tools/libspirv.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
/// Mutates the SPIR-V module using the spirv-opt tool.
///
/// The initial `binary` must be valid according to `target_env`. On each call
/// to the `Mutate` method the mutator selects `opt_batch_size` random
/// optimization passes (with substitutions) and applies them to the binary.
class SpirvOptMutator : public Mutator {
public:
/// Constructor.
/// @param target_env - target environment for the `binary`.
/// @param seed - seed for the RNG.
/// @param binary - SPIR-V binary. Must be valid.
/// @param validate_after_each_opt - whether to validate the binary after each
/// optimization pass.
/// @param opt_batch_size - the maximum number of optimization passes that
/// will be applied in a single call to `Mutate`. If it's equal to 0 then
/// all available optimization passes are applied.
SpirvOptMutator(spv_target_env target_env,
uint32_t seed,
std::vector<uint32_t> binary,
bool validate_after_each_opt,
uint32_t opt_batch_size);
Result Mutate() override;
std::vector<uint32_t> GetBinary() const override;
void LogErrors(const std::string* path, uint32_t count) const override;
std::string GetErrors() const override;
private:
// Number of times this mutator was executed.
uint32_t num_executions_;
// Whether the last execution left it in a valid state.
bool is_valid_;
// Target environment for the SPIR-V binary.
const spv_target_env target_env_;
// The original SPIR-V binary. Useful for debugging.
const std::vector<uint32_t> original_binary_;
// The seed for the RNG. Useful for debugging.
const uint32_t seed_;
// All the optimization passes available.
const std::vector<std::string> opt_passes_;
// The result of the optimization.
std::vector<uint32_t> optimized_binary_;
// Whether we need to validate the binary after each optimization pass.
const bool validate_after_each_opt_;
// The number of optimization passes to apply at once.
const uint32_t opt_batch_size_;
// All the errors produced by the optimizer.
std::stringstream errors_;
// The random number generator initialized with `seed_`.
std::mt19937 rng_;
};
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_

View File

@ -0,0 +1,190 @@
// 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/spirv_reduce_mutator.h"
#include <fstream>
#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/opt/build_module.h"
#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
#include "source/reduce/remove_block_reduction_opportunity_finder.h"
#include "source/reduce/remove_function_reduction_opportunity_finder.h"
#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
SpirvReduceMutator::SpirvReduceMutator(spv_target_env target_env,
std::vector<uint32_t> binary,
uint32_t seed,
uint32_t reductions_batch_size,
bool enable_all_reductions,
bool validate_after_each_reduction)
: ir_context_(nullptr),
finders_(),
rng_(seed),
errors_(),
is_valid_(true),
reductions_batch_size_(reductions_batch_size),
total_applied_reductions_(0),
enable_all_reductions_(enable_all_reductions),
validate_after_each_reduction_(validate_after_each_reduction),
original_binary_(std::move(binary)),
seed_(seed) {
ir_context_ = spvtools::BuildModule(
target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
original_binary_.data(), original_binary_.size());
assert(ir_context_ && "|binary| is invalid");
do {
MaybeAddFinder<
spvtools::reduce::
ConditionalBranchToSimpleConditionalBranchOpportunityFinder>();
MaybeAddFinder<spvtools::reduce::MergeBlocksReductionOpportunityFinder>();
MaybeAddFinder<
spvtools::reduce::OperandToConstReductionOpportunityFinder>();
MaybeAddFinder<
spvtools::reduce::OperandToDominatingIdReductionOpportunityFinder>();
MaybeAddFinder<
spvtools::reduce::OperandToUndefReductionOpportunityFinder>();
MaybeAddFinder<spvtools::reduce::RemoveBlockReductionOpportunityFinder>();
MaybeAddFinder<
spvtools::reduce::RemoveFunctionReductionOpportunityFinder>();
MaybeAddFinder<
spvtools::reduce::RemoveSelectionReductionOpportunityFinder>();
MaybeAddFinder<
spvtools::reduce::RemoveUnusedInstructionReductionOpportunityFinder>(
true);
MaybeAddFinder<
spvtools::reduce::RemoveUnusedStructMemberReductionOpportunityFinder>();
MaybeAddFinder<
spvtools::reduce::SimpleConditionalBranchToBranchOpportunityFinder>();
MaybeAddFinder<spvtools::reduce::
StructuredLoopToSelectionReductionOpportunityFinder>();
} while (finders_.empty());
}
Mutator::Result SpirvReduceMutator::Mutate() {
assert(is_valid_ && "Can't mutate invalid module");
// The upper limit on the number of applied reduction passes.
const uint32_t kMaxAppliedReductions = 500;
const auto old_applied_reductions = total_applied_reductions_;
// The upper limit on the number of failed attempts to apply reductions (i.e.
// when no reduction was returned by the reduction finder).
const uint32_t kMaxConsecutiveFailures = 10;
uint32_t num_consecutive_failures = 0;
// Iterate while we haven't exceeded the limit on the total number of applied
// reductions, the limit on the number of reductions applied at once and limit
// on the number of consecutive failed attempts.
while (total_applied_reductions_ < kMaxAppliedReductions &&
(reductions_batch_size_ == 0 ||
total_applied_reductions_ - old_applied_reductions <
reductions_batch_size_) &&
num_consecutive_failures < kMaxConsecutiveFailures) {
// Select an opportunity finder and get some reduction opportunities from
// it.
auto finder = GetRandomElement(&finders_);
auto reduction_opportunities =
finder->GetAvailableOpportunities(ir_context_.get(), 0);
if (reduction_opportunities.empty()) {
// There is nothing to reduce. We increase the counter to make sure we
// don't stuck in this situation.
num_consecutive_failures++;
} else {
// Apply a random reduction opportunity. The latter should be applicable.
auto opportunity = GetRandomElement(&reduction_opportunities);
assert(opportunity->PreconditionHolds() && "Preconditions should hold");
total_applied_reductions_++;
num_consecutive_failures = 0;
if (!ApplyReduction(opportunity)) {
// The module became invalid as a result of the applied reduction.
is_valid_ = false;
return {Mutator::Status::kInvalid,
total_applied_reductions_ != old_applied_reductions};
}
}
}
auto is_changed = total_applied_reductions_ != old_applied_reductions;
if (total_applied_reductions_ == kMaxAppliedReductions) {
return {Mutator::Status::kLimitReached, is_changed};
}
if (num_consecutive_failures == kMaxConsecutiveFailures) {
return {Mutator::Status::kStuck, is_changed};
}
assert(is_changed && "This is the only way left to break the loop");
return {Mutator::Status::kComplete, is_changed};
}
bool SpirvReduceMutator::ApplyReduction(
spvtools::reduce::ReductionOpportunity* reduction_opportunity) {
reduction_opportunity->TryToApply();
return !validate_after_each_reduction_ ||
spvtools::fuzz::fuzzerutil::IsValidAndWellFormed(
ir_context_.get(), spvtools::ValidatorOptions(),
util::GetBufferMessageConsumer(&errors_));
}
std::vector<uint32_t> SpirvReduceMutator::GetBinary() const {
std::vector<uint32_t> result;
ir_context_->module()->ToBinary(&result, true);
return result;
}
std::string SpirvReduceMutator::GetErrors() const {
return errors_.str();
}
void SpirvReduceMutator::LogErrors(const std::string* path,
uint32_t count) const {
auto message = GetErrors();
std::cout << count << " | SpirvReduceMutator (seed: " << seed_ << ")"
<< std::endl;
std::cout << message << std::endl;
if (path) {
auto prefix = *path + std::to_string(count);
// Write errors to file.
std::ofstream(prefix + ".reducer.log") << "seed: " << seed_ << std::endl
<< message << std::endl;
// Write the invalid SPIR-V binary.
util::WriteBinary(prefix + ".reducer.invalid.spv", GetBinary());
// Write the original SPIR-V binary.
util::WriteBinary(prefix + ".reducer.original.spv", original_binary_);
}
}
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,134 @@
// 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.
#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
#include <memory>
#include <random>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
#include "source/reduce/reduction_opportunity_finder.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
/// Mutates SPIR-V binary by running spirv-reduce tool.
///
/// The initial `binary` must be valid according to `target_env`. Applies at
/// most `reductions_batch_size` reductions at a time. This parameter is ignored
/// if its value is 0. Uses a random subset of reduction opportunity finders by
/// default. This can be overridden with the `enable_all_reductions` parameter.
class SpirvReduceMutator : public Mutator {
public:
/// Constructor.
/// @param target_env - the target environment for the `binary`.
/// @param binary - SPIR-V binary. Must be valid.
/// @param seed - the seed for the RNG.
/// @param reductions_batch_size - the number of reduction passes that will be
/// applied during a single call to `Mutate`. If it's equal to 0 then we
/// apply the passes until we reach the threshold for the total number of
/// applied passes.
/// @param enable_all_reductions - whether to use all reduction passes or only
/// a randomly selected subset of them.
/// @param validate_after_each_reduction - whether to validate after each
/// applied reduction.
SpirvReduceMutator(spv_target_env target_env,
std::vector<uint32_t> binary,
uint32_t seed,
uint32_t reductions_batch_size,
bool enable_all_reductions,
bool validate_after_each_reduction);
Result Mutate() override;
std::vector<uint32_t> GetBinary() const override;
void LogErrors(const std::string* path, uint32_t count) const override;
std::string GetErrors() const override;
private:
template <typename T, typename... Args>
void MaybeAddFinder(Args&&... args) {
if (enable_all_reductions_ || std::uniform_int_distribution<>(0, 1)(rng_)) {
finders_.push_back(std::make_unique<T>(std::forward<Args>(args)...));
}
}
template <typename T>
T* GetRandomElement(std::vector<T>* arr) {
assert(!arr->empty() && "Can't get random element from an empty vector");
auto index =
std::uniform_int_distribution<size_t>(0, arr->size() - 1)(rng_);
return &(*arr)[index];
}
template <typename T>
T* GetRandomElement(std::vector<std::unique_ptr<T>>* arr) {
assert(!arr->empty() && "Can't get random element from an empty vector");
auto index =
std::uniform_int_distribution<size_t>(0, arr->size() - 1)(rng_);
return (*arr)[index].get();
}
bool ApplyReduction(
spvtools::reduce::ReductionOpportunity* reduction_opportunity);
// The SPIR-V binary that is being reduced.
std::unique_ptr<spvtools::opt::IRContext> ir_context_;
// The selected subset of reduction opportunity finders.
std::vector<std::unique_ptr<spvtools::reduce::ReductionOpportunityFinder>>
finders_;
// Random number generator initialized with `seed_`.
std::mt19937 rng_;
// All the errors produced by the reducer.
std::stringstream errors_;
// Whether the last call to the `Mutate` method produced the valid binary.
bool is_valid_;
// The number of reductions to apply on a single call to `Mutate`.
const uint32_t reductions_batch_size_;
// The total number of applied reductions.
uint32_t total_applied_reductions_;
// Whether we want to use all the reduction opportunity finders and not just a
// subset of them.
const bool enable_all_reductions_;
// Whether we want to validate all the binary after each reduction.
const bool validate_after_each_reduction_;
// The original binary that was used to initialize this mutator.
// Useful for debugging.
const std::vector<uint32_t> original_binary_;
// The seed that was used to initialize the random number generator.
// Useful for debugging.
const uint32_t seed_;
};
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_

View File

@ -0,0 +1,160 @@
// 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 <fstream>
#include <iostream>
#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
namespace util {
namespace {
bool WriteBinary(const std::string& path, const uint8_t* data, size_t size) {
std::ofstream spv(path, std::ios::binary);
return spv && spv.write(reinterpret_cast<const char*>(data),
static_cast<std::streamsize>(size));
}
void LogError(uint32_t index,
const std::string& type,
const std::string& message,
const std::string* path,
const uint8_t* data,
size_t size,
const std::string* wgsl) {
std::cout << index << " | " << type << ": " << message << std::endl;
if (path) {
auto prefix = *path + std::to_string(index);
std::ofstream(prefix + ".log") << message << std::endl;
WriteBinary(prefix + ".spv", data, size);
if (wgsl) {
std::ofstream(prefix + ".wgsl") << *wgsl << std::endl;
}
}
}
} // namespace
spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer) {
return [buffer](spv_message_level_t level, const char*,
const spv_position_t& position, const char* message) {
std::string status;
switch (level) {
case SPV_MSG_FATAL:
case SPV_MSG_INTERNAL_ERROR:
case SPV_MSG_ERROR:
status = "ERROR";
break;
case SPV_MSG_WARNING:
case SPV_MSG_INFO:
case SPV_MSG_DEBUG:
status = "INFO";
break;
}
*buffer << status << " " << position.line << ":" << position.column << ":"
<< position.index << ": " << message << std::endl;
};
}
void LogMutatorError(const Mutator& mutator, const std::string& error_dir) {
static uint32_t mutator_count = 0;
auto error_path = error_dir.empty() ? error_dir : error_dir + "/mutator/";
mutator.LogErrors(error_dir.empty() ? nullptr : &error_path, mutator_count++);
}
void LogWgslError(const std::string& message,
const uint8_t* data,
size_t size,
const std::string& wgsl,
OutputFormat output_format,
const std::string& error_dir) {
static uint32_t wgsl_count = 0;
std::string error_type;
switch (output_format) {
case OutputFormat::kSpv:
error_type = "WGSL -> SPV";
break;
case OutputFormat::kMSL:
error_type = "WGSL -> MSL";
break;
case OutputFormat::kHLSL:
error_type = "WGSL -> HLSL";
break;
case OutputFormat::kWGSL:
error_type = "WGSL -> WGSL";
break;
default:
assert(false && "All output formats should've been handled");
break;
}
auto error_path = error_dir.empty() ? error_dir : error_dir + "/wgsl/";
LogError(wgsl_count++, error_type, message,
error_dir.empty() ? nullptr : &error_path, data, size, &wgsl);
}
void LogSpvError(const std::string& message,
const uint8_t* data,
size_t size,
const std::string& error_dir) {
static uint32_t spv_count = 0;
auto error_path = error_dir.empty() ? error_dir : error_dir + "/spv/";
LogError(spv_count++, "SPV -> WGSL", message,
error_dir.empty() ? nullptr : &error_path, data, size, nullptr);
}
bool ReadBinary(const std::string& path, std::vector<uint32_t>* out) {
if (!out) {
return false;
}
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
return false;
}
auto size = file.tellg();
if (!file) {
return false;
}
file.seekg(0);
if (!file) {
return false;
}
std::vector<char> binary(static_cast<size_t>(size));
if (!file.read(binary.data(), size)) {
return false;
}
out->resize(binary.size() / sizeof(uint32_t));
std::memcpy(out->data(), binary.data(), binary.size());
return true;
}
bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary) {
return WriteBinary(path, reinterpret_cast<const uint8_t*>(binary.data()),
binary.size() * sizeof(uint32_t));
}
} // namespace util
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,96 @@
// 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.
#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
#include <sstream>
#include <string>
#include <vector>
#include "fuzzers/tint_common_fuzzer.h"
#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
#include "spirv-tools/libspirv.hpp"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
namespace util {
/// @param buffer will be used to output errors by the returned message
/// consumer. Must remain in scope as long as the returned consumer is in
/// scope.
/// @return the message consumer that will print errors to the `buffer`.
spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer);
/// Output errors from the SPV -> WGSL conversion.
///
/// @param message - the error message.
/// @param data - invalid SPIR-V binary.
/// @param size - the size of `data`.
/// @param error_dir - the directory, to which the binary will be printed to.
/// If it's empty, the invalid binary and supplemental files will not be
/// printed. Otherwise, it must have a `spv/` subdirectory.
void LogSpvError(const std::string& message,
const uint8_t* data,
size_t size,
const std::string& error_dir);
/// Output errors from the WGSL -> `output_format` conversion.
///
/// @param message - the error message.
/// @param data - the SPIR-V binary that generated the WGSL binary.
/// @param size - the size of `data`.
/// @param wgsl - the invalid WGSL binary.
/// @param output_format - the format which we attempted to convert `wgsl` to.
/// @param error_dir - the directory, to which the binary will be printed out.
/// If it's empty, the invalid binary and supplemental files will not be
/// printed. Otherwise, it must have a `wgsl/` subdirectory.
void LogWgslError(const std::string& message,
const uint8_t* data,
size_t size,
const std::string& wgsl,
OutputFormat output_format,
const std::string& error_dir);
/// Output errors produced by the mutator.
///
/// @param mutator - the mutator with invalid state.
/// @param error_dir - the directory, to which invalid files will be printed to.
/// If it's empty, the invalid binary and supplemental files will not be
/// printed. Otherwise, it must have a `mutator/` subdirectory.
void LogMutatorError(const Mutator& mutator, const std::string& error_dir);
/// Reads SPIR-V binary from `path` into `out`. Returns `true` if successful and
/// `false` otherwise (in this case, `out` is unchanged).
///
/// @param path - the path to the SPIR-V binary.
/// @param out - may be a `nullptr`. In this case, `false` is returned.
/// @return `true` if successful and `false` otherwise.
bool ReadBinary(const std::string& path, std::vector<uint32_t>* out);
/// Writes `binary` into `path`.
///
/// @param path - the path to write `binary` to.
/// @param binary - SPIR-V binary.
/// @return whether the operation was successful.
bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary);
} // namespace util
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_

View File

@ -17,6 +17,13 @@ if (${TINT_BUILD_TESTS} AND NOT TARGET gmock)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/googletest EXCLUDE_FROM_ALL)
endif()
if (${TINT_BUILD_SPIRV_TOOLS_FUZZER} AND (NOT TARGET protobuf::libprotobuf OR NOT TARGET protobuf::protoc))
set(SPIRV_BUILD_FUZZER ON CACHE BOOL "Build spirv-fuzz")
set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests")
set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime")
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/protobuf/cmake)
endif()
if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER})
if (NOT IS_DIRECTORY "${SPIRV-Headers_SOURCE_DIR}")
set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/spirv-headers CACHE STRING "")