spirv-fuzz: Add validator options (#3254)
Allows several validator options to be passed to the fuzzer, to be
used when validation is invoked during fuzzing.
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 119bd3c..3a4fa0e 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -86,26 +86,31 @@
} // namespace
struct Fuzzer::Impl {
- explicit Impl(spv_target_env env, uint32_t random_seed,
- bool validate_after_each_pass)
+ 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) {}
+ 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.
- MessageConsumer consumer; // Message consumer.
+ // 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,
- bool validate_after_each_fuzzer_pass)
- : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass)) {}
+ bool validate_after_each_fuzzer_pass,
+ spv_validator_options validator_options)
+ : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass,
+ validator_options)) {}
Fuzzer::~Fuzzer() = default;
@@ -120,7 +125,8 @@
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())) {
+ 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.");
@@ -149,7 +155,8 @@
}
// Initial binary should be valid.
- if (!tools.Validate(&binary_in[0], binary_in.size())) {
+ if (!tools.Validate(&binary_in[0], binary_in.size(),
+ impl_->validator_options)) {
impl_->consumer(SPV_MSG_ERROR, nullptr, {},
"Initial binary is invalid; stopping.");
return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid;
diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h
index 3ac73a1..6c3ef71 100644
--- a/source/fuzz/fuzzer.h
+++ b/source/fuzz/fuzzer.h
@@ -41,8 +41,9 @@
// seed for pseudo-random number generation.
// |validate_after_each_fuzzer_pass| controls whether the validator will be
// invoked after every fuzzer pass is applied.
- explicit Fuzzer(spv_target_env env, uint32_t seed,
- bool validate_after_each_fuzzer_pass);
+ Fuzzer(spv_target_env env, uint32_t seed,
+ bool validate_after_each_fuzzer_pass,
+ spv_validator_options validator_options);
// Disables copy/move constructor/assignment operations.
Fuzzer(const Fuzzer&) = delete;
diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp
index 398ce59..07dfe72 100644
--- a/source/fuzz/replayer.cpp
+++ b/source/fuzz/replayer.cpp
@@ -37,18 +37,22 @@
namespace fuzz {
struct Replayer::Impl {
- explicit Impl(spv_target_env env, bool validate)
- : target_env(env), validate_during_replay(validate) {}
+ 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 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)
- : impl_(MakeUnique<Impl>(env, validate_during_replay)) {}
+Replayer::Replayer(spv_target_env env, bool validate_during_replay,
+ spv_validator_options validator_options)
+ : impl_(MakeUnique<Impl>(env, validate_during_replay, validator_options)) {}
Replayer::~Replayer() = default;
@@ -74,7 +78,8 @@
}
// Initial binary should be valid.
- if (!tools.Validate(&binary_in[0], binary_in.size())) {
+ if (!tools.Validate(&binary_in[0], binary_in.size(),
+ impl_->validator_options)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Initial binary is invalid; stopping.");
return Replayer::ReplayerResultStatus::kInitialBinaryInvalid;
@@ -111,8 +116,8 @@
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())) {
+ 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.");
diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h
index 1d58bae..e77d840 100644
--- a/source/fuzz/replayer.h
+++ b/source/fuzz/replayer.h
@@ -37,7 +37,8 @@
};
// Constructs a replayer from the given target environment.
- explicit Replayer(spv_target_env env, bool validate_during_replay);
+ Replayer(spv_target_env env, bool validate_during_replay,
+ spv_validator_options validator_options);
// Disables copy/move constructor/assignment operations.
Replayer(const Replayer&) = delete;
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
index 1bb92f1..b8e4145 100644
--- a/source/fuzz/shrinker.cpp
+++ b/source/fuzz/shrinker.cpp
@@ -60,20 +60,27 @@
} // namespace
struct Shrinker::Impl {
- explicit Impl(spv_target_env env, uint32_t limit, bool validate)
- : target_env(env), step_limit(limit), validate_during_replay(validate) {}
+ 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) {}
- 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.
+ 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.
};
Shrinker::Shrinker(spv_target_env env, uint32_t step_limit,
- bool validate_during_replay)
- : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay)) {}
+ bool validate_during_replay,
+ spv_validator_options validator_options)
+ : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay,
+ validator_options)) {}
Shrinker::~Shrinker() = default;
@@ -113,7 +120,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).
- if (Replayer(impl_->target_env, impl_->validate_during_replay)
+ if (Replayer(impl_->target_env, impl_->validate_during_replay,
+ impl_->validator_options)
.Run(binary_in, initial_facts, transformation_sequence_in,
¤t_best_binary, ¤t_best_transformations) !=
Replayer::ReplayerResultStatus::kComplete) {
@@ -184,7 +192,8 @@
// transformations inapplicable.
std::vector<uint32_t> next_binary;
protobufs::TransformationSequence next_transformation_sequence;
- if (Replayer(impl_->target_env, false)
+ if (Replayer(impl_->target_env, impl_->validate_during_replay,
+ impl_->validator_options)
.Run(binary_in, initial_facts, transformations_with_chunk_removed,
&next_binary, &next_transformation_sequence) !=
Replayer::ReplayerResultStatus::kComplete) {
diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h
index 0163a53..17b15bf 100644
--- a/source/fuzz/shrinker.h
+++ b/source/fuzz/shrinker.h
@@ -50,8 +50,8 @@
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 env, uint32_t step_limit, bool validate_during_replay,
+ spv_validator_options validator_options);
// Disables copy/move constructor/assignment operations.
Shrinker(const Shrinker&) = delete;
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index b91393e..b9024b9 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -1602,8 +1602,9 @@
std::vector<uint32_t> fuzzer_binary_out;
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
- Fuzzer fuzzer(env, seed, true);
- fuzzer.SetMessageConsumer(kSilentConsumer);
+ spvtools::ValidatorOptions validator_options;
+ Fuzzer fuzzer(env, seed, true, validator_options);
+ fuzzer.SetMessageConsumer(kConsoleMessageConsumer);
auto fuzzer_result_status =
fuzzer.Run(binary_in, initial_facts, donor_suppliers,
&fuzzer_binary_out, &fuzzer_transformation_sequence_out);
@@ -1613,8 +1614,8 @@
std::vector<uint32_t> replayer_binary_out;
protobufs::TransformationSequence replayer_transformation_sequence_out;
- Replayer replayer(env, false);
- replayer.SetMessageConsumer(kSilentConsumer);
+ Replayer replayer(env, false, validator_options);
+ replayer.SetMessageConsumer(kConsoleMessageConsumer);
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
index c906a1e..24b4460 100644
--- a/test/fuzz/fuzzer_shrinker_test.cpp
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -979,15 +979,19 @@
// The |step_limit| parameter restricts the number of steps that the shrinker
// will try; it can be set to something small for a faster (but less thorough)
// test.
+//
+// The |validator_options| parameter provides validator options that should be
+// used during shrinking.
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, uint32_t step_limit) {
+ uint32_t expected_transformations_out_size, uint32_t step_limit,
+ spv_validator_options validator_options) {
// Run the shrinker.
- Shrinker shrinker(target_env, step_limit, false);
+ Shrinker shrinker(target_env, step_limit, false, validator_options);
shrinker.SetMessageConsumer(kSilentConsumer);
std::vector<uint32_t> binary_out;
@@ -1035,7 +1039,8 @@
// 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;
- Fuzzer fuzzer(env, seed, true);
+ spvtools::ValidatorOptions validator_options;
+ Fuzzer fuzzer(env, seed, true, validator_options);
fuzzer.SetMessageConsumer(kSilentConsumer);
auto fuzzer_result_status =
fuzzer.Run(binary_in, initial_facts, donor_suppliers, &fuzzer_binary_out,
@@ -1048,9 +1053,10 @@
// 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, kReasonableStepLimit);
+ RunAndCheckShrinker(env, binary_in, initial_facts,
+ fuzzer_transformation_sequence_out,
+ AlwaysInteresting().AsFunction(), binary_in, 0,
+ kReasonableStepLimit, validator_options);
// With the OnlyInterestingFirstTime test, no shrinking should be achieved.
RunAndCheckShrinker(
@@ -1058,14 +1064,14 @@
OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
static_cast<uint32_t>(
fuzzer_transformation_sequence_out.transformation_size()),
- kReasonableStepLimit);
+ kReasonableStepLimit, validator_options);
// 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, kSmallStepLimit);
+ RunAndCheckShrinker(
+ env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
+ PingPong().AsFunction(), {}, 0, kSmallStepLimit, validator_options);
// The InterestingThenRandom test is unpredictable; passing an empty
// expected binary means that we do not check anything about shrinking
@@ -1073,7 +1079,7 @@
RunAndCheckShrinker(
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
- kSmallStepLimit);
+ kSmallStepLimit, validator_options);
}
TEST(FuzzerShrinkerTest, Miscellaneous1) {
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 718d038..469c81b 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -145,6 +145,13 @@
--version
Display fuzzer version information.
+Supported validator options are as follows. See `spirv-val --help` for details.
+ --before-hlsl-legalization
+ --relax-block-layout
+ --relax-logical-pointer
+ --relax-struct-store
+ --scalar-block-layout
+ --skip-block-layout
)",
program, program, program, program);
}
@@ -166,7 +173,8 @@
std::vector<std::string>* interestingness_test,
std::string* shrink_transformations_file,
std::string* shrink_temp_file_prefix,
- spvtools::FuzzerOptions* fuzzer_options) {
+ spvtools::FuzzerOptions* fuzzer_options,
+ spvtools::ValidatorOptions* validator_options) {
uint32_t positional_arg_index = 0;
bool only_positional_arguments_remain = false;
bool force_render_red = false;
@@ -227,6 +235,18 @@
sizeof("--shrinker-temp-file-prefix=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
*shrink_temp_file_prefix = std::string(split_flag.second);
+ } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
+ validator_options->SetBeforeHlslLegalization(true);
+ } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
+ validator_options->SetRelaxLogicalPointer(true);
+ } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
+ validator_options->SetRelaxBlockLayout(true);
+ } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
+ validator_options->SetScalarBlockLayout(true);
+ } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
+ validator_options->SetSkipBlockLayout(true);
+ } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
+ validator_options->SetRelaxStructStore(true);
} else if (0 == strcmp(cur_arg, "--")) {
only_positional_arguments_remain = true;
} else {
@@ -357,6 +377,7 @@
bool Replay(const spv_target_env& target_env,
spv_const_fuzzer_options fuzzer_options,
+ spv_validator_options validator_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
const std::string& replay_transformations_file,
@@ -368,8 +389,8 @@
&transformation_sequence)) {
return false;
}
- spvtools::fuzz::Replayer replayer(target_env,
- fuzzer_options->replay_validation_enabled);
+ spvtools::fuzz::Replayer replayer(
+ target_env, fuzzer_options->replay_validation_enabled, validator_options);
replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
auto replay_result_status =
replayer.Run(binary_in, initial_facts, transformation_sequence,
@@ -380,6 +401,7 @@
bool Shrink(const spv_target_env& target_env,
spv_const_fuzzer_options fuzzer_options,
+ spv_validator_options validator_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
const std::string& shrink_transformations_file,
@@ -393,9 +415,9 @@
&transformation_sequence)) {
return false;
}
- spvtools::fuzz::Shrinker shrinker(target_env,
- fuzzer_options->shrinker_step_limit,
- fuzzer_options->replay_validation_enabled);
+ spvtools::fuzz::Shrinker shrinker(
+ target_env, fuzzer_options->shrinker_step_limit,
+ fuzzer_options->replay_validation_enabled, validator_options);
shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
assert(!interestingness_command.empty() &&
@@ -434,6 +456,7 @@
bool Fuzz(const spv_target_env& target_env,
spv_const_fuzzer_options fuzzer_options,
+ spv_validator_options validator_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
const std::string& donors, std::vector<uint32_t>* binary_out,
@@ -469,7 +492,7 @@
fuzzer_options->has_random_seed
? fuzzer_options->random_seed
: static_cast<uint32_t>(std::random_device()()),
- fuzzer_options->fuzzer_pass_validation_enabled);
+ fuzzer_options->fuzzer_pass_validation_enabled, validator_options);
fuzzer.SetMessageConsumer(message_consumer);
auto fuzz_result_status =
fuzzer.Run(binary_in, initial_facts, donor_suppliers, binary_out,
@@ -513,11 +536,13 @@
std::string shrink_temp_file_prefix = "temp_";
spvtools::FuzzerOptions fuzzer_options;
+ spvtools::ValidatorOptions validator_options;
- FuzzStatus status = ParseFlags(
- argc, argv, &in_binary_file, &out_binary_file, &donors_file,
- &replay_transformations_file, &interestingness_test,
- &shrink_transformations_file, &shrink_temp_file_prefix, &fuzzer_options);
+ FuzzStatus status =
+ ParseFlags(argc, argv, &in_binary_file, &out_binary_file, &donors_file,
+ &replay_transformations_file, &interestingness_test,
+ &shrink_transformations_file, &shrink_temp_file_prefix,
+ &fuzzer_options, &validator_options);
if (status.action == FuzzActions::STOP) {
return status.code;
@@ -561,14 +586,15 @@
}
break;
case FuzzActions::FUZZ:
- if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
- donors_file, &binary_out, &transformations_applied)) {
+ if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in,
+ initial_facts, donors_file, &binary_out,
+ &transformations_applied)) {
return 1;
}
break;
case FuzzActions::REPLAY:
- if (!Replay(target_env, fuzzer_options, binary_in, initial_facts,
- replay_transformations_file, &binary_out,
+ if (!Replay(target_env, fuzzer_options, validator_options, binary_in,
+ initial_facts, replay_transformations_file, &binary_out,
&transformations_applied)) {
return 1;
}
@@ -579,9 +605,9 @@
<< std::endl;
return 1;
}
- if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
- shrink_transformations_file, shrink_temp_file_prefix,
- interestingness_test, &binary_out,
+ if (!Shrink(target_env, fuzzer_options, validator_options, binary_in,
+ initial_facts, shrink_transformations_file,
+ shrink_temp_file_prefix, interestingness_test, &binary_out,
&transformations_applied)) {
return 1;
}