spirv-fuzz: Remove opaque pointer design pattern (#3755)
There's no real need for Fuzzer, Replayer and Shrinker to use the
opaque pointer design pattern. This change removes it, paving the way
for making some upcoming changes to Fuzzer easier.
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index e35fc8b..4cf3ba3 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -16,7 +16,6 @@
#include <cassert>
#include <memory>
-#include <sstream>
#include "source/fuzz/fact_manager/fact_manager.h"
#include "source/fuzz/fuzzer_context.h"
@@ -112,51 +111,32 @@
} // namespace
-struct Fuzzer::Impl {
- Impl(spv_target_env env, uint32_t random_seed, bool validate_after_each_pass,
- spv_validator_options options)
- : target_env(env),
- seed(random_seed),
- validate_after_each_fuzzer_pass(validate_after_each_pass),
- validator_options(options) {}
-
- bool ApplyPassAndCheckValidity(FuzzerPass* pass,
- const opt::IRContext& ir_context,
- const spvtools::SpirvTools& tools) const;
-
- const spv_target_env target_env; // Target environment.
- MessageConsumer consumer; // Message consumer.
- const uint32_t seed; // Seed for random number generator.
- bool validate_after_each_fuzzer_pass; // Determines whether the validator
- // should be invoked after every fuzzer
- // pass.
- spv_validator_options validator_options; // Options to control validation.
-};
-
-Fuzzer::Fuzzer(spv_target_env env, uint32_t seed,
+Fuzzer::Fuzzer(spv_target_env target_env, uint32_t seed,
bool validate_after_each_fuzzer_pass,
spv_validator_options validator_options)
- : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass,
- validator_options)) {}
+ : target_env_(target_env),
+ seed_(seed),
+ validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass),
+ validator_options_(validator_options) {}
Fuzzer::~Fuzzer() = default;
-void Fuzzer::SetMessageConsumer(MessageConsumer c) {
- impl_->consumer = std::move(c);
+void Fuzzer::SetMessageConsumer(MessageConsumer consumer) {
+ consumer_ = std::move(consumer);
}
-bool Fuzzer::Impl::ApplyPassAndCheckValidity(
+bool Fuzzer::ApplyPassAndCheckValidity(
FuzzerPass* pass, const opt::IRContext& ir_context,
const spvtools::SpirvTools& tools) const {
pass->Apply();
- if (validate_after_each_fuzzer_pass) {
+ if (validate_after_each_fuzzer_pass_) {
std::vector<uint32_t> binary_to_validate;
ir_context.module()->ToBinary(&binary_to_validate, false);
if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
- validator_options)) {
- consumer(SPV_MSG_INFO, nullptr, {},
- "Binary became invalid during fuzzing (set a breakpoint to "
- "inspect); stopping.");
+ validator_options_)) {
+ consumer_(SPV_MSG_INFO, nullptr, {},
+ "Binary became invalid during fuzzing (set a breakpoint to "
+ "inspect); stopping.");
return false;
}
}
@@ -173,29 +153,28 @@
// header files being used.
GOOGLE_PROTOBUF_VERIFY_VERSION;
- spvtools::SpirvTools tools(impl_->target_env);
- tools.SetMessageConsumer(impl_->consumer);
+ spvtools::SpirvTools tools(target_env_);
+ tools.SetMessageConsumer(consumer_);
if (!tools.IsValid()) {
- impl_->consumer(SPV_MSG_ERROR, nullptr, {},
- "Failed to create SPIRV-Tools interface; stopping.");
+ consumer_(SPV_MSG_ERROR, nullptr, {},
+ "Failed to create SPIRV-Tools interface; stopping.");
return Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface;
}
// Initial binary should be valid.
- if (!tools.Validate(&binary_in[0], binary_in.size(),
- impl_->validator_options)) {
- impl_->consumer(SPV_MSG_ERROR, nullptr, {},
- "Initial binary is invalid; stopping.");
+ if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) {
+ consumer_(SPV_MSG_ERROR, nullptr, {},
+ "Initial binary is invalid; stopping.");
return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid;
}
// Build the module from the input binary.
- std::unique_ptr<opt::IRContext> ir_context = BuildModule(
- impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
+ std::unique_ptr<opt::IRContext> ir_context =
+ BuildModule(target_env_, consumer_, binary_in.data(), binary_in.size());
assert(ir_context);
// Make a PRNG from the seed passed to the fuzzer on creation.
- PseudoRandomGenerator random_generator(impl_->seed);
+ PseudoRandomGenerator random_generator(seed_);
// The fuzzer will introduce new ids into the module. The module's id bound
// gives the smallest id that can be used for this purpose. We add an offset
@@ -208,9 +187,9 @@
FuzzerContext fuzzer_context(&random_generator, minimum_fresh_id);
FactManager fact_manager;
- fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
+ fact_manager.AddFacts(consumer_, initial_facts, ir_context.get());
TransformationContext transformation_context(&fact_manager,
- impl_->validator_options);
+ validator_options_);
// Apply some semantics-preserving passes.
std::vector<std::unique_ptr<FuzzerPass>> passes;
@@ -356,7 +335,7 @@
(is_first ||
fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) {
is_first = false;
- if (!impl_->ApplyPassAndCheckValidity(
+ if (!ApplyPassAndCheckValidity(
passes[fuzzer_context.RandomIndex(passes)].get(), *ir_context,
tools)) {
return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
@@ -400,7 +379,7 @@
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
for (auto& pass : final_passes) {
- if (!impl_->ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) {
+ if (!ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) {
return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
}
}
diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h
index 6c3ef71..a437d58 100644
--- a/source/fuzz/fuzzer.h
+++ b/source/fuzz/fuzzer.h
@@ -18,6 +18,7 @@
#include <memory>
#include <vector>
+#include "source/fuzz/fuzzer_pass.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "spirv-tools/libspirv.hpp"
@@ -37,11 +38,11 @@
kInitialBinaryInvalid,
};
- // Constructs a fuzzer from the given target environment |env|. |seed| is a
- // seed for pseudo-random number generation.
+ // Constructs a fuzzer from the given target environment |target_env|. |seed|
+ // is a seed for pseudo-random number generation.
// |validate_after_each_fuzzer_pass| controls whether the validator will be
// invoked after every fuzzer pass is applied.
- Fuzzer(spv_target_env env, uint32_t seed,
+ Fuzzer(spv_target_env target_env, uint32_t seed,
bool validate_after_each_fuzzer_pass,
spv_validator_options validator_options);
@@ -71,8 +72,27 @@
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.
+ // Applies |pass|, which must be a pass constructed with |ir_context|, and
+ // then returns true if and only if |ir_context| is valid. |tools| is used to
+ // check validity.
+ bool ApplyPassAndCheckValidity(FuzzerPass* pass,
+ const opt::IRContext& ir_context,
+ const spvtools::SpirvTools& tools) const;
+
+ // Target environment.
+ const spv_target_env target_env_;
+
+ // Message consumer.
+ MessageConsumer consumer_;
+
+ // Seed for random number generator.
+ const uint32_t seed_;
+
+ // Determines whether the validator should be invoked after every fuzzer pass.
+ bool validate_after_each_fuzzer_pass_;
+
+ // Options to control validation.
+ spv_validator_options validator_options_;
};
} // namespace fuzz
diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp
index 3a49f55..d439b9d 100644
--- a/source/fuzz/replayer.cpp
+++ b/source/fuzz/replayer.cpp
@@ -28,28 +28,16 @@
namespace spvtools {
namespace fuzz {
-struct Replayer::Impl {
- Impl(spv_target_env env, bool validate, spv_validator_options options)
- : target_env(env),
- validate_during_replay(validate),
- validator_options(options) {}
-
- const spv_target_env target_env; // Target environment.
- MessageConsumer consumer; // Message consumer.
- const bool validate_during_replay; // Controls whether the validator should
- // be run after every replay step.
- spv_validator_options validator_options; // Options to control
- // validation
-};
-
-Replayer::Replayer(spv_target_env env, bool validate_during_replay,
+Replayer::Replayer(spv_target_env target_env, bool validate_during_replay,
spv_validator_options validator_options)
- : impl_(MakeUnique<Impl>(env, validate_during_replay, validator_options)) {}
+ : target_env_(target_env),
+ validate_during_replay_(validate_during_replay),
+ validator_options_(validator_options) {}
Replayer::~Replayer() = default;
-void Replayer::SetMessageConsumer(MessageConsumer c) {
- impl_->consumer = std::move(c);
+void Replayer::SetMessageConsumer(MessageConsumer consumer) {
+ consumer_ = std::move(consumer);
}
Replayer::ReplayerResultStatus Replayer::Run(
@@ -65,47 +53,45 @@
if (num_transformations_to_apply >
static_cast<uint32_t>(transformation_sequence_in.transformation_size())) {
- impl_->consumer(SPV_MSG_ERROR, nullptr, {},
- "The number of transformations to be replayed must not "
- "exceed the size of the transformation sequence.");
+ consumer_(SPV_MSG_ERROR, nullptr, {},
+ "The number of transformations to be replayed must not "
+ "exceed the size of the transformation sequence.");
return Replayer::ReplayerResultStatus::kTooManyTransformationsRequested;
}
- spvtools::SpirvTools tools(impl_->target_env);
+ spvtools::SpirvTools tools(target_env_);
if (!tools.IsValid()) {
- impl_->consumer(SPV_MSG_ERROR, nullptr, {},
- "Failed to create SPIRV-Tools interface; stopping.");
+ consumer_(SPV_MSG_ERROR, nullptr, {},
+ "Failed to create SPIRV-Tools interface; stopping.");
return Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface;
}
// Initial binary should be valid.
- if (!tools.Validate(&binary_in[0], binary_in.size(),
- impl_->validator_options)) {
- impl_->consumer(SPV_MSG_INFO, nullptr, {},
- "Initial binary is invalid; stopping.");
+ if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) {
+ consumer_(SPV_MSG_INFO, nullptr, {},
+ "Initial binary is invalid; stopping.");
return Replayer::ReplayerResultStatus::kInitialBinaryInvalid;
}
// Build the module from the input binary.
- std::unique_ptr<opt::IRContext> ir_context = BuildModule(
- impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
+ std::unique_ptr<opt::IRContext> ir_context =
+ BuildModule(target_env_, consumer_, binary_in.data(), binary_in.size());
assert(ir_context);
// For replay validation, we track the last valid SPIR-V binary that was
// observed. Initially this is the input binary.
std::vector<uint32_t> last_valid_binary;
- if (impl_->validate_during_replay) {
+ if (validate_during_replay_) {
last_valid_binary = binary_in;
}
FactManager fact_manager;
- fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
+ fact_manager.AddFacts(consumer_, initial_facts, ir_context.get());
std::unique_ptr<TransformationContext> transformation_context =
first_overflow_id == 0
- ? MakeUnique<TransformationContext>(&fact_manager,
- impl_->validator_options)
+ ? MakeUnique<TransformationContext>(&fact_manager, validator_options_)
: MakeUnique<TransformationContext>(
- &fact_manager, impl_->validator_options,
+ &fact_manager, validator_options_,
MakeUnique<CounterOverflowIdSource>(first_overflow_id));
// We track the largest id bound observed, to ensure that it only increases
@@ -136,16 +122,16 @@
"transformations.");
max_observed_id_bound = ir_context->module()->id_bound();
- if (impl_->validate_during_replay) {
+ if (validate_during_replay_) {
std::vector<uint32_t> binary_to_validate;
ir_context->module()->ToBinary(&binary_to_validate, false);
// Check whether the latest transformation led to a valid binary.
if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
- impl_->validator_options)) {
- impl_->consumer(SPV_MSG_INFO, nullptr, {},
- "Binary became invalid during replay (set a "
- "breakpoint to inspect); stopping.");
+ validator_options_)) {
+ consumer_(SPV_MSG_INFO, nullptr, {},
+ "Binary became invalid during replay (set a "
+ "breakpoint to inspect); stopping.");
return Replayer::ReplayerResultStatus::kReplayValidationFailure;
}
diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h
index 23e39d0..a10e536 100644
--- a/source/fuzz/replayer.h
+++ b/source/fuzz/replayer.h
@@ -38,7 +38,7 @@
};
// Constructs a replayer from the given target environment.
- Replayer(spv_target_env env, bool validate_during_replay,
+ Replayer(spv_target_env target_env, bool validate_during_replay,
spv_validator_options validator_options);
// Disables copy/move constructor/assignment operations.
@@ -76,8 +76,17 @@
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.
+ // Target environment.
+ const spv_target_env target_env_;
+
+ // Message consumer.
+ MessageConsumer consumer_;
+
+ // Controls whether the validator should be run after every replay step.
+ const bool validate_during_replay_;
+
+ // Options to control validation
+ spv_validator_options validator_options_;
};
} // namespace fuzz
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
index dcdf54a..7b88405 100644
--- a/source/fuzz/shrinker.cpp
+++ b/source/fuzz/shrinker.cpp
@@ -61,45 +61,26 @@
} // namespace
-struct Shrinker::Impl {
- Impl(spv_target_env env, uint32_t limit, bool validate,
- spv_validator_options options)
- : target_env(env),
- step_limit(limit),
- validate_during_replay(validate),
- validator_options(options) {}
-
- // Returns the id bound for the given SPIR-V binary, which is assumed to be
- // valid.
- uint32_t GetIdBound(const std::vector<uint32_t>& binary);
-
- const spv_target_env target_env; // Target environment.
- MessageConsumer consumer; // Message consumer.
- const uint32_t step_limit; // Step limit for reductions.
- const bool validate_during_replay; // Determines whether to check for
- // validity during the replaying of
- // transformations.
- spv_validator_options validator_options; // Options to control validation.
-};
-
-uint32_t Shrinker::Impl::GetIdBound(const std::vector<uint32_t>& binary) {
+uint32_t Shrinker::GetIdBound(const std::vector<uint32_t>& binary) const {
// Build the module from the input binary.
std::unique_ptr<opt::IRContext> ir_context =
- BuildModule(target_env, consumer, binary.data(), binary.size());
+ BuildModule(target_env_, consumer_, binary.data(), binary.size());
assert(ir_context && "Error building module.");
return ir_context->module()->id_bound();
}
-Shrinker::Shrinker(spv_target_env env, uint32_t step_limit,
+Shrinker::Shrinker(spv_target_env target_env, uint32_t step_limit,
bool validate_during_replay,
spv_validator_options validator_options)
- : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay,
- validator_options)) {}
+ : target_env_(target_env),
+ step_limit_(step_limit),
+ validate_during_replay_(validate_during_replay),
+ validator_options_(validator_options) {}
Shrinker::~Shrinker() = default;
-void Shrinker::SetMessageConsumer(MessageConsumer c) {
- impl_->consumer = std::move(c);
+void Shrinker::SetMessageConsumer(MessageConsumer consumer) {
+ consumer_ = std::move(consumer);
}
Shrinker::ShrinkerResultStatus Shrinker::Run(
@@ -113,18 +94,17 @@
// header files being used.
GOOGLE_PROTOBUF_VERIFY_VERSION;
- SpirvTools tools(impl_->target_env);
+ SpirvTools tools(target_env_);
if (!tools.IsValid()) {
- impl_->consumer(SPV_MSG_ERROR, nullptr, {},
- "Failed to create SPIRV-Tools interface; stopping.");
+ 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_->validator_options)) {
- impl_->consumer(SPV_MSG_INFO, nullptr, {},
- "Initial binary is invalid; stopping.");
+ if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) {
+ consumer_(SPV_MSG_INFO, nullptr, {},
+ "Initial binary is invalid; stopping.");
return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid;
}
@@ -135,9 +115,8 @@
// 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).
- Replayer replayer(impl_->target_env, impl_->validate_during_replay,
- impl_->validator_options);
- replayer.SetMessageConsumer(impl_->consumer);
+ Replayer replayer(target_env_, validate_during_replay_, validator_options_);
+ replayer.SetMessageConsumer(consumer_);
if (replayer.Run(binary_in, initial_facts, transformation_sequence_in,
static_cast<uint32_t>(
transformation_sequence_in.transformation_size()),
@@ -150,15 +129,15 @@
// 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.");
+ consumer_(SPV_MSG_INFO, nullptr, {},
+ "Initial binary is not interesting; stopping.");
return ShrinkerResultStatus::kInitialBinaryNotInteresting;
}
// The largest id used by the module before any shrinking has been applied
// serves as the first id that can be used for overflow purposes.
- const uint32_t first_overflow_id = impl_->GetIdBound(current_best_binary);
- assert(first_overflow_id >= impl_->GetIdBound(binary_in) &&
+ const uint32_t first_overflow_id = GetIdBound(current_best_binary);
+ assert(first_overflow_id >= GetIdBound(binary_in) &&
"Applying transformations should only increase a module's id bound.");
uint32_t attempt = 0; // Keeps track of the number of shrink attempts that
@@ -174,7 +153,7 @@
// - reach the step limit,
// - run out of transformations to remove, or
// - cannot make the chunk size any smaller.
- while (attempt < impl_->step_limit &&
+ while (attempt < step_limit_ &&
!current_best_transformations.transformation().empty() &&
chunk_size > 0) {
bool progress_this_round =
@@ -204,7 +183,7 @@
// |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--) {
+ attempt < 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 =
@@ -265,12 +244,12 @@
// Indicate whether shrinking completed or was truncated due to reaching the
// step limit.
- assert(attempt <= impl_->step_limit);
- if (attempt == impl_->step_limit) {
+ assert(attempt <= step_limit_);
+ if (attempt == step_limit_) {
std::stringstream strstream;
- strstream << "Shrinking did not complete; step limit " << impl_->step_limit
+ strstream << "Shrinking did not complete; step limit " << step_limit_
<< " was reached.";
- impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
+ consumer_(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
return Shrinker::ShrinkerResultStatus::kStepLimitReached;
}
return Shrinker::ShrinkerResultStatus::kComplete;
diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h
index 17b15bf..0fe8929 100644
--- a/source/fuzz/shrinker.h
+++ b/source/fuzz/shrinker.h
@@ -49,8 +49,8 @@
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, bool validate_during_replay,
+ Shrinker(spv_target_env target_env, uint32_t step_limit,
+ bool validate_during_replay,
spv_validator_options validator_options);
// Disables copy/move constructor/assignment operations.
@@ -82,8 +82,25 @@
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.
+ // Returns the id bound for the given SPIR-V binary, which is assumed to be
+ // valid.
+ uint32_t GetIdBound(const std::vector<uint32_t>& binary) const;
+
+ // Target environment.
+ const spv_target_env target_env_;
+
+ // Message consumer.
+ MessageConsumer consumer_;
+
+ // Step limit to decide when to terminate shrinking early.
+ const uint32_t step_limit_;
+
+ // Determines whether to check for validity during the replaying of
+ // transformations.
+ const bool validate_during_replay_;
+
+ // Options to control validation.
+ spv_validator_options validator_options_;
};
} // namespace fuzz