diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index 40da49743..012b5699a 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -17,9 +17,7 @@ #include #include #include -#include -#include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass_add_access_chains.h" #include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h" @@ -91,9 +89,6 @@ #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h" #include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h" #include "source/fuzz/pass_management/repeated_pass_manager.h" -#include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h" -#include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h" -#include "source/fuzz/pass_management/repeated_pass_manager_simple.h" #include "source/fuzz/pass_management/repeated_pass_recommender_standard.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation_context.h" @@ -104,35 +99,142 @@ namespace spvtools { namespace fuzz { -namespace { -const uint32_t kIdBoundGap = 100; - -} // namespace - -Fuzzer::Fuzzer(spv_target_env target_env, MessageConsumer consumer, - const std::vector& binary_in, - const protobufs::FactSequence& initial_facts, +Fuzzer::Fuzzer(std::unique_ptr ir_context, + std::unique_ptr transformation_context, + std::unique_ptr fuzzer_context, + MessageConsumer consumer, const std::vector& donor_suppliers, - std::unique_ptr random_generator, bool enable_all_passes, RepeatedPassStrategy repeated_pass_strategy, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options) - : target_env_(target_env), - consumer_(std::move(consumer)), - binary_in_(binary_in), - initial_facts_(initial_facts), - donor_suppliers_(donor_suppliers), - random_generator_(std::move(random_generator)), + : consumer_(std::move(consumer)), enable_all_passes_(enable_all_passes), - repeated_pass_strategy_(repeated_pass_strategy), validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass), validator_options_(validator_options), num_repeated_passes_applied_(0), - ir_context_(nullptr), - fuzzer_context_(nullptr), - transformation_context_(nullptr), - transformation_sequence_out_() {} + is_valid_(true), + ir_context_(std::move(ir_context)), + transformation_context_(std::move(transformation_context)), + fuzzer_context_(std::move(fuzzer_context)), + transformation_sequence_out_(), + pass_instances_(), + repeated_pass_recommender_(nullptr), + repeated_pass_manager_(nullptr), + final_passes_() { + assert(ir_context_ && "IRContext is not initialized"); + assert(fuzzer_context_ && "FuzzerContext is not initialized"); + assert(transformation_context_ && "TransformationContext is not initialized"); + assert(fuzzerutil::IsValidAndWellFormed(ir_context_.get(), validator_options_, + consumer_) && + "IRContext is invalid"); + + // The following passes are likely to be very useful: many other passes + // introduce synonyms, irrelevant ids and constants that these passes can work + // with. We thus enable them with high probability. + MaybeAddRepeatedPass(90, &pass_instances_); + MaybeAddRepeatedPass(90, &pass_instances_); + MaybeAddRepeatedPass(90, &pass_instances_); + + do { + // Each call to MaybeAddRepeatedPass randomly decides whether the given pass + // should be enabled, and adds an instance of the pass to |pass_instances| + // if it is enabled. + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_, + donor_suppliers); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + MaybeAddRepeatedPass( + &pass_instances_); + MaybeAddRepeatedPass(&pass_instances_); + // There is a theoretical possibility that no pass instances were created + // until now; loop again if so. + } while (pass_instances_.GetPasses().empty()); + + repeated_pass_recommender_ = MakeUnique( + &pass_instances_, fuzzer_context_.get()); + repeated_pass_manager_ = RepeatedPassManager::Create( + repeated_pass_strategy, fuzzer_context_.get(), &pass_instances_, + repeated_pass_recommender_.get()); + + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass( + &final_passes_); + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass(&final_passes_); + MaybeAddFinalPass(&final_passes_); +} Fuzzer::~Fuzzer() = default; @@ -165,240 +267,104 @@ bool Fuzzer::ApplyPassAndCheckValidity(FuzzerPass* pass) const { consumer_); } -Fuzzer::FuzzerResult Fuzzer::Run() { - // Check compatibility between the library version being linked with and the - // header files being used. - GOOGLE_PROTOBUF_VERIFY_VERSION; +opt::IRContext* Fuzzer::GetIRContext() { return ir_context_.get(); } - assert(ir_context_ == nullptr && fuzzer_context_ == nullptr && - transformation_context_ == nullptr && - transformation_sequence_out_.transformation_size() == 0 && - "'Run' must not be invoked more than once."); - - spvtools::SpirvTools tools(target_env_); - tools.SetMessageConsumer(consumer_); - if (!tools.IsValid()) { - consumer_(SPV_MSG_ERROR, nullptr, {}, - "Failed to create SPIRV-Tools interface; stopping."); - return {Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface, - std::vector(), protobufs::TransformationSequence()}; - } - - // Initial binary should be valid. - 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, - std::vector(), protobufs::TransformationSequence()}; - } - - // Build the module from the input binary. - ir_context_ = - BuildModule(target_env_, consumer_, binary_in_.data(), binary_in_.size()); - assert(ir_context_); - - // 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 - // to this so that there is a sizeable gap between the ids used in the - // original module and the ids used for fuzzing, as a readability aid. - // - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the - // case where the maximum id bound is reached. - auto minimum_fresh_id = ir_context_->module()->id_bound() + kIdBoundGap; - fuzzer_context_ = - MakeUnique(random_generator_.get(), minimum_fresh_id); - - transformation_context_ = MakeUnique( - MakeUnique(ir_context_.get()), validator_options_); - transformation_context_->GetFactManager()->AddInitialFacts(consumer_, - initial_facts_); - - RepeatedPassInstances pass_instances{}; - - // The following passes are likely to be very useful: many other passes - // introduce synonyms, irrelevant ids and constants that these passes can work - // with. We thus enable them with high probability. - MaybeAddRepeatedPass(90, &pass_instances); - MaybeAddRepeatedPass(90, &pass_instances); - MaybeAddRepeatedPass(90, &pass_instances); - - do { - // Each call to MaybeAddRepeatedPass randomly decides whether the given pass - // should be enabled, and adds an instance of the pass to |pass_instances| - // if it is enabled. - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances, - donor_suppliers_); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass(&pass_instances); - MaybeAddRepeatedPass( - &pass_instances); - MaybeAddRepeatedPass(&pass_instances); - // There is a theoretical possibility that no pass instances were created - // until now; loop again if so. - } while (pass_instances.GetPasses().empty()); - - RepeatedPassRecommenderStandard pass_recommender(&pass_instances, - fuzzer_context_.get()); - - std::unique_ptr repeated_pass_manager = nullptr; - switch (repeated_pass_strategy_) { - case RepeatedPassStrategy::kSimple: - repeated_pass_manager = MakeUnique( - fuzzer_context_.get(), &pass_instances); - break; - case RepeatedPassStrategy::kLoopedWithRecommendations: - repeated_pass_manager = - MakeUnique( - fuzzer_context_.get(), &pass_instances, &pass_recommender); - break; - case RepeatedPassStrategy::kRandomWithRecommendations: - repeated_pass_manager = - MakeUnique( - fuzzer_context_.get(), &pass_instances, &pass_recommender); - break; - } - - do { - if (!ApplyPassAndCheckValidity( - repeated_pass_manager->ChoosePass(transformation_sequence_out_))) { - return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule, - std::vector(), protobufs::TransformationSequence()}; - } - } while (ShouldContinueFuzzing()); - - // Now apply some passes that it does not make sense to apply repeatedly, - // as they do not unlock other passes. - std::vector> final_passes; - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass( - &final_passes); - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass(&final_passes); - MaybeAddFinalPass(&final_passes); - for (auto& pass : final_passes) { - if (!ApplyPassAndCheckValidity(pass.get())) { - return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule, - std::vector(), protobufs::TransformationSequence()}; - } - } - // Encode the module as a binary. - std::vector binary_out; - ir_context_->module()->ToBinary(&binary_out, false); - - return {Fuzzer::FuzzerResultStatus::kComplete, std::move(binary_out), - std::move(transformation_sequence_out_)}; +const protobufs::TransformationSequence& Fuzzer::GetTransformationSequence() + const { + return transformation_sequence_out_; } -bool Fuzzer::ShouldContinueFuzzing() { - // There's a risk that fuzzing could get stuck, if none of the enabled fuzzer - // passes are able to apply any transformations. To guard against this we - // count the number of times some repeated pass has been applied and ensure - // that fuzzing stops if the number of repeated passes hits the limit on the - // number of transformations that can be applied. - assert( - num_repeated_passes_applied_ <= - fuzzer_context_->GetTransformationLimit() && - "The number of repeated passes applied must not exceed its upper limit."); - if (ir_context_->module()->id_bound() >= fuzzer_context_->GetIdBoundLimit()) { - return false; - } - if (num_repeated_passes_applied_ == - fuzzer_context_->GetTransformationLimit()) { - // Stop because fuzzing has got stuck. - return false; - } - auto transformations_applied_so_far = +Fuzzer::Result Fuzzer::Run(uint32_t num_of_transformations_to_apply) { + assert(is_valid_ && "The module was invalidated during the previous fuzzing"); + + const auto initial_num_of_transformations = static_cast(transformation_sequence_out_.transformation_size()); - if (transformations_applied_so_far >= - fuzzer_context_->GetTransformationLimit()) { - // Stop because we have reached the transformation limit. - return false; + + auto status = Status::kComplete; + do { + // Check that the module is small enough. + if (ir_context_->module()->id_bound() >= + fuzzer_context_->GetIdBoundLimit()) { + status = Status::kModuleTooBig; + break; + } + + auto transformations_applied_so_far = static_cast( + transformation_sequence_out_.transformation_size()); + assert(transformations_applied_so_far >= initial_num_of_transformations && + "Number of transformations cannot decrease"); + + // Check if we've already applied the maximum number of transformations. + if (transformations_applied_so_far >= + fuzzer_context_->GetTransformationLimit()) { + status = Status::kTransformationLimitReached; + break; + } + + // If the number of transformations is still small + if (num_repeated_passes_applied_ >= + fuzzer_context_->GetTransformationLimit()) { + status = Status::kFuzzerStuck; + break; + } + + // Check whether we've exceeded the number of transformations we can apply + // in a single call to this method. + if (num_of_transformations_to_apply != 0 && + transformations_applied_so_far - initial_num_of_transformations >= + num_of_transformations_to_apply) { + status = Status::kComplete; + break; + } + + if (!ApplyPassAndCheckValidity( + repeated_pass_manager_->ChoosePass(transformation_sequence_out_))) { + status = Status::kFuzzerPassLedToInvalidModule; + break; + } + } while (ShouldContinueRepeatedPasses(num_of_transformations_to_apply == 0)); + + if (status != Status::kFuzzerPassLedToInvalidModule) { + // We apply this transformations despite the fact that we might exceed + // |num_of_transformations_to_apply|. This is not a problem for us since + // these fuzzer passes are relatively simple yet might trigger some bugs. + for (auto& pass : final_passes_) { + if (!ApplyPassAndCheckValidity(pass.get())) { + status = Status::kFuzzerPassLedToInvalidModule; + break; + } + } } - // If we have applied T transformations so far, and the limit on the number of - // transformations to apply is L (where T < L), the chance that we will - // continue fuzzing is: - // - // 1 - T/(2*L) - // - // That is, the chance of continuing decreases as more transformations are - // applied. Using 2*L instead of L increases the number of transformations - // that are applied on average. - auto chance_of_continuing = static_cast( - 100.0 * (1.0 - (static_cast(transformations_applied_so_far) / - (2.0 * static_cast( - fuzzer_context_->GetTransformationLimit()))))); - if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) { - // We have probabilistically decided to stop. - return false; + + is_valid_ = status != Status::kFuzzerPassLedToInvalidModule; + return {status, static_cast( + transformation_sequence_out_.transformation_size()) != + initial_num_of_transformations}; +} + +bool Fuzzer::ShouldContinueRepeatedPasses( + bool continue_fuzzing_probabilistically) { + if (continue_fuzzing_probabilistically) { + // If we have applied T transformations so far, and the limit on the number + // of transformations to apply is L (where T < L), the chance that we will + // continue fuzzing is: + // + // 1 - T/(2*L) + // + // That is, the chance of continuing decreases as more transformations are + // applied. Using 2*L instead of L increases the number of transformations + // that are applied on average. + auto transformations_applied_so_far = static_cast( + transformation_sequence_out_.transformation_size()); + auto chance_of_continuing = static_cast( + 100.0 * + (1.0 - (static_cast(transformations_applied_so_far) / + (2.0 * static_cast( + fuzzer_context_->GetTransformationLimit()))))); + if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) { + // We have probabilistically decided to stop. + return false; + } } // Continue fuzzing! num_repeated_passes_applied_++; diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h index 774457f54..1e7d6860c 100644 --- a/source/fuzz/fuzzer.h +++ b/source/fuzz/fuzzer.h @@ -23,6 +23,7 @@ #include "source/fuzz/fuzzer_pass.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/pass_management/repeated_pass_instances.h" +#include "source/fuzz/pass_management/repeated_pass_manager.h" #include "source/fuzz/pass_management/repeated_pass_recommender.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/random_generator.h" @@ -37,33 +38,28 @@ namespace fuzz { class Fuzzer { public: // Possible statuses that can result from running the fuzzer. - enum class FuzzerResultStatus { + enum class Status { kComplete, - kFailedToCreateSpirvToolsInterface, + kModuleTooBig, + kTransformationLimitReached, + kFuzzerStuck, kFuzzerPassLedToInvalidModule, - kInitialBinaryInvalid, }; - struct FuzzerResult { - FuzzerResultStatus status; - std::vector transformed_binary; - protobufs::TransformationSequence applied_transformations; + struct Result { + // Status of the fuzzing session. + Status status; + + // Equals to true if new transformations were applied during the previous + // fuzzing session. + bool is_changed; }; - // Each field of this enum corresponds to an available repeated pass - // strategy, and is used to decide which kind of RepeatedPassManager object - // to create. - enum class RepeatedPassStrategy { - kSimple, - kRandomWithRecommendations, - kLoopedWithRecommendations - }; - - Fuzzer(spv_target_env target_env, MessageConsumer consumer, - const std::vector& binary_in, - const protobufs::FactSequence& initial_facts, + Fuzzer(std::unique_ptr ir_context, + std::unique_ptr transformation_context, + std::unique_ptr fuzzer_context, + MessageConsumer consumer, const std::vector& donor_suppliers, - std::unique_ptr random_generator, bool enable_all_passes, RepeatedPassStrategy repeated_pass_strategy, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options); @@ -76,15 +72,23 @@ class Fuzzer { ~Fuzzer(); - // Transforms |binary_in_| by running a number of randomized fuzzer passes. - // Initial facts about the input binary and the context in which it will - // execute are provided via |initial_facts_|. A source of donor modules to be - // used by transformations is provided via |donor_suppliers_|. On success, - // returns a successful result status together with the transformed binary and - // the sequence of transformations that were applied. Otherwise, returns an - // appropriate result status together with an empty binary and empty - // transformation sequence. - FuzzerResult Run(); + // Transforms |ir_context_| by running a number of randomized fuzzer passes. + // Initial facts about the input binary and the context in which it will be + // executed are provided with |transformation_context_|. + // |num_of_transformations| is equal to the maximum number of transformations + // applied in a single call to this method. This parameter is ignored if its + // value is equal to 0. Because fuzzing cannot stop mid way through a fuzzer + // pass, fuzzing will stop after the fuzzer pass that exceeds + // |num_of_transformations| has completed, so that the total number of + // transformations may be somewhat larger than this number. + Result Run(uint32_t num_of_transformations_to_apply); + + // Returns the current IR context. It may be invalid if the Run method + // returned Status::kFuzzerPassLedToInvalidModule previously. + opt::IRContext* GetIRContext(); + + // Returns the sequence of applied transformations. + const protobufs::TransformationSequence& GetTransformationSequence() const; private: // A convenience method to add a repeated fuzzer pass to |pass_instances| with @@ -119,7 +123,9 @@ class Fuzzer { // Decides whether to apply more repeated passes. The probability decreases as // the number of transformations that have been applied increases. - bool ShouldContinueFuzzing(); + // The described probability is only applied if + // |continue_fuzzing_probabilistically| is true. + bool ShouldContinueRepeatedPasses(bool continue_fuzzing_probabilistically); // Applies |pass|, which must be a pass constructed with |ir_context|. // If |validate_after_each_fuzzer_pass_| is not set, true is always returned. @@ -128,57 +134,59 @@ class Fuzzer { // instruction has a distinct unique id. bool ApplyPassAndCheckValidity(FuzzerPass* pass) const; - // Target environment. - const spv_target_env target_env_; - // Message consumer that will be invoked once for each message communicated // from the library. - MessageConsumer consumer_; - - // The initial binary to which fuzzing should be applied. - const std::vector& binary_in_; - - // Initial facts known to hold in advance of applying any transformations. - const protobufs::FactSequence& initial_facts_; - - // A source of modules whose contents can be donated into the module being - // fuzzed. - const std::vector& donor_suppliers_; - - // Random number generator to control decision making during fuzzing. - std::unique_ptr random_generator_; + const MessageConsumer consumer_; // Determines whether all passes should be enabled, vs. having passes be // probabilistically enabled. - bool enable_all_passes_; - - // Controls which type of RepeatedPassManager object to create. - RepeatedPassStrategy repeated_pass_strategy_; + const bool enable_all_passes_; // Determines whether the validator should be invoked after every fuzzer pass. - bool validate_after_each_fuzzer_pass_; + const bool validate_after_each_fuzzer_pass_; // Options to control validation. - spv_validator_options validator_options_; + const spv_validator_options validator_options_; // The number of repeated fuzzer passes that have been applied is kept track // of, in order to enforce a hard limit on the number of times such passes // can be applied. uint32_t num_repeated_passes_applied_; + // We use this to determine whether we can continue fuzzing incrementally + // since the previous call to the Run method could've returned + // kFuzzerPassLedToInvalidModule. + bool is_valid_; + // Intermediate representation for the module being fuzzed, which gets // mutated as fuzzing proceeds. std::unique_ptr ir_context_; + // Contextual information that is required in order to apply + // transformations. + std::unique_ptr transformation_context_; + // Provides probabilities that control the fuzzing process. std::unique_ptr fuzzer_context_; - // Contextual information that is required in order to apply transformations. - std::unique_ptr transformation_context_; - - // The sequence of transformations that have been applied during fuzzing. It + // The sequence of transformations that have been applied during fuzzing. It // is initially empty and grows as fuzzer passes are applied. protobufs::TransformationSequence transformation_sequence_out_; + + // This object contains instances of all fuzzer passes that will participate + // in the fuzzing. + RepeatedPassInstances pass_instances_; + + // This object defines the recommendation logic for fuzzer passes. + std::unique_ptr repeated_pass_recommender_; + + // This object manager a list of fuzzer pass and their available + // recommendations. + std::unique_ptr repeated_pass_manager_; + + // Some passes that it does not make sense to apply repeatedly, as they do not + // unlock other passes. + std::vector> final_passes_; }; } // namespace fuzz diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index 47bf4e258..1ace8c25e 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -21,6 +21,12 @@ namespace fuzz { namespace { +// An offset between the the module's id bound and the minimum fresh id. +// +// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541): consider +// the case where the maximum id bound is reached. +const uint32_t kIdBoundGap = 100; + // Limits to help control the overall fuzzing process and rein in individual // fuzzer passes. const uint32_t kIdBoundLimit = 50000; @@ -177,9 +183,9 @@ const std::function } // namespace -FuzzerContext::FuzzerContext(RandomGenerator* random_generator, +FuzzerContext::FuzzerContext(std::unique_ptr random_generator, uint32_t min_fresh_id) - : random_generator_(random_generator), + : random_generator_(std::move(random_generator)), next_fresh_id_(min_fresh_id), max_equivalence_class_size_for_data_synonym_fact_closure_( kDefaultMaxEquivalenceClassSizeForDataSynonymFactClosure), @@ -403,5 +409,9 @@ uint32_t FuzzerContext::GetTransformationLimit() const { return kTransformationLimit; } +uint32_t FuzzerContext::GetMinFreshId(opt::IRContext* ir_context) { + return ir_context->module()->id_bound() + kIdBoundGap; +} + } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 8c5104188..7db29c8db 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -21,6 +21,7 @@ #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/random_generator.h" #include "source/opt/function.h" +#include "source/opt/ir_context.h" namespace spvtools { namespace fuzz { @@ -32,7 +33,8 @@ class FuzzerContext { public: // Constructs a fuzzer context with a given random generator and the minimum // value that can be used for fresh ids. - FuzzerContext(RandomGenerator* random_generator, uint32_t min_fresh_id); + FuzzerContext(std::unique_ptr random_generator, + uint32_t min_fresh_id); ~FuzzerContext(); @@ -115,6 +117,9 @@ class FuzzerContext { // fuzzer passes. uint32_t GetTransformationLimit() const; + // Returns the minimum fresh id that can be used given the |ir_context|. + static uint32_t GetMinFreshId(opt::IRContext* ir_context); + // Probabilities associated with applying various transformations. // Keep them in alphabetical order. uint32_t GetChanceOfAcceptingRepeatedPassRecommendation() const { @@ -442,12 +447,12 @@ class FuzzerContext { return random_generator_->RandomUint32(max_unused_component_count) + 1; } bool GoDeeperInConstantObfuscation(uint32_t depth) { - return go_deeper_in_constant_obfuscation_(depth, random_generator_); + return go_deeper_in_constant_obfuscation_(depth, random_generator_.get()); } private: // The source of randomness. - RandomGenerator* random_generator_; + std::unique_ptr random_generator_; // The next fresh id to be issued. uint32_t next_fresh_id_; diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp index 8c14db4b8..8f6c6c981 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp @@ -47,6 +47,34 @@ const spvtools::MessageConsumer kSilentMessageConsumer = [](spv_message_level_t, const char*, const spv_position_t&, const char*) -> void {}; +bool BuildIRContext(spv_target_env target_env, + const spvtools::MessageConsumer& message_consumer, + const std::vector& binary_in, + spv_validator_options validator_options, + std::unique_ptr* ir_context) { + SpirvTools tools(target_env); + tools.SetMessageConsumer(message_consumer); + if (!tools.IsValid()) { + message_consumer(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); + return false; + } + + // Initial binary should be valid. + if (!tools.Validate(binary_in.data(), binary_in.size(), validator_options)) { + message_consumer(SPV_MSG_ERROR, nullptr, {}, + "Initial binary is invalid; stopping."); + return false; + } + + // Build the module from the input binary. + auto result = BuildModule(target_env, message_consumer, binary_in.data(), + binary_in.size()); + assert(result && "IRContext must be valid"); + *ir_context = std::move(result); + return true; +} + bool IsFreshId(opt::IRContext* context, uint32_t id) { return !context->get_def_use_mgr()->GetDef(id); } @@ -410,7 +438,7 @@ bool IsValid(const opt::IRContext* context, std::vector binary; context->module()->ToBinary(&binary, false); SpirvTools tools(context->grammar().target_env()); - tools.SetMessageConsumer(consumer); + tools.SetMessageConsumer(std::move(consumer)); return tools.Validate(binary.data(), binary.size(), validator_options); } diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h index 4e6ec36ea..3bb1aa62d 100644 --- a/source/fuzz/fuzzer_util.h +++ b/source/fuzz/fuzzer_util.h @@ -38,6 +38,15 @@ extern const spvtools::MessageConsumer kSilentMessageConsumer; // Function type that produces a SPIR-V module. using ModuleSupplier = std::function()>; +// Builds a new opt::IRContext object. Returns true if successful and changes +// the |ir_context| parameter. Otherwise (if any errors occur), returns false +// and |ir_context| remains unchanged. +bool BuildIRContext(spv_target_env target_env, + const spvtools::MessageConsumer& message_consumer, + const std::vector& binary_in, + spv_validator_options validator_options, + std::unique_ptr* ir_context); + // Returns true if and only if the module does not define the given id. bool IsFreshId(opt::IRContext* context, uint32_t id); diff --git a/source/fuzz/pass_management/repeated_pass_manager.cpp b/source/fuzz/pass_management/repeated_pass_manager.cpp index 032f26456..ec443733a 100644 --- a/source/fuzz/pass_management/repeated_pass_manager.cpp +++ b/source/fuzz/pass_management/repeated_pass_manager.cpp @@ -14,6 +14,10 @@ #include "source/fuzz/pass_management/repeated_pass_manager.h" +#include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h" +#include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h" +#include "source/fuzz/pass_management/repeated_pass_manager_simple.h" + namespace spvtools { namespace fuzz { @@ -23,5 +27,25 @@ RepeatedPassManager::RepeatedPassManager(FuzzerContext* fuzzer_context, RepeatedPassManager::~RepeatedPassManager() = default; +std::unique_ptr RepeatedPassManager::Create( + RepeatedPassStrategy strategy, FuzzerContext* fuzzer_context, + RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender) { + switch (strategy) { + case RepeatedPassStrategy::kSimple: + return MakeUnique(fuzzer_context, + pass_instances); + case RepeatedPassStrategy::kLoopedWithRecommendations: + return MakeUnique( + fuzzer_context, pass_instances, pass_recommender); + case RepeatedPassStrategy::kRandomWithRecommendations: + return MakeUnique( + fuzzer_context, pass_instances, pass_recommender); + } + + assert(false && "Unreachable"); + return nullptr; +} + } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager.h b/source/fuzz/pass_management/repeated_pass_manager.h index 1c231792d..1e6ae3e48 100644 --- a/source/fuzz/pass_management/repeated_pass_manager.h +++ b/source/fuzz/pass_management/repeated_pass_manager.h @@ -18,11 +18,21 @@ #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass.h" #include "source/fuzz/pass_management/repeated_pass_instances.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" namespace spvtools { namespace fuzz { +// Each field of this enum corresponds to an available repeated pass +// strategy, and is used to decide which kind of RepeatedPassManager object +// to create. +enum class RepeatedPassStrategy { + kSimple, + kRandomWithRecommendations, + kLoopedWithRecommendations +}; + // An interface to encapsulate the manner in which the sequence of repeated // passes that are applied during fuzzing is chosen. An implementation of this // interface could, for example, keep track of the history of passes that have @@ -40,6 +50,12 @@ class RepeatedPassManager { virtual FuzzerPass* ChoosePass( const protobufs::TransformationSequence& applied_transformations) = 0; + // Creates a corresponding RepeatedPassManager based on the |strategy|. + static std::unique_ptr Create( + RepeatedPassStrategy strategy, FuzzerContext* fuzzer_context, + RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender); + protected: FuzzerContext* GetFuzzerContext() { return fuzzer_context_; } diff --git a/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp index f7a09964c..ebc100271 100644 --- a/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp +++ b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp @@ -128,8 +128,7 @@ TEST(FuzzerPassAddOpPhiSynonymsTest, HelperFunctions) { kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassAddOpPhiSynonyms fuzzer_pass(context.get(), &transformation_context, diff --git a/test/fuzz/fuzzer_pass_construct_composites_test.cpp b/test/fuzz/fuzzer_pass_construct_composites_test.cpp index d49d1d686..8559ed947 100644 --- a/test/fuzz/fuzzer_pass_construct_composites_test.cpp +++ b/test/fuzz/fuzzer_pass_construct_composites_test.cpp @@ -77,7 +77,7 @@ TEST(FuzzerPassConstructCompositesTest, IsomorphicStructs) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; - auto prng = MakeUnique(0); + FuzzerContext fuzzer_context(MakeUnique(0), 100); for (uint32_t i = 0; i < 10; i++) { const auto context = @@ -87,7 +87,6 @@ TEST(FuzzerPassConstructCompositesTest, IsomorphicStructs) { context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); - FuzzerContext fuzzer_context(prng.get(), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassConstructComposites fuzzer_pass( @@ -158,7 +157,7 @@ TEST(FuzzerPassConstructCompositesTest, IsomorphicArrays) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; - auto prng = MakeUnique(0); + FuzzerContext fuzzer_context(MakeUnique(0), 100); for (uint32_t i = 0; i < 10; i++) { const auto context = @@ -168,7 +167,6 @@ TEST(FuzzerPassConstructCompositesTest, IsomorphicArrays) { context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); - FuzzerContext fuzzer_context(prng.get(), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassConstructComposites fuzzer_pass( diff --git a/test/fuzz/fuzzer_pass_donate_modules_test.cpp b/test/fuzz/fuzzer_pass_donate_modules_test.cpp index 1a7cd4ab5..99026652b 100644 --- a/test/fuzz/fuzzer_pass_donate_modules_test.cpp +++ b/test/fuzz/fuzzer_pass_donate_modules_test.cpp @@ -204,8 +204,7 @@ TEST(FuzzerPassDonateModulesTest, BasicDonation) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -285,8 +284,7 @@ TEST(FuzzerPassDonateModulesTest, DonationWithUniforms) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -416,8 +414,7 @@ TEST(FuzzerPassDonateModulesTest, DonationWithInputAndOutputVariables) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -511,8 +508,7 @@ TEST(FuzzerPassDonateModulesTest, DonateFunctionTypeWithDifferentPointers) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -581,8 +577,7 @@ TEST(FuzzerPassDonateModulesTest, DonateOpConstantNull) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -709,8 +704,7 @@ TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImages) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -805,8 +799,7 @@ TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesSampler) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -937,8 +930,7 @@ TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImageStructField) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1073,8 +1065,7 @@ TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImageFunctionParameter) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1155,8 +1146,7 @@ TEST(FuzzerPassDonateModulesTest, DonateShaderWithImageStorageClass) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1242,8 +1232,7 @@ TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithRuntimeArray) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1346,8 +1335,7 @@ TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithRuntimeArrayLivesafe) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1418,8 +1406,7 @@ TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithWorkgroupVariables) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1528,8 +1515,7 @@ TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithAtomics) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1712,8 +1698,7 @@ TEST(FuzzerPassDonateModulesTest, Miscellaneous1) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator rng(0); - FuzzerContext fuzzer_context(&rng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1784,8 +1769,7 @@ TEST(FuzzerPassDonateModulesTest, OpSpecConstantInstructions) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -1941,8 +1925,7 @@ TEST(FuzzerPassDonateModulesTest, DonationSupportsOpTypeRuntimeArray) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator rng(0); - FuzzerContext fuzzer_context(&rng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -2014,8 +1997,7 @@ TEST(FuzzerPassDonateModulesTest, HandlesCapabilities) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator rng(0); - FuzzerContext fuzzer_context(&rng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), @@ -2247,8 +2229,7 @@ TEST(FuzzerPassDonateModulesTest, HandlesOpPhisInMergeBlock) { TransformationContext transformation_context( MakeUnique(recipient_context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), diff --git a/test/fuzz/fuzzer_pass_outline_functions_test.cpp b/test/fuzz/fuzzer_pass_outline_functions_test.cpp index 576962c30..3a29513c6 100644 --- a/test/fuzz/fuzzer_pass_outline_functions_test.cpp +++ b/test/fuzz/fuzzer_pass_outline_functions_test.cpp @@ -124,8 +124,7 @@ TEST(FuzzerPassOutlineFunctionsTest, EntryIsAlreadySuitable) { kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context, @@ -167,8 +166,7 @@ TEST(FuzzerPassOutlineFunctionsTest, EntryHasOpVariable) { kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context, @@ -291,8 +289,7 @@ TEST(FuzzerPassOutlineFunctionsTest, EntryBlockIsHeader) { kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context, @@ -458,8 +455,7 @@ TEST(FuzzerPassOutlineFunctionsTest, ExitBlock) { kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformation_sequence; FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context, diff --git a/test/fuzz/fuzzer_pass_test.cpp b/test/fuzz/fuzzer_pass_test.cpp index 283aa114c..81f7bc41f 100644 --- a/test/fuzz/fuzzer_pass_test.cpp +++ b/test/fuzz/fuzzer_pass_test.cpp @@ -87,8 +87,7 @@ TEST(FuzzerPassTest, ForEachInstructionWithInstructionDescriptor) { ASSERT_TRUE(dominator_analysis->IsReachable(5)); ASSERT_FALSE(dominator_analysis->IsReachable(8)); - PseudoRandomGenerator prng(0); - FuzzerContext fuzzer_context(&prng, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); protobufs::TransformationSequence transformations; FuzzerPassMock fuzzer_pass_mock(context.get(), &transformation_context, &fuzzer_context, &transformations); diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp index dc905741b..2baa4c6e0 100644 --- a/test/fuzz/fuzzer_replayer_test.cpp +++ b/test/fuzz/fuzzer_replayer_test.cpp @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "source/fuzz/fuzzer.h" -#include "source/fuzz/replayer.h" - #include "gtest/gtest.h" +#include "source/fuzz/fuzzer.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/pseudo_random_generator.h" +#include "source/fuzz/replayer.h" #include "source/fuzz/uniform_buffer_element_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -1642,37 +1641,53 @@ void RunFuzzerAndReplayer(const std::string& shader, }); } - std::vector strategies{ - Fuzzer::RepeatedPassStrategy::kSimple, - Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations, - Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations}; + std::vector strategies{ + RepeatedPassStrategy::kSimple, + RepeatedPassStrategy::kLoopedWithRecommendations, + RepeatedPassStrategy::kRandomWithRecommendations}; uint32_t strategy_index = 0; for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) { spvtools::ValidatorOptions validator_options; + + std::unique_ptr ir_context; + ASSERT_TRUE(fuzzerutil::BuildIRContext(env, kConsoleMessageConsumer, + binary_in, validator_options, + &ir_context)); + + auto fuzzer_context = MakeUnique( + MakeUnique(seed), + FuzzerContext::GetMinFreshId(ir_context.get())); + + auto transformation_context = MakeUnique( + MakeUnique(ir_context.get()), validator_options); + transformation_context->GetFactManager()->AddInitialFacts( + kConsoleMessageConsumer, initial_facts); + // Every 4th time we run the fuzzer, enable all fuzzer passes. bool enable_all_passes = (seed % 4) == 0; - auto fuzzer_result = - Fuzzer(env, kConsoleMessageConsumer, binary_in, initial_facts, - donor_suppliers, MakeUnique(seed), - enable_all_passes, strategies[strategy_index], true, - validator_options) - .Run(); + Fuzzer fuzzer(std::move(ir_context), std::move(transformation_context), + std::move(fuzzer_context), kConsoleMessageConsumer, + donor_suppliers, enable_all_passes, + strategies[strategy_index], true, validator_options); + auto fuzzer_result = fuzzer.Run(0); // Cycle the repeated pass strategy so that we try a different one next time // we run the fuzzer. strategy_index = (strategy_index + 1) % static_cast(strategies.size()); - ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result.status); - ASSERT_TRUE(t.Validate(fuzzer_result.transformed_binary)); + ASSERT_NE(Fuzzer::Status::kFuzzerPassLedToInvalidModule, + fuzzer_result.status); + std::vector transformed_binary; + fuzzer.GetIRContext()->module()->ToBinary(&transformed_binary, true); + ASSERT_TRUE(t.Validate(transformed_binary)); auto replayer_result = - Replayer( - env, kConsoleMessageConsumer, binary_in, initial_facts, - fuzzer_result.applied_transformations, - static_cast( - fuzzer_result.applied_transformations.transformation_size()), - false, validator_options) + Replayer(env, kConsoleMessageConsumer, binary_in, initial_facts, + fuzzer.GetTransformationSequence(), + static_cast( + fuzzer.GetTransformationSequence().transformation_size()), + false, validator_options) .Run(); ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, replayer_result.status); @@ -1682,12 +1697,12 @@ void RunFuzzerAndReplayer(const std::string& shader, // replay should be identical to that which resulted from fuzzing. std::string fuzzer_transformations_string; std::string replayer_transformations_string; - fuzzer_result.applied_transformations.SerializeToString( + fuzzer.GetTransformationSequence().SerializeToString( &fuzzer_transformations_string); replayer_result.applied_transformations.SerializeToString( &replayer_transformations_string); ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string); - ASSERT_TRUE(IsEqual(env, fuzzer_result.transformed_binary, + ASSERT_TRUE(IsEqual(env, transformed_binary, replayer_result.transformed_module.get())); } } diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp index 6d9dad358..68329f6a6 100644 --- a/test/fuzz/fuzzer_shrinker_test.cpp +++ b/test/fuzz/fuzzer_shrinker_test.cpp @@ -12,15 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "source/fuzz/fuzzer.h" -#include "source/fuzz/shrinker.h" - #include #include #include "gtest/gtest.h" +#include "source/fuzz/fuzzer.h" #include "source/fuzz/fuzzer_util.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" @@ -1044,24 +1043,38 @@ void RunFuzzerAndShrinker(const std::string& shader, // Depending on the seed, decide whether to enable all passes and which // repeated pass manager to use. bool enable_all_passes = (seed % 4) == 0; - Fuzzer::RepeatedPassStrategy repeated_pass_strategy; + RepeatedPassStrategy repeated_pass_strategy; if ((seed % 3) == 0) { - repeated_pass_strategy = Fuzzer::RepeatedPassStrategy::kSimple; + repeated_pass_strategy = RepeatedPassStrategy::kSimple; } else if ((seed % 3) == 1) { - repeated_pass_strategy = - Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations; + repeated_pass_strategy = RepeatedPassStrategy::kLoopedWithRecommendations; } else { - repeated_pass_strategy = - Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations; + repeated_pass_strategy = RepeatedPassStrategy::kRandomWithRecommendations; } - auto fuzzer_result = - Fuzzer(env, kConsoleMessageConsumer, binary_in, initial_facts, - donor_suppliers, MakeUnique(seed), - enable_all_passes, repeated_pass_strategy, true, validator_options) - .Run(); - ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result.status); - ASSERT_TRUE(t.Validate(fuzzer_result.transformed_binary)); + std::unique_ptr ir_context; + ASSERT_TRUE(fuzzerutil::BuildIRContext( + env, kConsoleMessageConsumer, binary_in, validator_options, &ir_context)); + + auto fuzzer_context = + MakeUnique(MakeUnique(seed), + FuzzerContext::GetMinFreshId(ir_context.get())); + + auto transformation_context = MakeUnique( + MakeUnique(ir_context.get()), validator_options); + transformation_context->GetFactManager()->AddInitialFacts( + kConsoleMessageConsumer, initial_facts); + + Fuzzer fuzzer(std::move(ir_context), std::move(transformation_context), + std::move(fuzzer_context), kConsoleMessageConsumer, + donor_suppliers, enable_all_passes, repeated_pass_strategy, + true, validator_options); + auto fuzzer_result = fuzzer.Run(0); + ASSERT_NE(Fuzzer::Status::kFuzzerPassLedToInvalidModule, + fuzzer_result.status); + std::vector transformed_binary; + fuzzer.GetIRContext()->module()->ToBinary(&transformed_binary, true); + ASSERT_TRUE(t.Validate(transformed_binary)); const uint32_t kReasonableStepLimit = 50; const uint32_t kSmallStepLimit = 20; @@ -1069,30 +1082,30 @@ 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_result.applied_transformations, + fuzzer.GetTransformationSequence(), AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit, validator_options); // With the OnlyInterestingFirstTime test, no shrinking should be achieved. RunAndCheckShrinker( - env, binary_in, initial_facts, fuzzer_result.applied_transformations, - OnlyInterestingFirstTime().AsFunction(), fuzzer_result.transformed_binary, + env, binary_in, initial_facts, fuzzer.GetTransformationSequence(), + OnlyInterestingFirstTime().AsFunction(), transformed_binary, static_cast( - fuzzer_result.applied_transformations.transformation_size()), + fuzzer.GetTransformationSequence().transformation_size()), 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_result.applied_transformations, + env, binary_in, initial_facts, fuzzer.GetTransformationSequence(), 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 // results. RunAndCheckShrinker( - env, binary_in, initial_facts, fuzzer_result.applied_transformations, + env, binary_in, initial_facts, fuzzer.GetTransformationSequence(), InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0, kSmallStepLimit, validator_options); } diff --git a/test/fuzz/shrinker_test.cpp b/test/fuzz/shrinker_test.cpp index 42cd182cb..2071a500a 100644 --- a/test/fuzz/shrinker_test.cpp +++ b/test/fuzz/shrinker_test.cpp @@ -163,8 +163,7 @@ TEST(ShrinkerTest, ReduceAddedFunctions) { ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( donor_ir_context.get(), validator_options, kConsoleMessageConsumer)); - PseudoRandomGenerator random_generator(0); - FuzzerContext fuzzer_context(&random_generator, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); TransformationContext transformation_context( MakeUnique(variant_ir_context.get()), validator_options); @@ -341,8 +340,7 @@ TEST(ShrinkerTest, HitStepLimitWhenReducingAddedFunctions) { ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( donor_ir_context.get(), validator_options, kConsoleMessageConsumer)); - PseudoRandomGenerator random_generator(0); - FuzzerContext fuzzer_context(&random_generator, 100); + FuzzerContext fuzzer_context(MakeUnique(0), 100); TransformationContext transformation_context( MakeUnique(variant_ir_context.get()), validator_options); diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp index 3423f6c9c..a93d26acf 100644 --- a/tools/fuzz/fuzz.cpp +++ b/tools/fuzz/fuzz.cpp @@ -204,7 +204,7 @@ FuzzStatus ParseFlags( std::vector* interestingness_test, std::string* shrink_transformations_file, std::string* shrink_temp_file_prefix, - spvtools::fuzz::Fuzzer::RepeatedPassStrategy* repeated_pass_strategy, + spvtools::fuzz::RepeatedPassStrategy* repeated_pass_strategy, spvtools::FuzzerOptions* fuzzer_options, spvtools::ValidatorOptions* validator_options) { uint32_t positional_arg_index = 0; @@ -212,7 +212,7 @@ FuzzStatus ParseFlags( bool force_render_red = false; *repeated_pass_strategy = - spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations; + spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations; for (int argi = 1; argi < argc; ++argi) { const char* cur_arg = argv[argi]; @@ -250,14 +250,14 @@ FuzzStatus ParseFlags( sizeof("--repeated-pass-strategy=") - 1)) { std::string strategy = spvtools::utils::SplitFlagArgs(cur_arg).second; if (strategy == "looped") { - *repeated_pass_strategy = spvtools::fuzz::Fuzzer:: - RepeatedPassStrategy::kLoopedWithRecommendations; + *repeated_pass_strategy = + spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations; } else if (strategy == "random") { - *repeated_pass_strategy = spvtools::fuzz::Fuzzer:: - RepeatedPassStrategy::kRandomWithRecommendations; + *repeated_pass_strategy = + spvtools::fuzz::RepeatedPassStrategy::kRandomWithRecommendations; } else if (strategy == "simple") { *repeated_pass_strategy = - spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kSimple; + spvtools::fuzz::RepeatedPassStrategy::kSimple; } else { std::stringstream ss; ss << "Unknown repeated pass strategy '" << strategy << "'" @@ -549,7 +549,7 @@ bool Fuzz(const spv_target_env& target_env, const std::vector& binary_in, const spvtools::fuzz::protobufs::FactSequence& initial_facts, const std::string& donors, - spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy, + spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy, std::vector* binary_out, spvtools::fuzz::protobufs::TransformationSequence* transformations_applied) { @@ -578,24 +578,42 @@ bool Fuzz(const spv_target_env& target_env, }); } - auto fuzz_result = - spvtools::fuzz::Fuzzer( - target_env, message_consumer, binary_in, initial_facts, - donor_suppliers, - spvtools::MakeUnique( - fuzzer_options->has_random_seed - ? fuzzer_options->random_seed - : static_cast(std::random_device()())), - fuzzer_options->all_passes_enabled, repeated_pass_strategy, - fuzzer_options->fuzzer_pass_validation_enabled, validator_options) - .Run(); - *binary_out = std::move(fuzz_result.transformed_binary); - *transformations_applied = std::move(fuzz_result.applied_transformations); - if (fuzz_result.status != - spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) { + std::unique_ptr ir_context; + if (!spvtools::fuzz::fuzzerutil::BuildIRContext(target_env, message_consumer, + binary_in, validator_options, + &ir_context)) { + spvtools::Error(FuzzDiagnostic, nullptr, {}, "Initial binary is invalid"); + return false; + } + + auto fuzzer_context = spvtools::MakeUnique( + spvtools::MakeUnique( + fuzzer_options->has_random_seed + ? fuzzer_options->random_seed + : static_cast(std::random_device()())), + spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get())); + + auto transformation_context = + spvtools::MakeUnique( + spvtools::MakeUnique(ir_context.get()), + validator_options); + transformation_context->GetFactManager()->AddInitialFacts(message_consumer, + initial_facts); + + spvtools::fuzz::Fuzzer fuzzer( + std::move(ir_context), std::move(transformation_context), + std::move(fuzzer_context), message_consumer, donor_suppliers, + fuzzer_options->all_passes_enabled, repeated_pass_strategy, + fuzzer_options->fuzzer_pass_validation_enabled, validator_options); + auto fuzz_result = fuzzer.Run(0); + if (fuzz_result.status == + spvtools::fuzz::Fuzzer::Status::kFuzzerPassLedToInvalidModule) { spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer"); return false; } + + fuzzer.GetIRContext()->module()->ToBinary(binary_out, true); + *transformations_applied = fuzzer.GetTransformationSequence(); return true; } @@ -656,7 +674,7 @@ int main(int argc, const char** argv) { std::vector interestingness_test; std::string shrink_transformations_file; std::string shrink_temp_file_prefix = "temp_"; - spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy; + spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy; spvtools::FuzzerOptions fuzzer_options; spvtools::ValidatorOptions validator_options;