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.
This commit is contained in:
Alastair Donaldson 2020-04-02 14:37:59 +01:00 committed by GitHub
parent af01d57b5e
commit 2fdea57d19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 125 additions and 69 deletions

View File

@ -86,26 +86,31 @@ void MaybeAddPass(
} // 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 @@ bool Fuzzer::Impl::ApplyPassAndCheckValidity(
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 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
}
// 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;

View File

@ -41,8 +41,9 @@ class Fuzzer {
// 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;

View File

@ -37,18 +37,22 @@ namespace spvtools {
namespace fuzz {
struct Replayer::Impl {
explicit Impl(spv_target_env env, bool validate)
: target_env(env), validate_during_replay(validate) {}
const spv_target_env target_env; // Target environment.
MessageConsumer consumer; // Message consumer.
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)
: 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 @@ Replayer::ReplayerResultStatus Replayer::Run(
}
// 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 @@ Replayer::ReplayerResultStatus Replayer::Run(
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.");

View File

@ -37,7 +37,8 @@ class Replayer {
};
// 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;

View File

@ -60,20 +60,27 @@ protobufs::TransformationSequence RemoveChunk(
} // 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 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
// 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,
&current_best_binary, &current_best_transformations) !=
Replayer::ReplayerResultStatus::kComplete) {
@ -184,7 +192,8 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
// 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) {

View File

@ -50,8 +50,8 @@ class Shrinker {
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;

View File

@ -1602,8 +1602,9 @@ void RunFuzzerAndReplayer(const std::string& shader,
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 @@ void RunFuzzerAndReplayer(const std::string& shader,
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);

View File

@ -979,15 +979,19 @@ class InterestingThenRandom : public InterestingnessTest {
// 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 @@ void RunFuzzerAndShrinker(const std::string& shader,
// 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 @@ void RunFuzzerAndShrinker(const std::string& shader,
// 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 @@ void RunFuzzerAndShrinker(const std::string& shader,
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 @@ void RunFuzzerAndShrinker(const std::string& shader,
RunAndCheckShrinker(
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
kSmallStepLimit);
kSmallStepLimit, validator_options);
}
TEST(FuzzerShrinkerTest, Miscellaneous1) {

View File

@ -145,6 +145,13 @@ Options (in lexicographical order):
--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 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
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 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
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 ParseTransformations(
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 @@ bool Replay(const spv_target_env& target_env,
&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 Replay(const spv_target_env& target_env,
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 @@ bool Shrink(const spv_target_env& target_env,
&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 Shrink(const spv_target_env& target_env,
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 @@ bool Fuzz(const spv_target_env& target_env,
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 @@ int main(int argc, const char** argv) {
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 @@ int main(int argc, const char** argv) {
}
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 @@ int main(int argc, const char** argv) {
<< 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;
}