Shrinker for spirv-fuzz (#2708)

Adds to spirv-fuzz the option to shrink a sequence of transformations
that lead to an interesting binary to be generated, to find a smaller
sub-sequence of transformations that still lead to an interesting (but
hopefully simpler) binary being generated. The notion of what counts
as "interesting" comes from a user-provided script, the
"interestingness function", similar to the way the spirv-reduce tool
works. The shrinking process will give up after a maximum number of
steps, which can be configured on the command line.

Tests for the combination of fuzzing and shrinking are included, using
a variety of interestingness functions.
This commit is contained in:
Alastair Donaldson 2019-07-07 08:55:30 +01:00 committed by GitHub
parent 37e8f79946
commit b8ab80843f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1651 additions and 30 deletions

View File

@ -607,6 +607,11 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsDestroy(spv_fuzzer_options options);
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetRandomSeed(
spv_fuzzer_options options, uint32_t seed);
// Sets the maximum number of steps that the shrinker should take before giving
// up.
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit(
spv_fuzzer_options options, uint32_t shrinker_step_limit);
// Encodes the given SPIR-V assembly text to its binary representation. The
// length parameter specifies the number of bytes for text. Encoded binary will
// be stored into *binary. Any error will be written into *diagnostic if

View File

@ -207,6 +207,11 @@ class FuzzerOptions {
spvFuzzerOptionsSetRandomSeed(options_, seed);
}
// See spvFuzzerOptionsSetShrinkerStepLimit.
void set_shrinker_step_limit(uint32_t shrinker_step_limit) {
spvFuzzerOptionsSetShrinkerStepLimit(options_, shrinker_step_limit);
}
private:
spv_fuzzer_options options_;
};

View File

@ -41,6 +41,7 @@ if(SPIRV_BUILD_FUZZER)
pseudo_random_generator.h
random_generator.h
replayer.h
shrinker.h
transformation.h
transformation_add_constant_boolean.h
transformation_add_constant_scalar.h
@ -70,6 +71,7 @@ if(SPIRV_BUILD_FUZZER)
pseudo_random_generator.cpp
random_generator.cpp
replayer.cpp
shrinker.cpp
transformation.cpp
transformation_add_constant_boolean.cpp
transformation_add_constant_scalar.cpp

View File

