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/googletest
|
||||||
third_party/gpuweb-cts
|
third_party/gpuweb-cts
|
||||||
third_party/llvm-build
|
third_party/llvm-build
|
||||||
|
third_party/protobuf
|
||||||
third_party/spirv-headers
|
third_party/spirv-headers
|
||||||
third_party/spirv-tools
|
third_party/spirv-tools
|
||||||
tools/clang
|
tools/clang
|
||||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -5,3 +5,4 @@
|
||||||
# of contributors, see the revision history in source control.
|
# of contributors, see the revision history in source control.
|
||||||
|
|
||||||
Google LLC
|
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_SPV_WRITER "Build the SPIR-V output writer" ON)
|
||||||
option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON)
|
option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON)
|
||||||
option(TINT_BUILD_FUZZERS "Build fuzzers" OFF)
|
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_TESTS "Build tests" ${TINT_BUILD_TESTS_DEFAULT})
|
||||||
option(TINT_BUILD_AS_OTHER_OS "Override OS detection to force building of *_other.cc files" OFF)
|
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 SPIR-V writer: ${TINT_BUILD_SPV_WRITER}")
|
||||||
message(STATUS "Tint build WGSL writer: ${TINT_BUILD_WGSL_WRITER}")
|
message(STATUS "Tint build WGSL writer: ${TINT_BUILD_WGSL_WRITER}")
|
||||||
message(STATUS "Tint build fuzzers: ${TINT_BUILD_FUZZERS}")
|
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 tests: ${TINT_BUILD_TESTS}")
|
||||||
message(STATUS "Tint build with ASAN: ${TINT_ENABLE_ASAN}")
|
message(STATUS "Tint build with ASAN: ${TINT_ENABLE_ASAN}")
|
||||||
message(STATUS "Tint build with MSAN: ${TINT_ENABLE_MSAN}")
|
message(STATUS "Tint build with MSAN: ${TINT_ENABLE_MSAN}")
|
||||||
|
@ -77,6 +79,24 @@ message(STATUS "Tint build checking [chromium-style]: ${TINT_CHECK_CHROMIUM_STYL
|
||||||
message(STATUS "Using python3")
|
message(STATUS "Using python3")
|
||||||
find_package(PythonInterp 3 REQUIRED)
|
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.
|
# CMake < 3.15 sets /W3 in CMAKE_CXX_FLAGS. Remove it if it's there.
|
||||||
# See https://gitlab.kitware.com/cmake/cmake/-/issues/18317
|
# See https://gitlab.kitware.com/cmake/cmake/-/issues/18317
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
|
|
4
DEPS
4
DEPS
|
@ -11,6 +11,7 @@ vars = {
|
||||||
'clang_revision': 'eb5ab41f3801e2085208204fd71a490573d72dfd',
|
'clang_revision': 'eb5ab41f3801e2085208204fd71a490573d72dfd',
|
||||||
'googletest_revision': '5c8ca58edfb304b2dd5e6061f83387470826dd87',
|
'googletest_revision': '5c8ca58edfb304b2dd5e6061f83387470826dd87',
|
||||||
'gpuweb_cts_revision': '177a4faf0a7ce6f8c64b42a715c634e363912a74',
|
'gpuweb_cts_revision': '177a4faf0a7ce6f8c64b42a715c634e363912a74',
|
||||||
|
'protobuf_revision': 'fde7cf7358ec7cd69e8db9be4f1fa6a5c431386a',
|
||||||
'spirv_headers_revision': 'f5417a4b6633c3217c9a1bc2f0c70b1454975ba7',
|
'spirv_headers_revision': 'f5417a4b6633c3217c9a1bc2f0c70b1454975ba7',
|
||||||
'spirv_tools_revision': 'ecdd9a3e6bd384bf51d096b507291faa10f14685',
|
'spirv_tools_revision': 'ecdd9a3e6bd384bf51d096b507291faa10f14685',
|
||||||
'testing_revision': '2691851e49de541c3fe42fa8692ddcdee938162f',
|
'testing_revision': '2691851e49de541c3fe42fa8692ddcdee938162f',
|
||||||
|
@ -42,6 +43,9 @@ deps = {
|
||||||
|
|
||||||
'third_party/googletest': Var('chromium_git') + Var('github') +
|
'third_party/googletest': Var('chromium_git') + Var('github') +
|
||||||
'/google/googletest.git@' + Var('googletest_revision'),
|
'/google/googletest.git@' + Var('googletest_revision'),
|
||||||
|
|
||||||
|
'third_party/protobuf': Var('chromium_git') + Var('github') +
|
||||||
|
'/protocolbuffers/protobuf.git@' + Var('protobuf_revision'),
|
||||||
}
|
}
|
||||||
|
|
||||||
hooks = [
|
hooks = [
|
||||||
|
|
3
Doxyfile
3
Doxyfile
|
@ -786,7 +786,8 @@ WARN_LOGFILE =
|
||||||
# Note: If this tag is empty the current directory is searched.
|
# Note: If this tag is empty the current directory is searched.
|
||||||
|
|
||||||
INPUT = CODE_OF_CONDUCT.md \
|
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
|
# 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
|
# 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})
|
if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER})
|
||||||
add_tint_fuzzer(tint_ast_clone_fuzzer)
|
add_tint_fuzzer(tint_ast_clone_fuzzer)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (${TINT_BUILD_SPIRV_TOOLS_FUZZER})
|
||||||
|
add_subdirectory(tint_spirv_tools_fuzzer)
|
||||||
|
endif()
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "src/ast/module.h"
|
#include "src/ast/module.h"
|
||||||
|
#include "src/diagnostic/formatter.h"
|
||||||
#include "src/program.h"
|
#include "src/program.h"
|
||||||
|
|
||||||
namespace tint {
|
namespace tint {
|
||||||
|
@ -191,6 +192,7 @@ int CommonFuzzer::Run(const uint8_t* data, size_t size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!program.IsValid()) {
|
if (!program.IsValid()) {
|
||||||
|
errors_ = diag::Formatter().format(program.Diagnostics());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,58 +201,68 @@ int CommonFuzzer::Run(const uint8_t* data, size_t size) {
|
||||||
|
|
||||||
auto entry_points = inspector.GetEntryPoints();
|
auto entry_points = inspector.GetEntryPoints();
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& ep : entry_points) {
|
for (auto& ep : entry_points) {
|
||||||
auto remapped_name = inspector.GetRemappedNameForEntryPoint(ep.name);
|
auto remapped_name = inspector.GetRemappedNameForEntryPoint(ep.name);
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto constant_ids = inspector.GetConstantIDs();
|
auto constant_ids = inspector.GetConstantIDs();
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto uniform_bindings =
|
auto uniform_bindings =
|
||||||
inspector.GetUniformBufferResourceBindings(ep.name);
|
inspector.GetUniformBufferResourceBindings(ep.name);
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto storage_bindings =
|
auto storage_bindings =
|
||||||
inspector.GetStorageBufferResourceBindings(ep.name);
|
inspector.GetStorageBufferResourceBindings(ep.name);
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto readonly_bindings =
|
auto readonly_bindings =
|
||||||
inspector.GetReadOnlyStorageBufferResourceBindings(ep.name);
|
inspector.GetReadOnlyStorageBufferResourceBindings(ep.name);
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sampler_bindings = inspector.GetSamplerResourceBindings(ep.name);
|
auto sampler_bindings = inspector.GetSamplerResourceBindings(ep.name);
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto comparison_sampler_bindings =
|
auto comparison_sampler_bindings =
|
||||||
inspector.GetComparisonSamplerResourceBindings(ep.name);
|
inspector.GetComparisonSamplerResourceBindings(ep.name);
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sampled_texture_bindings =
|
auto sampled_texture_bindings =
|
||||||
inspector.GetSampledTextureResourceBindings(ep.name);
|
inspector.GetSampledTextureResourceBindings(ep.name);
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto multisampled_texture_bindings =
|
auto multisampled_texture_bindings =
|
||||||
inspector.GetMultisampledTextureResourceBindings(ep.name);
|
inspector.GetMultisampledTextureResourceBindings(ep.name);
|
||||||
if (inspector.has_error()) {
|
if (inspector.has_error()) {
|
||||||
|
errors_ = inspector.error();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,39 +284,44 @@ int CommonFuzzer::Run(const uint8_t* data, size_t size) {
|
||||||
program = std::move(out.program);
|
program = std::move(out.program);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<writer::Writer> writer;
|
|
||||||
|
|
||||||
switch (output_) {
|
switch (output_) {
|
||||||
case OutputFormat::kWGSL:
|
case OutputFormat::kWGSL:
|
||||||
#if TINT_BUILD_WGSL_WRITER
|
#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
|
#endif // TINT_BUILD_WGSL_WRITER
|
||||||
break;
|
break;
|
||||||
case OutputFormat::kSpv:
|
case OutputFormat::kSpv:
|
||||||
#if TINT_BUILD_SPV_WRITER
|
#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
|
#endif // TINT_BUILD_SPV_WRITER
|
||||||
break;
|
break;
|
||||||
case OutputFormat::kHLSL:
|
case OutputFormat::kHLSL:
|
||||||
#if TINT_BUILD_HLSL_WRITER
|
#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
|
#endif // TINT_BUILD_HLSL_WRITER
|
||||||
break;
|
break;
|
||||||
case OutputFormat::kMSL:
|
case OutputFormat::kMSL:
|
||||||
#if TINT_BUILD_MSL_WRITER
|
#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
|
#endif // TINT_BUILD_MSL_WRITER
|
||||||
break;
|
break;
|
||||||
case OutputFormat::kNone:
|
case OutputFormat::kNone:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writer) {
|
if (writer_) {
|
||||||
writer->Generate();
|
if (!writer_->Generate()) {
|
||||||
|
errors_ = writer_->error();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const writer::Writer* CommonFuzzer::GetWriter() const {
|
||||||
|
return writer_.get();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace fuzzers
|
} // namespace fuzzers
|
||||||
} // namespace tint
|
} // namespace tint
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#define FUZZERS_TINT_COMMON_FUZZER_H_
|
#define FUZZERS_TINT_COMMON_FUZZER_H_
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -108,12 +109,20 @@ class CommonFuzzer {
|
||||||
|
|
||||||
int Run(const uint8_t* data, size_t size);
|
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:
|
private:
|
||||||
InputFormat input_;
|
InputFormat input_;
|
||||||
OutputFormat output_;
|
OutputFormat output_;
|
||||||
|
std::unique_ptr<writer::Writer> writer_;
|
||||||
transform::Manager* transform_manager_;
|
transform::Manager* transform_manager_;
|
||||||
transform::DataMap transform_inputs_;
|
transform::DataMap transform_inputs_;
|
||||||
bool inspector_enabled_;
|
bool inspector_enabled_;
|
||||||
|
std::string errors_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fuzzers
|
} // 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) {
|
||||||
|
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
|
|
@ -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_
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -17,6 +17,13 @@ if (${TINT_BUILD_TESTS} AND NOT TARGET gmock)
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/googletest EXCLUDE_FROM_ALL)
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/googletest EXCLUDE_FROM_ALL)
|
||||||
endif()
|
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(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER})
|
||||||
if (NOT IS_DIRECTORY "${SPIRV-Headers_SOURCE_DIR}")
|
if (NOT IS_DIRECTORY "${SPIRV-Headers_SOURCE_DIR}")
|
||||||
set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/spirv-headers CACHE STRING "")
|
set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/spirv-headers CACHE STRING "")
|
||||||
|
|
Loading…
Reference in New Issue