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:
parent
c3f19081f8
commit
0b3611b8c8
|
@ -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
|
||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -5,3 +5,4 @@
|
|||
# of contributors, see the revision history in source control.
|
||||
|
||||
Google LLC
|
||||
Vasyl Teliman
|
||||
|
|
|
@ -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
4
DEPS
|
@ -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 = [
|
||||
|
|
3
Doxyfile
3
Doxyfile
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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) {
|
||||