@ -55,14 +55,14 @@ void Fuzzer::SetMessageConsumer(MessageConsumer c) {
Fuzzer::FuzzerResultStatus Fuzzer::Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out,
spv_const_fuzzer_options options) const {
spv_const_fuzzer_options options, std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const {
// Check compatibility between the library version being linked with and the
// header files being used.
GOOGLE_PROTOBUF_VERIFY_VERSION;
spvtools::SpirvTools tools(impl_->target_env);
tools.SetMessageConsumer(impl_->consumer);
if (!tools.IsValid()) {
impl_->consumer(SPV_MSG_ERROR, nullptr, {},
"Failed to create SPIRV-Tools interface; stopping.");

View File

@ -58,9 +58,8 @@ class Fuzzer {
FuzzerResultStatus Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out,
spv_const_fuzzer_options options) const;
spv_const_fuzzer_options options, std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const;
private:
struct Impl; // Opaque struct for holding internal data.

240
source/fuzz/shrinker.cpp Normal file
View File

@ -0,0 +1,240 @@
// Copyright (c) 2019 Google LLC
//
// 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 "source/fuzz/shrinker.h"
#include <sstream>
#include "source/fuzz/pseudo_random_generator.h"
#include "source/fuzz/replayer.h"
#include "source/spirv_fuzzer_options.h"
#include "source/util/make_unique.h"
namespace spvtools {
namespace fuzz {
namespace {
// A helper to get the size of a protobuf transformation sequence in a less
// verbose manner.
uint32_t NumRemainingTransformations(
const protobufs::TransformationSequence& transformation_sequence) {
return static_cast<uint32_t>(transformation_sequence.transformation_size());
}
// A helper to return a transformation sequence identical to |transformations|,
// except that a chunk of size |chunk_size| starting from |chunk_index| x
// |chunk_size| is removed (or as many transformations as available if the whole
// chunk is not).
protobufs::TransformationSequence RemoveChunk(
const protobufs::TransformationSequence& transformations,
uint32_t chunk_index, uint32_t chunk_size) {
uint32_t lower = chunk_index * chunk_size;
uint32_t upper = std::min((chunk_index + 1) * chunk_size,
NumRemainingTransformations(transformations));
assert(lower < upper);
assert(upper <= NumRemainingTransformations(transformations));
protobufs::TransformationSequence result;
for (uint32_t j = 0; j < NumRemainingTransformations(transformations); j++) {
if (j >= lower && j < upper) {
continue;
}
protobufs::Transformation transformation =
transformations.transformation()[j];
*result.mutable_transformation()->Add() = transformation;
}
return result;
}
} // namespace
struct Shrinker::Impl {
explicit Impl(spv_target_env env, uint32_t limit)
: target_env(env), step_limit(limit) {}
const spv_target_env target_env; // Target environment.
MessageConsumer consumer; // Message consumer.
const uint32_t step_limit; // Step limit for reductions.
};
Shrinker::Shrinker(spv_target_env env, uint32_t step_limit)
: impl_(MakeUnique<Impl>(env, step_limit)) {}
Shrinker::~Shrinker() = default;
void Shrinker::SetMessageConsumer(MessageConsumer c) {
impl_->consumer = std::move(c);
}
Shrinker::ShrinkerResultStatus Shrinker::Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const protobufs::TransformationSequence& transformation_sequence_in,
const Shrinker::InterestingnessFunction& interestingness_function,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const {
// Check compatibility between the library version being linked with and the
// header files being used.
GOOGLE_PROTOBUF_VERIFY_VERSION;
spvtools::SpirvTools tools(impl_->target_env);
if (!tools.IsValid()) {
impl_->consumer(SPV_MSG_ERROR, nullptr, {},
"Failed to create SPIRV-Tools interface; stopping.");
return Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface;
}
// Initial binary should be valid.
if (!tools.Validate(&binary_in[0], binary_in.size())) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Initial binary is invalid; stopping.");
return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid;
}
std::vector<uint32_t> current_best_binary;
protobufs::TransformationSequence current_best_transformations;
// Run a replay of the initial transformation sequence to (a) check that it
// succeeds, (b) get the binary that results from running these
// transformations, and (c) get the subsequence of the initial transformations
// that actually apply (in principle this could be a strict subsequence).
if (Replayer(impl_->target_env)
.Run(binary_in, initial_facts, transformation_sequence_in,
&current_best_binary, &current_best_transformations) !=
Replayer::ReplayerResultStatus::kComplete) {
return ShrinkerResultStatus::kReplayFailed;
}
// Check that the binary produced by applying the initial transformations is
// indeed interesting.
if (!interestingness_function(current_best_binary, 0)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Initial binary is not interesting; stopping.");
return ShrinkerResultStatus::kInitialBinaryNotInteresting;
}
uint32_t attempt = 0; // Keeps track of the number of shrink attempts that
// have been tried, whether successful or not.
uint32_t chunk_size =
std::max(1u, NumRemainingTransformations(current_best_transformations) /
2); // The number of contiguous transformations that the
// shrinker will try to remove in one go; starts
// high and decreases during the shrinking process.
// Keep shrinking until we:
// - reach the step limit,
// - run out of transformations to remove, or
// - cannot make the chunk size any smaller.
while (attempt < impl_->step_limit &&
!current_best_transformations.transformation().empty() &&
chunk_size > 0) {
bool progress_this_round =
false; // Used to decide whether to make the chunk size with which we
// remove transformations smaller. If we managed to remove at
// least one chunk of transformations at a particular chunk
// size, we set this flag so that we do not yet decrease the
// chunk size.
assert(chunk_size <=
NumRemainingTransformations(current_best_transformations) &&
"Chunk size should never exceed the number of transformations that "
"remain.");
// The number of chunks is the ceiling of (#remaining_transformations /
// chunk_size).
const uint32_t num_chunks =
(NumRemainingTransformations(current_best_transformations) +
chunk_size - 1) /
chunk_size;
assert(num_chunks >= 1 && "There should be at least one chunk.");
assert(num_chunks * chunk_size >=
NumRemainingTransformations(current_best_transformations) &&
"All transformations should be in some chunk.");
// We go through the transformations in reverse, in chunks of size
// |chunk_size|, using |chunk_index| to track which chunk to try removing
// next. The loop exits early if we reach the shrinking step limit.
for (int chunk_index = num_chunks - 1;
attempt < impl_->step_limit && chunk_index >= 0; chunk_index--) {
// Remove a chunk of transformations according to the current index and
// chunk size.
auto transformations_with_chunk_removed =
RemoveChunk(current_best_transformations, chunk_index, chunk_size);
// Replay the smaller sequence of transformations to get a next binary and
// transformation sequence. Note that the transformations arising from
// replay might be even smaller than the transformations with the chunk
// removed, because removing those transformations might make further
// transformations inapplicable.
std::vector<uint32_t> next_binary;
protobufs::TransformationSequence next_transformation_sequence;
if (Replayer(impl_->target_env)
.Run(binary_in, initial_facts, transformations_with_chunk_removed,
&next_binary, &next_transformation_sequence) !=
Replayer::ReplayerResultStatus::kComplete) {
// Replay should not fail; if it does, we need to abort shrinking.
return ShrinkerResultStatus::kReplayFailed;
}
assert(NumRemainingTransformations(next_transformation_sequence) >=
chunk_index * chunk_size &&
"Removing this chunk of transformations should not have an effect "
"on earlier chunks.");
if (interestingness_function(next_binary, attempt)) {
// If the binary arising from the smaller transformation sequence is
// interesting, this becomes our current best binary and transformation
// sequence.
current_best_binary = next_binary;
current_best_transformations = next_transformation_sequence;
progress_this_round = true;
}
// Either way, this was a shrink attempt, so increment our count of shrink
// attempts.
attempt++;
}
if (!progress_this_round) {
// If we didn't manage to remove any chunks at this chunk size, try a
// smaller chunk size.
chunk_size /= 2;
}
// Decrease the chunk size until it becomes no larger than the number of
// remaining transformations.
while (chunk_size >
NumRemainingTransformations(current_best_transformations)) {
chunk_size /= 2;
}
}
// The output from the shrinker is the best binary we saw, and the
// transformations that led to it.
*binary_out = current_best_binary;
*transformation_sequence_out = current_best_transformations;
// Indicate whether shrinking completed or was truncated due to reaching the
// step limit.
assert(attempt <= impl_->step_limit);
if (attempt == impl_->step_limit) {
std::stringstream strstream;
strstream << "Shrinking did not complete; step limit " << impl_->step_limit
<< " was reached.";
impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
return Shrinker::ShrinkerResultStatus::kStepLimitReached;
}
return Shrinker::ShrinkerResultStatus::kComplete;
}
} // namespace fuzz
} // namespace spvtools

91
source/fuzz/shrinker.h Normal file
View File

@ -0,0 +1,91 @@
// Copyright (c) 2019 Google LLC
//
// 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 SOURCE_FUZZ_SHRINKER_H_
#define SOURCE_FUZZ_SHRINKER_H_
#include <memory>
#include <vector>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "spirv-tools/libspirv.hpp"
namespace spvtools {
namespace fuzz {
// Shrinks a sequence of transformations that lead to an interesting SPIR-V
// binary to yield a smaller sequence of transformations that still produce an
// interesting binary.
class Shrinker {
public:
// Possible statuses that can result from running the shrinker.
enum ShrinkerResultStatus {
kComplete,
kFailedToCreateSpirvToolsInterface,
kInitialBinaryInvalid,
kInitialBinaryNotInteresting,
kReplayFailed,
kStepLimitReached,
};
// The type for a function that will take a binary, |binary|, and return true
// if and only if the binary is deemed interesting. (The function also takes
// an integer argument, |counter|, that will be incremented each time the
// function is called; this is for debugging purposes).
//
// The notion of "interesting" depends on what properties of the binary or
// tools that process the binary we are trying to maintain during shrinking.
using InterestingnessFunction = std::function<bool(
const std::vector<uint32_t>& binary, uint32_t counter)>;
// Constructs a shrinker from the given target environment.
Shrinker(spv_target_env env, uint32_t step_limit);
// Disables copy/move constructor/assignment operations.
Shrinker(const Shrinker&) = delete;
Shrinker(Shrinker&&) = delete;
Shrinker& operator=(const Shrinker&) = delete;
Shrinker& operator=(Shrinker&&) = delete;
~Shrinker();
// Sets the message consumer to the given |consumer|. The |consumer| will be
// invoked once for each message communicated from the library.
void SetMessageConsumer(MessageConsumer consumer);
// Requires that when |transformation_sequence_in| is applied to |binary_in|
// with initial facts |initial_facts|, the resulting binary is interesting
// according to |interestingness_function|.
//
// Produces, via |transformation_sequence_out|, a subsequence of
// |transformation_sequence_in| that, when applied with initial facts
// |initial_facts|, produces a binary (captured via |binary_out|) that is
// also interesting according to |interestingness_function|.
ShrinkerResultStatus Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const protobufs::TransformationSequence& transformation_sequence_in,
const InterestingnessFunction& interestingness_function,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const;
private:
struct Impl; // Opaque struct for holding internal data.
std::unique_ptr<Impl> impl_; // Unique pointer to internal data.
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_SHRINKER_H_

View File

@ -14,7 +14,15 @@
#include "source/spirv_fuzzer_options.h"
spv_fuzzer_options_t::spv_fuzzer_options_t() = default;
namespace {
// The default maximum number of steps for the reducer to run before giving up.
const uint32_t kDefaultStepLimit = 250;
} // namespace
spv_fuzzer_options_t::spv_fuzzer_options_t()
: has_random_seed(false),
random_seed(0),
shrinker_step_limit(kDefaultStepLimit) {}
SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() {
return new spv_fuzzer_options_t();
@ -29,3 +37,8 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetRandomSeed(
options->has_random_seed = true;
options->random_seed = seed;
}
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit(
spv_fuzzer_options options, uint32_t shrinker_step_limit) {
options->shrinker_step_limit = shrinker_step_limit;
}

View File

@ -26,8 +26,11 @@ struct spv_fuzzer_options_t {
spv_fuzzer_options_t();
// See spvFuzzerOptionsSetRandomSeed.
bool has_random_seed = false;
uint32_t random_seed = 0;
bool has_random_seed;
uint32_t random_seed;
// See spvFuzzerOptionsSetShrinkerStepLimit.
uint32_t shrinker_step_limit;
};
#endif // SOURCE_SPIRV_FUZZER_OPTIONS_H_

View File

@ -18,6 +18,7 @@ if (${SPIRV_BUILD_FUZZER})
fuzz_test_util.h
fuzzer_replayer_test.cpp
fuzzer_shrinker_test.cpp
fact_manager_test.cpp
fuzz_test_util.cpp
fuzzer_pass_add_useful_constructs_test.cpp

View File

@ -59,6 +59,11 @@ const uint32_t kFuzzAssembleOption =
const uint32_t kFuzzDisassembleOption =
SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT;
// A silent message consumer.
const spvtools::MessageConsumer kSilentConsumer =
[](spv_message_level_t, const char*, const spv_position_t&,
const char*) -> void {};
} // namespace fuzz
} // namespace spvtools

View File

@ -43,9 +43,10 @@ void RunFuzzerAndReplayer(const std::string& shader,
spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed);
Fuzzer fuzzer(env);
fuzzer.SetMessageConsumer(kSilentConsumer);
auto fuzzer_result_status =
fuzzer.Run(binary_in, initial_facts, &fuzzer_binary_out,
&fuzzer_transformation_sequence_out, fuzzer_options);
fuzzer.Run(binary_in, initial_facts, fuzzer_options, &fuzzer_binary_out,
&fuzzer_transformation_sequence_out);
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
@ -53,6 +54,7 @@ void RunFuzzerAndReplayer(const std::string& shader,
protobufs::TransformationSequence replayer_transformation_sequence_out;
Replayer replayer(env);
replayer.SetMessageConsumer(kSilentConsumer);
auto replayer_result_status = replayer.Run(
binary_in, initial_facts, fuzzer_transformation_sequence_out,
&replayer_binary_out, &replayer_transformation_sequence_out);

File diff suppressed because it is too large Load Diff

View File

@ -17,11 +17,13 @@
#include <cstring>
#include <fstream>
#include <functional>
#include <sstream>
#include <string>
#include "source/fuzz/fuzzer.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/replayer.h"
#include "source/fuzz/shrinker.h"
#include "source/opt/build_module.h"
#include "source/opt/ir_context.h"
#include "source/opt/log.h"
@ -32,10 +34,30 @@
namespace {
// Check that the std::system function can actually be used.
bool CheckExecuteCommand() {
int res = std::system(nullptr);
return res != 0;
}
// Execute a command using the shell.
// Returns true if and only if the command's exit status was 0.
bool ExecuteCommand(const std::string& command) {
errno = 0;
int status = std::system(command.c_str());
assert(errno == 0 && "failed to execute command");
// The result returned by 'system' is implementation-defined, but is
// usually the case that the returned value is 0 when the command's exit
// code was 0. We are assuming that here, and that's all we depend on.
return status == 0;
}
// Status and actions to perform after parsing command-line arguments.
enum class FuzzActions {
FUZZ, // Run the fuzzer to apply transformations in a randomized fashion.
REPLAY, // Replay an existing sequence of transformations.
SHRINK, // Shrink an existing sequence of transformations with respect to an
// interestingness function.
STOP // Do nothing.
};
@ -71,6 +93,17 @@ Options (in lexicographical order):
--seed
Unsigned 32-bit integer seed to control random number
generation.
--shrink
File from which to read a sequence of transformations to shrink
(instead of fuzzing)
--shrinker-step-limit
Unsigned 32-bit integer specifying maximum number of steps the
shrinker will take before giving up. Ignored unless --shrink
is used.
--interestingness
Path to an interestingness function to guide shrinking: a script
that returns 0 if and only if a given binary is interesting.
Required if --shrink is provided; disallowed otherwise.
--version
Display fuzzer version information.
@ -99,6 +132,8 @@ bool EndsWithSpv(const std::string& filename) {
FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
std::string* out_binary_file,
std::string* replay_transformations_file,
std::string* interestingness_function_file,
std::string* shrink_transformations_file,
spvtools::FuzzerOptions* fuzzer_options) {
uint32_t positional_arg_index = 0;
@ -122,6 +157,13 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
} else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
*replay_transformations_file = std::string(split_flag.second);
} else if (0 == strncmp(cur_arg, "--interestingness=",
sizeof("--interestingness=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
*interestingness_function_file = std::string(split_flag.second);
} else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
*shrink_transformations_file = std::string(split_flag.second);
} else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
char* end = nullptr;
@ -130,6 +172,15 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
assert(end != split_flag.second.c_str() && errno == 0);
fuzzer_options->set_random_seed(seed);
} else if (0 == strncmp(cur_arg, "--shrinker-step-limit=",
sizeof("--shrinker-step-limit=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
char* end = nullptr;
errno = 0;
const auto step_limit =
static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
assert(end != split_flag.second.c_str() && errno == 0);
fuzzer_options->set_shrinker_step_limit(step_limit);
} else if ('\0' == cur_arg[1]) {
// We do not support fuzzing from standard input. We could support
// this if there was a compelling use case.
@ -173,12 +224,59 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
if (!replay_transformations_file->empty()) {
// A replay transformations file was given, thus the tool is being invoked
// in replay mode.
if (!shrink_transformations_file->empty()) {
spvtools::Error(
FuzzDiagnostic, nullptr, {},
"The --replay and --shrink arguments are mutually exclusive.");
return {FuzzActions::STOP, 1};
}
if (!interestingness_function_file->empty()) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"The --replay and --interestingness arguments are "
"mutually exclusive.");
return {FuzzActions::STOP, 1};
}
return {FuzzActions::REPLAY, 0};
}
if (!shrink_transformations_file->empty() ^
!interestingness_function_file->empty()) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Both or neither of the --shrink and --interestingness "
"arguments must be provided.");
return {FuzzActions::STOP, 1};
}
if (!shrink_transformations_file->empty()) {
// The tool is being invoked in shrink mode.
assert(!interestingness_function_file->empty() &&
"An error should have been raised if --shrink but not --interesting "
"was provided.");
return {FuzzActions::SHRINK, 0};
}
return {FuzzActions::FUZZ, 0};
}
bool ParseTransformations(
const std::string& transformations_file,
spvtools::fuzz::protobufs::TransformationSequence* transformations) {
std::ifstream transformations_stream;
transformations_stream.open(transformations_file,
std::ios::in | std::ios::binary);
auto parse_success =
transformations->ParseFromIstream(&transformations_stream);
transformations_stream.close();
if (!parse_success) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
("Error reading transformations from file '" +
transformations_file + "'")
.c_str());
return false;
}
return true;
}
bool Replay(const spv_target_env& target_env,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
@ -186,29 +284,61 @@ bool Replay(const spv_target_env& target_env,
std::vector<uint32_t>* binary_out,
spvtools::fuzz::protobufs::TransformationSequence*
transformations_applied) {
std::ifstream existing_transformations_file;
existing_transformations_file.open(replay_transformations_file,
std::ios::in | std::ios::binary);
spvtools::fuzz::protobufs::TransformationSequence
existing_transformation_sequence;
auto parse_success = existing_transformation_sequence.ParseFromIstream(
&existing_transformations_file);
existing_transformations_file.close();
if (!parse_success) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Error reading transformations for replay");
spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
if (!ParseTransformations(replay_transformations_file,
&transformation_sequence)) {
return false;
}
spvtools::fuzz::Replayer replayer(target_env);
replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
auto replay_result_status =
replayer.Run(binary_in, initial_facts, existing_transformation_sequence,
replayer.Run(binary_in, initial_facts, transformation_sequence,
binary_out, transformations_applied);
if (replay_result_status !=
spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete) {
return !(replay_result_status !=
spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
}
bool Shrink(const spv_target_env& target_env,
spv_const_fuzzer_options fuzzer_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
const std::string& shrink_transformations_file,
const std::string& interestingness_function_file,
std::vector<uint32_t>* binary_out,
spvtools::fuzz::protobufs::TransformationSequence*
transformations_applied) {
spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
if (!ParseTransformations(shrink_transformations_file,
&transformation_sequence)) {
return false;
}
return true;
spvtools::fuzz::Shrinker shrinker(target_env,
fuzzer_options->shrinker_step_limit);
shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function =
[interestingness_function_file](std::vector<uint32_t> binary,
uint32_t reductions_applied) -> bool {
std::stringstream ss;
ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
<< ".spv";
const auto spv_file = ss.str();
const std::string command =
std::string(interestingness_function_file) + " " + spv_file;
auto write_file_succeeded =
WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
(void)(write_file_succeeded);
assert(write_file_succeeded);
return ExecuteCommand(command);
};
auto shrink_result_status = shrinker.Run(
binary_in, initial_facts, transformation_sequence,
interestingness_function, binary_out, transformations_applied);
return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete ==
shrink_result_status ||
spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached ==
shrink_result_status;
}
bool Fuzz(const spv_target_env& target_env,
@ -220,8 +350,8 @@ bool Fuzz(const spv_target_env& target_env,
transformations_applied) {
spvtools::fuzz::Fuzzer fuzzer(target_env);
fuzzer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
auto fuzz_result_status = fuzzer.Run(binary_in, initial_facts, binary_out,
transformations_applied, fuzzer_options);
auto fuzz_result_status = fuzzer.Run(binary_in, initial_facts, fuzzer_options,
binary_out, transformations_applied);
if (fuzz_result_status !=
spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
@ -238,11 +368,15 @@ int main(int argc, const char** argv) {
std::string in_binary_file;
std::string out_binary_file;
std::string replay_transformations_file;
std::string interestingness_function_file;
std::string shrink_transformations_file;
spvtools::FuzzerOptions fuzzer_options;
FuzzStatus status = ParseFlags(argc, argv, &in_binary_file, &out_binary_file,
&replay_transformations_file, &fuzzer_options);
FuzzStatus status =
ParseFlags(argc, argv, &in_binary_file, &out_binary_file,
&replay_transformations_file, &interestingness_function_file,
&shrink_transformations_file, &fuzzer_options);
if (status.action == FuzzActions::STOP) {
return status.code;
@ -290,6 +424,18 @@ int main(int argc, const char** argv) {
return 1;
}
break;
case FuzzActions::SHRINK: {
if (!CheckExecuteCommand()) {
std::cerr << "could not find shell interpreter for executing a command"
<< std::endl;
return 1;
}
if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
shrink_transformations_file, interestingness_function_file,
&binary_out, &transformations_applied)) {
return 1;
}
} break;
default:
assert(false && "Unknown fuzzer action.");
break;