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.
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index 82717e9..acd641e 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -607,6 +607,11 @@
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
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index b0fd4a2..a1db488 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -207,6 +207,11 @@
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_;
};
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 2e065b7..75e2125 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -41,6 +41,7 @@
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 @@
pseudo_random_generator.cpp
random_generator.cpp
replayer.cpp
+ shrinker.cpp
transformation.cpp
transformation_add_constant_boolean.cpp
transformation_add_constant_scalar.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 606ccd6..0fa645a 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -55,14 +55,14 @@
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.");
diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h
index af45d9b..a257c2a 100644
--- a/source/fuzz/fuzzer.h
+++ b/source/fuzz/fuzzer.h
@@ -58,9 +58,8 @@
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.
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
new file mode 100644
index 0000000..f8d8aa3
--- /dev/null
+++ b/source/fuzz/shrinker.cpp
@@ -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,
+ ¤t_best_binary, ¤t_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
diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h
new file mode 100644
index 0000000..72dd470
--- /dev/null
+++ b/source/fuzz/shrinker.h
@@ -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_
diff --git a/source/spirv_fuzzer_options.cpp b/source/spirv_fuzzer_options.cpp
index f6319ca..9a2cb9f 100644
--- a/source/spirv_fuzzer_options.cpp
+++ b/source/spirv_fuzzer_options.cpp
@@ -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 @@
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;
+}
diff --git a/source/spirv_fuzzer_options.h b/source/spirv_fuzzer_options.h
index 1193bfd..3b8e15c 100644
--- a/source/spirv_fuzzer_options.h
+++ b/source/spirv_fuzzer_options.h
@@ -26,8 +26,11 @@
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_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 7b47b55..9e4066b 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -18,6 +18,7 @@
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
diff --git a/test/fuzz/fuzz_test_util.h b/test/fuzz/fuzz_test_util.h
index 202622c..76b7fc2 100644
--- a/test/fuzz/fuzz_test_util.h
+++ b/test/fuzz/fuzz_test_util.h
@@ -59,6 +59,11 @@
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
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index 8b3775b..2cb595a 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -43,9 +43,10 @@
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 @@
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);
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
new file mode 100644
index 0000000..644ace4
--- /dev/null
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -0,0 +1,1109 @@
+// 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 <functional>
+#include <vector>
+
+#include "source/fuzz/fuzzer.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/shrinker.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+// Abstract class exposing an interestingness function as a virtual method.
+class InterestingnessTest {
+ public:
+ virtual ~InterestingnessTest() = default;
+
+ // Abstract method that subclasses should implement for specific notions of
+ // interestingness. Its signature matches Shrinker::InterestingnessFunction.
+ // Argument |binary| is the SPIR-V binary to be checked; |counter| is used for
+ // debugging purposes.
+ virtual bool Interesting(const std::vector<uint32_t>& binary,
+ uint32_t counter) = 0;
+
+ // Yields the Interesting instance method wrapped in a function object.
+ Shrinker::InterestingnessFunction AsFunction() {
+ return std::bind(&InterestingnessTest::Interesting, this,
+ std::placeholders::_1, std::placeholders::_2);
+ }
+};
+
+// A test that says all binaries are interesting.
+class AlwaysInteresting : public InterestingnessTest {
+ public:
+ bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
+ return true;
+ }
+};
+
+// A test that says a binary is interesting first time round, and uninteresting
+// thereafter.
+class OnlyInterestingFirstTime : public InterestingnessTest {
+ public:
+ explicit OnlyInterestingFirstTime() : first_time_(true) {}
+
+ bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
+ if (first_time_) {
+ first_time_ = false;
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ bool first_time_;
+};
+
+// A test that says a binary is interesting first time round, after which
+// interestingness ping pongs between false and true.
+class PingPong : public InterestingnessTest {
+ public:
+ explicit PingPong() : interesting_(false) {}
+
+ bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
+ interesting_ = !interesting_;
+ return interesting_;
+ }
+
+ private:
+ bool interesting_;
+};
+
+// A test that says a binary is interesting first time round, thereafter
+// decides at random whether it is interesting. This allows the logic of the
+// shrinker to be exercised quite a bit.
+class InterestingThenRandom : public InterestingnessTest {
+ public:
+ InterestingThenRandom(const PseudoRandomGenerator& random_generator)
+ : first_time_(true), random_generator_(random_generator) {}
+
+ bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
+ if (first_time_) {
+ first_time_ = false;
+ return true;
+ }
+ return random_generator_.RandomBool();
+ }
+
+ private:
+ bool first_time_;
+ PseudoRandomGenerator random_generator_;
+};
+
+// |binary_in| and |initial_facts| are a SPIR-V binary and sequence of facts to
+// which |transformation_sequence_in| can be applied. Shrinking of
+// |transformation_sequence_in| gets performed with respect to
+// |interestingness_function|. If |expected_binary_out| is non-empty, it must
+// match the binary obtained by applying the final shrunk set of
+// transformations, in which case the number of such transforations should equal
+// |expected_transformations_out_size|.
+void RunAndCheckShrinker(
+ const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
+ const protobufs::FactSequence& initial_facts,
+ const protobufs::TransformationSequence& transformation_sequence_in,
+ const Shrinker::InterestingnessFunction& interestingness_function,
+ const std::vector<uint32_t>& expected_binary_out,
+ uint32_t expected_transformations_out_size) {
+ // We want tests to complete in reasonable time, so don't go on too long.
+ const uint32_t kStepLimit = 50;
+
+ // Run the shrinker.
+ Shrinker shrinker(target_env, kStepLimit);
+ shrinker.SetMessageConsumer(kSilentConsumer);
+
+ std::vector<uint32_t> binary_out;
+ protobufs::TransformationSequence transformations_out;
+ Shrinker::ShrinkerResultStatus shrinker_result_status =
+ shrinker.Run(binary_in, initial_facts, transformation_sequence_in,
+ interestingness_function, &binary_out, &transformations_out);
+ ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete ==
+ shrinker_result_status ||
+ Shrinker::ShrinkerResultStatus::kStepLimitReached ==
+ shrinker_result_status);
+
+ // If a non-empty expected binary was provided, check that it matches the
+ // result of shrinking and that the expected number of transformations remain.
+ if (!expected_binary_out.empty()) {
+ ASSERT_EQ(expected_binary_out, binary_out);
+ ASSERT_EQ(expected_transformations_out_size,
+ static_cast<uint32_t>(transformations_out.transformation_size()));
+ }
+}
+
+// Assembles the given |shader| text, and then does the following |num_runs|
+// times, with successive seeds starting from |initial_seed|:
+// - Runs the fuzzer with the seed to yield a set of transformations
+// - Shrinks the transformation with various interestingness functions,
+// asserting some properties about the result each time
+void RunFuzzerAndShrinker(const std::string& shader,
+ const protobufs::FactSequence& initial_facts,
+ uint32_t initial_seed, uint32_t num_runs) {
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+
+ std::vector<uint32_t> binary_in;
+ SpirvTools t(env);
+ ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
+ ASSERT_TRUE(t.Validate(binary_in));
+
+ for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
+ // Run the fuzzer and check that it successfully yields a valid binary.
+ std::vector<uint32_t> fuzzer_binary_out;
+ protobufs::TransformationSequence fuzzer_transformation_sequence_out;
+ spvtools::FuzzerOptions fuzzer_options;
+ spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed);
+ Fuzzer fuzzer(env);
+ fuzzer.SetMessageConsumer(kSilentConsumer);
+ auto fuzzer_result_status =
+ 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));
+
+ // With the AlwaysInteresting test, we should quickly shrink to the original
+ // binary with no transformations remaining.
+ RunAndCheckShrinker(env, binary_in, initial_facts,
+ fuzzer_transformation_sequence_out,
+ AlwaysInteresting().AsFunction(), binary_in, 0);
+
+ // With the OnlyInterestingFirstTime test, no shrinking should be achieved.
+ RunAndCheckShrinker(
+ env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
+ OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
+ static_cast<uint32_t>(
+ fuzzer_transformation_sequence_out.transformation_size()));
+
+ // The PingPong test is unpredictable; passing an empty expected binary
+ // means that we don't check anything beyond that shrinking completes
+ // successfully.
+ RunAndCheckShrinker(env, binary_in, initial_facts,
+ fuzzer_transformation_sequence_out,
+ PingPong().AsFunction(), {}, 0);
+
+ // The InterestingThenRandom test is unpredictable; passing an empty
+ // expected binary means that we do not check anything about shrinking
+ // results.
+ RunAndCheckShrinker(
+ env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
+ InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0);
+ }
+}
+
+TEST(FuzzerShrinkerTest, Miscellaneous1) {
+ // The following SPIR-V came from this GLSL:
+ //
+ // #version 310 es
+ //
+ // void foo() {
+ // int x;
+ // x = 2;
+ // for (int i = 0; i < 100; i++) {
+ // x += i;
+ // x = x * 2;
+ // }
+ // return;
+ // }
+ //
+ // void main() {
+ // foo();
+ // for (int i = 0; i < 10; i++) {
+ // int j = 20;
+ // while(j > 0) {
+ // foo();
+ // j--;
+ // }
+ // do {
+ // i++;
+ // } while(i < 4);
+ // }
+ // }
+
+ const std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "foo("
+ OpName %10 "x"
+ OpName %12 "i"
+ OpName %33 "i"
+ OpName %42 "j"
+ OpDecorate %10 RelaxedPrecision
+ OpDecorate %12 RelaxedPrecision
+ OpDecorate %19 RelaxedPrecision
+ OpDecorate %23 RelaxedPrecision
+ OpDecorate %24 RelaxedPrecision
+ OpDecorate %25 RelaxedPrecision
+ OpDecorate %26 RelaxedPrecision
+ OpDecorate %27 RelaxedPrecision
+ OpDecorate %28 RelaxedPrecision
+ OpDecorate %30 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %49 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %53 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %59 RelaxedPrecision
+ OpDecorate %60 RelaxedPrecision
+ OpDecorate %63 RelaxedPrecision
+ OpDecorate %64 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %8 2
+ %13 = OpConstant %8 0
+ %20 = OpConstant %8 100
+ %21 = OpTypeBool
+ %29 = OpConstant %8 1
+ %40 = OpConstant %8 10
+ %43 = OpConstant %8 20
+ %61 = OpConstant %8 4
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %33 = OpVariable %9 Function
+ %42 = OpVariable %9 Function
+ %32 = OpFunctionCall %2 %6
+ OpStore %33 %13
+ OpBranch %34
+ %34 = OpLabel
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %39 = OpLoad %8 %33
+ %41 = OpSLessThan %21 %39 %40
+ OpBranchConditional %41 %35 %36
+ %35 = OpLabel
+ OpStore %42 %43
+ OpBranch %44
+ %44 = OpLabel
+ OpLoopMerge %46 %47 None
+ OpBranch %48
+ %48 = OpLabel
+ %49 = OpLoad %8 %42
+ %50 = OpSGreaterThan %21 %49 %13
+ OpBranchConditional %50 %45 %46
+ %45 = OpLabel
+ %51 = OpFunctionCall %2 %6
+ %52 = OpLoad %8 %42
+ %53 = OpISub %8 %52 %29
+ OpStore %42 %53
+ OpBranch %47
+ %47 = OpLabel
+ OpBranch %44
+ %46 = OpLabel
+ OpBranch %54
+ %54 = OpLabel
+ OpLoopMerge %56 %57 None
+ OpBranch %55
+ %55 = OpLabel
+ %58 = OpLoad %8 %33
+ %59 = OpIAdd %8 %58 %29
+ OpStore %33 %59
+ OpBranch %57
+ %57 = OpLabel
+ %60 = OpLoad %8 %33
+ %62 = OpSLessThan %21 %60 %61
+ OpBranchConditional %62 %54 %56
+ %56 = OpLabel
+ OpBranch %37
+ %37 = OpLabel
+ %63 = OpLoad %8 %33
+ %64 = OpIAdd %8 %63 %29
+ OpStore %33 %64
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %10 = OpVariable %9 Function
+ %12 = OpVariable %9 Function
+ OpStore %10 %11
+ OpStore %12 %13
+ OpBranch %14
+ %14 = OpLabel
+ OpLoopMerge %16 %17 None
+ OpBranch %18
+ %18 = OpLabel
+ %19 = OpLoad %8 %12
+ %22 = OpSLessThan %21 %19 %20
+ OpBranchConditional %22 %15 %16
+ %15 = OpLabel
+ %23 = OpLoad %8 %12
+ %24 = OpLoad %8 %10
+ %25 = OpIAdd %8 %24 %23
+ OpStore %10 %25
+ %26 = OpLoad %8 %10
+ %27 = OpIMul %8 %26 %11
+ OpStore %10 %27
+ OpBranch %17
+ %17 = OpLabel
+ %28 = OpLoad %8 %12
+ %30 = OpIAdd %8 %28 %29
+ OpStore %12 %30
+ OpBranch %14
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ )";
+
+ // Do 2 fuzz-and-shrink runs, starting from an initial seed of 2 (seed value
+ // chosen arbitrarily).
+ RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2, 2);
+}
+
+TEST(FuzzerShrinkerTest, Miscellaneous2) {
+ // The following SPIR-V came from this GLSL, which was then optimized using
+ // spirv-opt with the -O argument:
+ //
+ // #version 310 es
+ //
+ // precision highp float;
+ //
+ // layout(location = 0) out vec4 _GLF_color;
+ //
+ // layout(set = 0, binding = 0) uniform buf0 {
+ // vec2 injectionSwitch;
+ // };
+ // layout(set = 0, binding = 1) uniform buf1 {
+ // vec2 resolution;
+ // };
+ // bool checkSwap(float a, float b)
+ // {
+ // return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
+ // }
+ // void main()
+ // {
+ // float data[10];
+ // for(int i = 0; i < 10; i++)
+ // {
+ // data[i] = float(10 - i) * injectionSwitch.y;
+ // }
+ // for(int i = 0; i < 9; i++)
+ // {
+ // for(int j = 0; j < 10; j++)
+ // {
+ // if(j < i + 1)
+ // {
+ // continue;
+ // }
+ // bool doSwap = checkSwap(data[i], data[j]);
+ // if(doSwap)
+ // {
+ // float temp = data[i];
+ // data[i] = data[j];
+ // data[j] = temp;
+ // }
+ // }
+ // }
+ // if(gl_FragCoord.x < resolution.x / 2.0)
+ // {
+ // _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
+ // }
+ // else
+ // {
+ // _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
+ // }
+ // }
+
+ const std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %16 %139
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %16 "gl_FragCoord"
+ OpName %23 "buf1"
+ OpMemberName %23 0 "resolution"
+ OpName %25 ""
+ OpName %61 "data"
+ OpName %66 "buf0"
+ OpMemberName %66 0 "injectionSwitch"
+ OpName %68 ""
+ OpName %139 "_GLF_color"
+ OpDecorate %16 BuiltIn FragCoord
+ OpMemberDecorate %23 0 Offset 0
+ OpDecorate %23 Block
+ OpDecorate %25 DescriptorSet 0
+ OpDecorate %25 Binding 1
+ OpDecorate %64 RelaxedPrecision
+ OpMemberDecorate %66 0 Offset 0
+ OpDecorate %66 Block
+ OpDecorate %68 DescriptorSet 0
+ OpDecorate %68 Binding 0
+ OpDecorate %75 RelaxedPrecision
+ OpDecorate %95 RelaxedPrecision
+ OpDecorate %126 RelaxedPrecision
+ OpDecorate %128 RelaxedPrecision
+ OpDecorate %139 Location 0
+ OpDecorate %182 RelaxedPrecision
+ OpDecorate %183 RelaxedPrecision
+ OpDecorate %184 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeBool
+ %14 = OpTypeVector %6 4
+ %15 = OpTypePointer Input %14
+ %16 = OpVariable %15 Input
+ %17 = OpTypeInt 32 0
+ %18 = OpConstant %17 1
+ %19 = OpTypePointer Input %6
+ %22 = OpTypeVector %6 2
+ %23 = OpTypeStruct %22
+ %24 = OpTypePointer Uniform %23
+ %25 = OpVariable %24 Uniform
+ %26 = OpTypeInt 32 1
+ %27 = OpConstant %26 0
+ %28 = OpTypePointer Uniform %6
+ %56 = OpConstant %26 10
+ %58 = OpConstant %17 10
+ %59 = OpTypeArray %6 %58
+ %60 = OpTypePointer Function %59
+ %66 = OpTypeStruct %22
+ %67 = OpTypePointer Uniform %66
+ %68 = OpVariable %67 Uniform
+ %74 = OpConstant %26 1
+ %83 = OpConstant %26 9
+ %129 = OpConstant %17 0
+ %138 = OpTypePointer Output %14
+ %139 = OpVariable %138 Output
+ %144 = OpConstant %26 5
+ %151 = OpConstant %6 1
+ %194 = OpConstant %6 0.5
+ %195 = OpConstant %6 0.100000001
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %61 = OpVariable %60 Function
+ OpBranch %50
+ %50 = OpLabel
+ %182 = OpPhi %26 %27 %5 %75 %51
+ %57 = OpSLessThan %8 %182 %56
+ OpLoopMerge %52 %51 None
+ OpBranchConditional %57 %51 %52
+ %51 = OpLabel
+ %64 = OpISub %26 %56 %182
+ %65 = OpConvertSToF %6 %64
+ %69 = OpAccessChain %28 %68 %27 %18
+ %70 = OpLoad %6 %69
+ %71 = OpFMul %6 %65 %70
+ %72 = OpAccessChain %7 %61 %182
+ OpStore %72 %71
+ %75 = OpIAdd %26 %182 %74
+ OpBranch %50
+ %52 = OpLabel
+ OpBranch %77
+ %77 = OpLabel
+ %183 = OpPhi %26 %27 %52 %128 %88
+ %84 = OpSLessThan %8 %183 %83
+ OpLoopMerge %79 %88 None
+ OpBranchConditional %84 %78 %79
+ %78 = OpLabel
+ OpBranch %86
+ %86 = OpLabel
+ %184 = OpPhi %26 %27 %78 %126 %89
+ %92 = OpSLessThan %8 %184 %56
+ OpLoopMerge %88 %89 None
+ OpBranchConditional %92 %87 %88
+ %87 = OpLabel
+ %95 = OpIAdd %26 %183 %74
+ %96 = OpSLessThan %8 %184 %95
+ OpSelectionMerge %98 None
+ OpBranchConditional %96 %97 %98
+ %97 = OpLabel
+ OpBranch %89
+ %98 = OpLabel
+ %104 = OpAccessChain %7 %61 %183
+ %105 = OpLoad %6 %104
+ %107 = OpAccessChain %7 %61 %184
+ %108 = OpLoad %6 %107
+ %166 = OpAccessChain %19 %16 %18
+ %167 = OpLoad %6 %166
+ %168 = OpAccessChain %28 %25 %27 %18
+ %169 = OpLoad %6 %168
+ %170 = OpFMul %6 %169 %194
+ %171 = OpFOrdLessThan %8 %167 %170
+ OpSelectionMerge %172 None
+ OpBranchConditional %171 %173 %174
+ %173 = OpLabel
+ %177 = OpFOrdGreaterThan %8 %105 %108
+ OpBranch %172
+ %174 = OpLabel
+ %180 = OpFOrdLessThan %8 %105 %108
+ OpBranch %172
+ %172 = OpLabel
+ %186 = OpPhi %8 %177 %173 %180 %174
+ OpSelectionMerge %112 None
+ OpBranchConditional %186 %111 %112
+ %111 = OpLabel
+ %116 = OpLoad %6 %104
+ %120 = OpLoad %6 %107
+ OpStore %104 %120
+ OpStore %107 %116
+ OpBranch %112
+ %112 = OpLabel
+ OpBranch %89
+ %89 = OpLabel
+ %126 = OpIAdd %26 %184 %74
+ OpBranch %86
+ %88 = OpLabel
+ %128 = OpIAdd %26 %183 %74
+ OpBranch %77
+ %79 = OpLabel
+ %130 = OpAccessChain %19 %16 %129
+ %131 = OpLoad %6 %130
+ %132 = OpAccessChain %28 %25 %27 %129
+ %133 = OpLoad %6 %132
+ %134 = OpFMul %6 %133 %194
+ %135 = OpFOrdLessThan %8 %131 %134
+ OpSelectionMerge %137 None
+ OpBranchConditional %135 %136 %153
+ %136 = OpLabel
+ %140 = OpAccessChain %7 %61 %27
+ %141 = OpLoad %6 %140
+ %143 = OpFMul %6 %141 %195
+ %145 = OpAccessChain %7 %61 %144
+ %146 = OpLoad %6 %145
+ %147 = OpFMul %6 %146 %195
+ %148 = OpAccessChain %7 %61 %83
+ %149 = OpLoad %6 %148
+ %150 = OpFMul %6 %149 %195
+ %152 = OpCompositeConstruct %14 %143 %147 %150 %151
+ OpStore %139 %152
+ OpBranch %137
+ %153 = OpLabel
+ %154 = OpAccessChain %7 %61 %144
+ %155 = OpLoad %6 %154
+ %156 = OpFMul %6 %155 %195
+ %157 = OpAccessChain %7 %61 %83
+ %158 = OpLoad %6 %157
+ %159 = OpFMul %6 %158 %195
+ %160 = OpAccessChain %7 %61 %27
+ %161 = OpLoad %6 %160
+ %162 = OpFMul %6 %161 %195
+ %163 = OpCompositeConstruct %14 %156 %159 %162 %151
+ OpStore %139 %163
+ OpBranch %137
+ %137 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ // Do 2 fuzzer runs, starting from an initial seed of 19 (seed value chosen
+ // arbitrarily).
+ RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19, 2);
+}
+
+TEST(FuzzerShrinkerTest, Miscellaneous3) {
+ // The following SPIR-V came from this GLSL, which was then optimized using
+ // spirv-opt with the -O argument:
+ //
+ // #version 310 es
+ //
+ // precision highp float;
+ //
+ // layout(location = 0) out vec4 _GLF_color;
+ //
+ // layout(set = 0, binding = 0) uniform buf0 {
+ // vec2 resolution;
+ // };
+ // void main(void)
+ // {
+ // float A[50];
+ // for(
+ // int i = 0;
+ // i < 200;
+ // i ++
+ // )
+ // {
+ // if(i >= int(resolution.x))
+ // {
+ // break;
+ // }
+ // if((4 * (i / 4)) == i)
+ // {
+ // A[i / 4] = float(i);
+ // }
+ // }
+ // for(
+ // int i = 0;
+ // i < 50;
+ // i ++
+ // )
+ // {
+ // if(i < int(gl_FragCoord.x))
+ // {
+ // break;
+ // }
+ // if(i > 0)
+ // {
+ // A[i] += A[i - 1];
+ // }
+ // }
+ // if(int(gl_FragCoord.x) < 20)
+ // {
+ // _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 40)
+ // {
+ // _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 60)
+ // {
+ // _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
+ // 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 80)
+ // {
+ // _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
+ // 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 100)
+ // {
+ // _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
+ // 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 120)
+ // {
+ // _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
+ // 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 140)
+ // {
+ // _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
+ // 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 160)
+ // {
+ // _GLF_color = vec4(A[35] / resolution.x, A[39] /
+ // resolution.y, 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 180)
+ // {
+ // _GLF_color = vec4(A[40] / resolution.x, A[44] /
+ // resolution.y, 1.0, 1.0);
+ // }
+ // else
+ // if(int(gl_FragCoord.x) < 180)
+ // {
+ // _GLF_color = vec4(A[45] / resolution.x, A[49] /
+ // resolution.y, 1.0, 1.0);
+ // }
+ // else
+ // {
+ // discard;
+ // }
+ // }
+
+ const std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %68 %100
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %22 "buf0"
+ OpMemberName %22 0 "resolution"
+ OpName %24 ""
+ OpName %46 "A"
+ OpName %68 "gl_FragCoord"
+ OpName %100 "_GLF_color"
+ OpMemberDecorate %22 0 Offset 0
+ OpDecorate %22 Block
+ OpDecorate %24 DescriptorSet 0
+ OpDecorate %24 Binding 0
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %38 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %68 BuiltIn FragCoord
+ OpDecorate %83 RelaxedPrecision
+ OpDecorate %91 RelaxedPrecision
+ OpDecorate %100 Location 0
+ OpDecorate %302 RelaxedPrecision
+ OpDecorate %304 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 200
+ %17 = OpTypeBool
+ %20 = OpTypeFloat 32
+ %21 = OpTypeVector %20 2
+ %22 = OpTypeStruct %21
+ %23 = OpTypePointer Uniform %22
+ %24 = OpVariable %23 Uniform
+ %25 = OpTypeInt 32 0
+ %26 = OpConstant %25 0
+ %27 = OpTypePointer Uniform %20
+ %35 = OpConstant %6 4
+ %43 = OpConstant %25 50
+ %44 = OpTypeArray %20 %43
+ %45 = OpTypePointer Function %44
+ %51 = OpTypePointer Function %20
+ %54 = OpConstant %6 1
+ %63 = OpConstant %6 50
+ %66 = OpTypeVector %20 4
+ %67 = OpTypePointer Input %66
+ %68 = OpVariable %67 Input
+ %69 = OpTypePointer Input %20
+ %95 = OpConstant %6 20
+ %99 = OpTypePointer Output %66
+ %100 = OpVariable %99 Output
+ %108 = OpConstant %25 1
+ %112 = OpConstant %20 1
+ %118 = OpConstant %6 40
+ %122 = OpConstant %6 5
+ %128 = OpConstant %6 9
+ %139 = OpConstant %6 60
+ %143 = OpConstant %6 10
+ %149 = OpConstant %6 14
+ %160 = OpConstant %6 80
+ %164 = OpConstant %6 15
+ %170 = OpConstant %6 19
+ %181 = OpConstant %6 100
+ %190 = OpConstant %6 24
+ %201 = OpConstant %6 120
+ %205 = OpConstant %6 25
+ %211 = OpConstant %6 29
+ %222 = OpConstant %6 140
+ %226 = OpConstant %6 30
+ %232 = OpConstant %6 34
+ %243 = OpConstant %6 160
+ %247 = OpConstant %6 35
+ %253 = OpConstant %6 39
+ %264 = OpConstant %6 180
+ %273 = OpConstant %6 44
+ %287 = OpConstant %6 45
+ %293 = OpConstant %6 49
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %46 = OpVariable %45 Function
+ OpBranch %10
+ %10 = OpLabel
+ %302 = OpPhi %6 %9 %5 %55 %42
+ %18 = OpSLessThan %17 %302 %16
+ OpLoopMerge %12 %42 None
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %28 = OpAccessChain %27 %24 %9 %26
+ %29 = OpLoad %20 %28
+ %30 = OpConvertFToS %6 %29
+ %31 = OpSGreaterThanEqual %17 %302 %30
+ OpSelectionMerge %33 None
+ OpBranchConditional %31 %32 %33
+ %32 = OpLabel
+ OpBranch %12
+ %33 = OpLabel
+ %37 = OpSDiv %6 %302 %35
+ %38 = OpIMul %6 %35 %37
+ %40 = OpIEqual %17 %38 %302
+ OpSelectionMerge %42 None
+ OpBranchConditional %40 %41 %42
+ %41 = OpLabel
+ %50 = OpConvertSToF %20 %302
+ %52 = OpAccessChain %51 %46 %37
+ OpStore %52 %50
+ OpBranch %42
+ %42 = OpLabel
+ %55 = OpIAdd %6 %302 %54
+ OpBranch %10
+ %12 = OpLabel
+ OpBranch %57
+ %57 = OpLabel
+ %304 = OpPhi %6 %9 %12 %91 %80
+ %64 = OpSLessThan %17 %304 %63
+ OpLoopMerge %59 %80 None
+ OpBranchConditional %64 %58 %59
+ %58 = OpLabel
+ %70 = OpAccessChain %69 %68 %26
+ %71 = OpLoad %20 %70
+ %72 = OpConvertFToS %6 %71
+ %73 = OpSLessThan %17 %304 %72
+ OpSelectionMerge %75 None
+ OpBranchConditional %73 %74 %75
+ %74 = OpLabel
+ OpBranch %59
+ %75 = OpLabel
+ %78 = OpSGreaterThan %17 %304 %9
+ OpSelectionMerge %80 None
+ OpBranchConditional %78 %79 %80
+ %79 = OpLabel
+ %83 = OpISub %6 %304 %54
+ %84 = OpAccessChain %51 %46 %83
+ %85 = OpLoad %20 %84
+ %86 = OpAccessChain %51 %46 %304
+ %87 = OpLoad %20 %86
+ %88 = OpFAdd %20 %87 %85
+ OpStore %86 %88
+ OpBranch %80
+ %80 = OpLabel
+ %91 = OpIAdd %6 %304 %54
+ OpBranch %57
+ %59 = OpLabel
+ %92 = OpAccessChain %69 %68 %26
+ %93 = OpLoad %20 %92
+ %94 = OpConvertFToS %6 %93
+ %96 = OpSLessThan %17 %94 %95
+ OpSelectionMerge %98 None
+ OpBranchConditional %96 %97 %114
+ %97 = OpLabel
+ %101 = OpAccessChain %51 %46 %9
+ %102 = OpLoad %20 %101
+ %103 = OpAccessChain %27 %24 %9 %26
+ %104 = OpLoad %20 %103
+ %105 = OpFDiv %20 %102 %104
+ %106 = OpAccessChain %51 %46 %35
+ %107 = OpLoad %20 %106
+ %109 = OpAccessChain %27 %24 %9 %108
+ %110 = OpLoad %20 %109
+ %111 = OpFDiv %20 %107 %110
+ %113 = OpCompositeConstruct %66 %105 %111 %112 %112
+ OpStore %100 %113
+ OpBranch %98
+ %114 = OpLabel
+ %119 = OpSLessThan %17 %94 %118
+ OpSelectionMerge %121 None
+ OpBranchConditional %119 %120 %135
+ %120 = OpLabel
+ %123 = OpAccessChain %51 %46 %122
+ %124 = OpLoad %20 %123
+ %125 = OpAccessChain %27 %24 %9 %26
+ %126 = OpLoad %20 %125
+ %127 = OpFDiv %20 %124 %126
+ %129 = OpAccessChain %51 %46 %128
+ %130 = OpLoad %20 %129
+ %131 = OpAccessChain %27 %24 %9 %108
+ %132 = OpLoad %20 %131
+ %133 = OpFDiv %20 %130 %132
+ %134 = OpCompositeConstruct %66 %127 %133 %112 %112
+ OpStore %100 %134
+ OpBranch %121
+ %135 = OpLabel
+ %140 = OpSLessThan %17 %94 %139
+ OpSelectionMerge %142 None
+ OpBranchConditional %140 %141 %156
+ %141 = OpLabel
+ %144 = OpAccessChain %51 %46 %143
+ %145 = OpLoad %20 %144
+ %146 = OpAccessChain %27 %24 %9 %26
+ %147 = OpLoad %20 %146
+ %148 = OpFDiv %20 %145 %147
+ %150 = OpAccessChain %51 %46 %149
+ %151 = OpLoad %20 %150
+ %152 = OpAccessChain %27 %24 %9 %108
+ %153 = OpLoad %20 %152
+ %154 = OpFDiv %20 %151 %153
+ %155 = OpCompositeConstruct %66 %148 %154 %112 %112
+ OpStore %100 %155
+ OpBranch %142
+ %156 = OpLabel
+ %161 = OpSLessThan %17 %94 %160
+ OpSelectionMerge %163 None
+ OpBranchConditional %161 %162 %177
+ %162 = OpLabel
+ %165 = OpAccessChain %51 %46 %164
+ %166 = OpLoad %20 %165
+ %167 = OpAccessChain %27 %24 %9 %26
+ %168 = OpLoad %20 %167
+ %169 = OpFDiv %20 %166 %168
+ %171 = OpAccessChain %51 %46 %170
+ %172 = OpLoad %20 %171
+ %173 = OpAccessChain %27 %24 %9 %108
+ %174 = OpLoad %20 %173
+ %175 = OpFDiv %20 %172 %174
+ %176 = OpCompositeConstruct %66 %169 %175 %112 %112
+ OpStore %100 %176
+ OpBranch %163
+ %177 = OpLabel
+ %182 = OpSLessThan %17 %94 %181
+ OpSelectionMerge %184 None
+ OpBranchConditional %182 %183 %197
+ %183 = OpLabel
+ %185 = OpAccessChain %51 %46 %95
+ %186 = OpLoad %20 %185
+ %187 = OpAccessChain %27 %24 %9 %26
+ %188 = OpLoad %20 %187
+ %189 = OpFDiv %20 %186 %188
+ %191 = OpAccessChain %51 %46 %190
+ %192 = OpLoad %20 %191
+ %193 = OpAccessChain %27 %24 %9 %108
+ %194 = OpLoad %20 %193
+ %195 = OpFDiv %20 %192 %194
+ %196 = OpCompositeConstruct %66 %189 %195 %112 %112
+ OpStore %100 %196
+ OpBranch %184
+ %197 = OpLabel
+ %202 = OpSLessThan %17 %94 %201
+ OpSelectionMerge %204 None
+ OpBranchConditional %202 %203 %218
+ %203 = OpLabel
+ %206 = OpAccessChain %51 %46 %205
+ %207 = OpLoad %20 %206
+ %208 = OpAccessChain %27 %24 %9 %26
+ %209 = OpLoad %20 %208
+ %210 = OpFDiv %20 %207 %209
+ %212 = OpAccessChain %51 %46 %211
+ %213 = OpLoad %20 %212
+ %214 = OpAccessChain %27 %24 %9 %108
+ %215 = OpLoad %20 %214
+ %216 = OpFDiv %20 %213 %215
+ %217 = OpCompositeConstruct %66 %210 %216 %112 %112
+ OpStore %100 %217
+ OpBranch %204
+ %218 = OpLabel
+ %223 = OpSLessThan %17 %94 %222
+ OpSelectionMerge %225 None
+ OpBranchConditional %223 %224 %239
+ %224 = OpLabel
+ %227 = OpAccessChain %51 %46 %226
+ %228 = OpLoad %20 %227
+ %229 = OpAccessChain %27 %24 %9 %26
+ %230 = OpLoad %20 %229
+ %231 = OpFDiv %20 %228 %230
+ %233 = OpAccessChain %51 %46 %232
+ %234 = OpLoad %20 %233
+ %235 = OpAccessChain %27 %24 %9 %108
+ %236 = OpLoad %20 %235
+ %237 = OpFDiv %20 %234 %236
+ %238 = OpCompositeConstruct %66 %231 %237 %112 %112
+ OpStore %100 %238
+ OpBranch %225
+ %239 = OpLabel
+ %244 = OpSLessThan %17 %94 %243
+ OpSelectionMerge %246 None
+ OpBranchConditional %244 %245 %260
+ %245 = OpLabel
+ %248 = OpAccessChain %51 %46 %247
+ %249 = OpLoad %20 %248
+ %250 = OpAccessChain %27 %24 %9 %26
+ %251 = OpLoad %20 %250
+ %252 = OpFDiv %20 %249 %251
+ %254 = OpAccessChain %51 %46 %253
+ %255 = OpLoad %20 %254
+ %256 = OpAccessChain %27 %24 %9 %108
+ %257 = OpLoad %20 %256
+ %258 = OpFDiv %20 %255 %257
+ %259 = OpCompositeConstruct %66 %252 %258 %112 %112
+ OpStore %100 %259
+ OpBranch %246
+ %260 = OpLabel
+ %265 = OpSLessThan %17 %94 %264
+ OpSelectionMerge %267 None
+ OpBranchConditional %265 %266 %280
+ %266 = OpLabel
+ %268 = OpAccessChain %51 %46 %118
+ %269 = OpLoad %20 %268
+ %270 = OpAccessChain %27 %24 %9 %26
+ %271 = OpLoad %20 %270
+ %272 = OpFDiv %20 %269 %271
+ %274 = OpAccessChain %51 %46 %273
+ %275 = OpLoad %20 %274
+ %276 = OpAccessChain %27 %24 %9 %108
+ %277 = OpLoad %20 %276
+ %278 = OpFDiv %20 %275 %277
+ %279 = OpCompositeConstruct %66 %272 %278 %112 %112
+ OpStore %100 %279
+ OpBranch %267
+ %280 = OpLabel
+ OpSelectionMerge %285 None
+ OpBranchConditional %265 %285 %300
+ %285 = OpLabel
+ %288 = OpAccessChain %51 %46 %287
+ %289 = OpLoad %20 %288
+ %290 = OpAccessChain %27 %24 %9 %26
+ %291 = OpLoad %20 %290
+ %292 = OpFDiv %20 %289 %291
+ %294 = OpAccessChain %51 %46 %293
+ %295 = OpLoad %20 %294
+ %296 = OpAccessChain %27 %24 %9 %108
+ %297 = OpLoad %20 %296
+ %298 = OpFDiv %20 %295 %297
+ %299 = OpCompositeConstruct %66 %292 %298 %112 %112
+ OpStore %100 %299
+ OpBranch %267
+ %300 = OpLabel
+ OpKill
+ %267 = OpLabel
+ OpBranch %246
+ %246 = OpLabel
+ OpBranch %225
+ %225 = OpLabel
+ OpBranch %204
+ %204 = OpLabel
+ OpBranch %184
+ %184 = OpLabel
+ OpBranch %163
+ %163 = OpLabel
+ OpBranch %142
+ %142 = OpLabel
+ OpBranch %121
+ %121 = OpLabel
+ OpBranch %98
+ %98 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ // Add the facts "resolution.x == 250" and "resolution.y == 100".
+ protobufs::FactSequence facts;
+ {
+ protobufs::FactConstantUniform resolution_x_eq_250;
+ *resolution_x_eq_250.mutable_uniform_buffer_element_descriptor() =
+ MakeUniformBufferElementDescriptor(0, 0, {0, 0});
+ *resolution_x_eq_250.mutable_constant_word()->Add() = 250;
+ protobufs::Fact temp;
+ *temp.mutable_constant_uniform_fact() = resolution_x_eq_250;
+ *facts.mutable_fact()->Add() = temp;
+ }
+ {
+ protobufs::FactConstantUniform resolution_y_eq_100;
+ *resolution_y_eq_100.mutable_uniform_buffer_element_descriptor() =
+ MakeUniformBufferElementDescriptor(0, 0, {0, 1});
+ *resolution_y_eq_100.mutable_constant_word()->Add() = 100;
+ protobufs::Fact temp;
+ *temp.mutable_constant_uniform_fact() = resolution_y_eq_100;
+ *facts.mutable_fact()->Add() = temp;
+ }
+
+ // Do 2 fuzzer runs, starting from an initial seed of 194 (seed value chosen
+ // arbitrarily).
+ RunFuzzerAndShrinker(shader, facts, 194, 2);
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 8967b4a..481942f 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -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 @@
--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 @@
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 @@
} 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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;