dawn-cmake/fuzzers/tint_common_fuzzer.cc
Ryan Harrison 6839cba568 Add toggle to control if validity is expected in fuzzer
This toggle controls if the fuzzer will throw a fatal error in the
case that the shader becomes invalid.

Currently the fuzzers do no guarantee that the options that are
provided are correct/valid, so there are many uninteresting cases that
become invalid due to the limited nature of the fuzzers, not due to
bugs in the code. The default off state of this toggle will suppress
this noise.

Once https://bugs.chromium.org/p/tint/issues/detail?id=1356 is
implemented this toggle can be default on.

BUG=tint:1357,chromium:1294533

Change-Id: I7170e5a30691105c97e20d8337aadf81ac2bc3bc
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/79840
Reviewed-by: Ben Clayton <bclayton@google.com>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
2022-02-09 19:45:17 +00:00

354 lines
12 KiB
C++

// 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_common_fuzzer.h"
#include <cassert>
#include <cstring>
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#if TINT_BUILD_SPV_READER
#include "spirv-tools/libspirv.hpp"
#endif // TINT_BUILD_SPV_READER
#include "src/ast/module.h"
#include "src/diagnostic/formatter.h"
#include "src/program.h"
#include "src/utils/hash.h"
namespace tint {
namespace fuzzers {
namespace {
// A macro is used to avoid FATAL_ERROR creating its own stack frame. This leads
// to better de-duplication of bug reports, because ClusterFuzz only uses the
// top few stack frames for de-duplication, and a FATAL_ERROR stack frame
// provides no useful information.
#define FATAL_ERROR(diags, msg_string) \
do { \
std::string msg = msg_string; \
auto printer = tint::diag::Printer::create(stderr, true); \
if (!msg.empty()) { \
printer->write(msg + "\n", {diag::Color::kRed, true}); \
} \
tint::diag::Formatter().format(diags, printer.get()); \
__builtin_trap(); \
} while (false)
[[noreturn]] void TintInternalCompilerErrorReporter(
const tint::diag::List& diagnostics) {
FATAL_ERROR(diagnostics, "");
}
// Wrapping in a macro, so it can be a one-liner in the code, but not
// introduce another level in the stack trace. This will help with de-duping
// ClusterFuzz issues.
#define CHECK_INSPECTOR(program, inspector) \
do { \
if ((inspector).has_error()) { \
if (!enforce_validity) { \
return; \
} \
FATAL_ERROR((program)->Diagnostics(), \
"Inspector failed: " + (inspector).error()); \
} \
} while (false)
// Wrapping in a macro to make code more readable and help with issue de-duping.
#define VALIDITY_ERROR(diags, msg_string) \
do { \
if (!enforce_validity) { \
return 0; \
} \
FATAL_ERROR(diags, msg_string); \
} while (false)
bool SPIRVToolsValidationCheck(const tint::Program& program,
const std::vector<uint32_t>& spirv) {
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
const tint::diag::List& diags = program.Diagnostics();
tools.SetMessageConsumer([diags](spv_message_level_t, const char*,
const spv_position_t& pos, const char* msg) {
std::stringstream out;
out << "Unexpected spirv-val error:\n"
<< (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
<< std::endl;
auto printer = tint::diag::Printer::create(stderr, true);
printer->write(out.str(), {diag::Color::kYellow, false});
tint::diag::Formatter().format(diags, printer.get());
});
return tools.Validate(spirv.data(), spirv.size(),
spvtools::ValidatorOptions());
}
} // namespace
void GenerateSpirvOptions(DataBuilder* b, writer::spirv::Options* options) {
*options = b->build<writer::spirv::Options>();
}
void GenerateWgslOptions(DataBuilder* b, writer::wgsl::Options* options) {
*options = b->build<writer::wgsl::Options>();
}
void GenerateHlslOptions(DataBuilder* b, writer::hlsl::Options* options) {
*options = b->build<writer::hlsl::Options>();
}
void GenerateMslOptions(DataBuilder* b, writer::msl::Options* options) {
*options = b->build<writer::msl::Options>();
}
CommonFuzzer::CommonFuzzer(InputFormat input, OutputFormat output)
: input_(input), output_(output) {}
CommonFuzzer::~CommonFuzzer() = default;
int CommonFuzzer::Run(const uint8_t* data, size_t size) {
tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
#if TINT_BUILD_WGSL_WRITER
tint::Program::printer = [](const tint::Program* program) {
auto result = tint::writer::wgsl::Generate(program, {});
if (!result.error.empty()) {
return "error: " + result.error;
}
return result.wgsl;
};
#endif // TINT_BUILD_WGSL_WRITER
Program program;
#if TINT_BUILD_SPV_READER
std::vector<uint32_t> spirv_input(size / sizeof(uint32_t));
#endif // TINT_BUILD_SPV_READER
#if TINT_BUILD_WGSL_READER || TINT_BUILD_SPV_READER
auto dump_input_data = [&](auto& content, const char* extension) {
size_t hash = utils::Hash(content);
auto filename = "fuzzer_input_" + std::to_string(hash) + extension; //
std::ofstream fout(filename, std::ios::binary);
fout.write(reinterpret_cast<const char*>(data),
static_cast<std::streamsize>(size));
std::cout << "Dumped input data to " << filename << std::endl;
};
#endif
switch (input_) {
#if TINT_BUILD_WGSL_READER
case InputFormat::kWGSL: {
// Clear any existing diagnostics, as these will hold pointers to file_,
// which we are about to release.
diagnostics_ = {};
std::string str(reinterpret_cast<const char*>(data), size);
file_ = std::make_unique<Source::File>("test.wgsl", str);
if (dump_input_) {
dump_input_data(str, ".wgsl");
}
program = reader::wgsl::Parse(file_.get());
break;
}
#endif // TINT_BUILD_WGSL_READER
#if TINT_BUILD_SPV_READER
case InputFormat::kSpv: {
// `spirv_input` has been initialized with the capacity to store `size /
// sizeof(uint32_t)` uint32_t values. If `size` is not a multiple of
// sizeof(uint32_t) then not all of `data` can be copied into
// `spirv_input`, and any trailing bytes are discarded.
std::memcpy(spirv_input.data(), data,
spirv_input.size() * sizeof(uint32_t));
if (spirv_input.empty()) {
return 0;
}
if (dump_input_) {
dump_input_data(spirv_input, ".spv");
}
program = reader::spirv::Parse(spirv_input);
break;
}
#endif // TINT_BUILD_SPV_READER
}
if (!program.IsValid()) {
diagnostics_ = program.Diagnostics();
return 0;
}
#if TINT_BUILD_SPV_READER
if (input_ == InputFormat::kSpv &&
!SPIRVToolsValidationCheck(program, spirv_input)) {
FATAL_ERROR(
program.Diagnostics(),
"Fuzzing detected invalid input spirv not being caught by Tint");
}
#endif // TINT_BUILD_SPV_READER
RunInspector(&program);
diagnostics_ = program.Diagnostics();
if (transform_manager_) {
auto out = transform_manager_->Run(&program, *transform_inputs_);
if (!out.program.IsValid()) {
// Transforms can produce error messages for bad input.
// Catch ICEs and errors from non transform systems.
for (const auto& diag : out.program.Diagnostics()) {
if (diag.severity > diag::Severity::Error ||
diag.system != diag::System::Transform) {
VALIDITY_ERROR(program.Diagnostics(),
"Fuzzing detected valid input program being "
"transformed into an invalid output program");
}
}
}
program = std::move(out.program);
RunInspector(&program);
}
switch (output_) {
case OutputFormat::kWGSL: {
#if TINT_BUILD_WGSL_WRITER
auto result = writer::wgsl::Generate(&program, options_wgsl_);
generated_wgsl_ = std::move(result.wgsl);
if (!result.success) {
VALIDITY_ERROR(
program.Diagnostics(),
"WGSL writer errored on validated input:\n" + result.error);
}
#endif // TINT_BUILD_WGSL_WRITER
break;
}
case OutputFormat::kSpv: {
#if TINT_BUILD_SPV_WRITER
auto result = writer::spirv::Generate(&program, options_spirv_);
generated_spirv_ = std::move(result.spirv);
if (!result.success) {
VALIDITY_ERROR(
program.Diagnostics(),
"SPIR-V writer errored on validated input:\n" + result.error);
}
if (!SPIRVToolsValidationCheck(program, generated_spirv_)) {
VALIDITY_ERROR(program.Diagnostics(),
"Fuzzing detected invalid spirv being emitted by Tint");
}
#endif // TINT_BUILD_SPV_WRITER
break;
}
case OutputFormat::kHLSL: {
#if TINT_BUILD_HLSL_WRITER
auto result = writer::hlsl::Generate(&program, options_hlsl_);
generated_hlsl_ = std::move(result.hlsl);
if (!result.success) {
VALIDITY_ERROR(
program.Diagnostics(),
"HLSL writer errored on validated input:\n" + result.error);
}
#endif // TINT_BUILD_HLSL_WRITER
break;
}
case OutputFormat::kMSL: {
#if TINT_BUILD_MSL_WRITER
auto result = writer::msl::Generate(&program, options_msl_);
generated_msl_ = std::move(result.msl);
if (!result.success) {
VALIDITY_ERROR(
program.Diagnostics(),
"MSL writer errored on validated input:\n" + result.error);
}
#endif // TINT_BUILD_MSL_WRITER
break;
}
}
return 0;
}
void CommonFuzzer::RunInspector(Program* program) {
inspector::Inspector inspector(program);
diagnostics_ = program->Diagnostics();
auto entry_points = inspector.GetEntryPoints();
CHECK_INSPECTOR(program, inspector);
auto constant_ids = inspector.GetConstantIDs();
CHECK_INSPECTOR(program, inspector);
auto constant_name_to_id = inspector.GetConstantNameToIdMap();
CHECK_INSPECTOR(program, inspector);
for (auto& ep : entry_points) {
inspector.GetRemappedNameForEntryPoint(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetStorageSize(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetUniformBufferResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetStorageBufferResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetReadOnlyStorageBufferResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetSamplerResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetComparisonSamplerResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetSampledTextureResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetMultisampledTextureResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetWriteOnlyStorageTextureResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetDepthTextureResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetDepthMultisampledTextureResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetExternalTextureResourceBindings(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetSamplerTextureUses(ep.name);
CHECK_INSPECTOR(program, inspector);
inspector.GetWorkgroupStorageSize(ep.name);
CHECK_INSPECTOR(program, inspector);
}
}
} // namespace fuzzers
} // namespace tint