| // Copyright (c) 2019 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "source/fuzz/fuzzer.h" | |
| #include <cassert> | |
| #include <memory> | |
| #include <numeric> | |
| #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" | |
| #include "source/fuzz/fuzzer_pass_add_composite_extract.h" | |
| #include "source/fuzz/fuzzer_pass_add_composite_inserts.h" | |
| #include "source/fuzz/fuzzer_pass_add_composite_types.h" | |
| #include "source/fuzz/fuzzer_pass_add_copy_memory.h" | |
| #include "source/fuzz/fuzzer_pass_add_dead_blocks.h" | |
| #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" | |
| #include "source/fuzz/fuzzer_pass_add_dead_continues.h" | |
| #include "source/fuzz/fuzzer_pass_add_equation_instructions.h" | |
| #include "source/fuzz/fuzzer_pass_add_function_calls.h" | |
| #include "source/fuzz/fuzzer_pass_add_global_variables.h" | |
| #include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h" | |
| #include "source/fuzz/fuzzer_pass_add_loads.h" | |
| #include "source/fuzz/fuzzer_pass_add_local_variables.h" | |
| #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" | |
| #include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h" | |
| #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" | |
| #include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" | |
| #include "source/fuzz/fuzzer_pass_add_parameters.h" | |
| #include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" | |
| #include "source/fuzz/fuzzer_pass_add_stores.h" | |
| #include "source/fuzz/fuzzer_pass_add_synonyms.h" | |
| #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h" | |
| #include "source/fuzz/fuzzer_pass_adjust_branch_weights.h" | |
| #include "source/fuzz/fuzzer_pass_adjust_function_controls.h" | |
| #include "source/fuzz/fuzzer_pass_adjust_loop_controls.h" | |
| #include "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h" | |
| #include "source/fuzz/fuzzer_pass_adjust_selection_controls.h" | |
| #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h" | |
| #include "source/fuzz/fuzzer_pass_construct_composites.h" | |
| #include "source/fuzz/fuzzer_pass_copy_objects.h" | |
| #include "source/fuzz/fuzzer_pass_donate_modules.h" | |
| #include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h" | |
| #include "source/fuzz/fuzzer_pass_expand_vector_reductions.h" | |
| #include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h" | |
| #include "source/fuzz/fuzzer_pass_inline_functions.h" | |
| #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h" | |
| #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h" | |
| #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" | |
| #include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h" | |
| #include "source/fuzz/fuzzer_pass_merge_blocks.h" | |
| #include "source/fuzz/fuzzer_pass_merge_function_returns.h" | |
| #include "source/fuzz/fuzzer_pass_mutate_pointers.h" | |
| #include "source/fuzz/fuzzer_pass_obfuscate_constants.h" | |
| #include "source/fuzz/fuzzer_pass_outline_functions.h" | |
| #include "source/fuzz/fuzzer_pass_permute_blocks.h" | |
| #include "source/fuzz/fuzzer_pass_permute_function_parameters.h" | |
| #include "source/fuzz/fuzzer_pass_permute_function_variables.h" | |
| #include "source/fuzz/fuzzer_pass_permute_instructions.h" | |
| #include "source/fuzz/fuzzer_pass_permute_phi_operands.h" | |
| #include "source/fuzz/fuzzer_pass_propagate_instructions_down.h" | |
| #include "source/fuzz/fuzzer_pass_propagate_instructions_up.h" | |
| #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" | |
| #include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" | |
| #include "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h" | |
| #include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h" | |
| #include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h" | |
| #include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h" | |
| #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" | |
| #include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" | |
| #include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h" | |
| #include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h" | |
| #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" | |
| #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h" | |
| #include "source/fuzz/fuzzer_pass_split_blocks.h" | |
| #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h" | |
| #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" | |
| #include "source/fuzz/fuzzer_pass_swap_functions.h" | |
| #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h" | |
| #include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h" | |
| #include "source/fuzz/fuzzer_pass_wrap_vector_synonym.h" | |
| #include "source/fuzz/pass_management/repeated_pass_manager.h" | |
| #include "source/fuzz/pass_management/repeated_pass_recommender_standard.h" | |
| #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" | |
| #include "source/fuzz/transformation_context.h" | |
| #include "source/opt/build_module.h" | |
| #include "source/spirv_fuzzer_options.h" | |
| #include "source/util/make_unique.h" | |
| namespace spvtools { | |
| namespace fuzz { | |
| Fuzzer::Fuzzer(std::unique_ptr<opt::IRContext> ir_context, | |
| std::unique_ptr<TransformationContext> transformation_context, | |
| std::unique_ptr<FuzzerContext> fuzzer_context, | |
| MessageConsumer consumer, | |
| const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers, | |
| bool enable_all_passes, | |
| RepeatedPassStrategy repeated_pass_strategy, | |
| bool validate_after_each_fuzzer_pass, | |
| spv_validator_options validator_options, | |
| bool ignore_inapplicable_transformations /* = true */) | |
| : consumer_(std::move(consumer)), | |
| enable_all_passes_(enable_all_passes), | |
| validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass), | |
| validator_options_(validator_options), | |
| num_repeated_passes_applied_(0), | |
| 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_(), | |
| ignore_inapplicable_transformations_( | |
| ignore_inapplicable_transformations) { | |
| 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<FuzzerPassObfuscateConstants>(90, &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassApplyIdSynonyms>(90, &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceIrrelevantIds>(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<FuzzerPassAddAccessChains>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddBitInstructionSynonyms>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddCompositeExtract>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddCompositeInserts>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddCompositeTypes>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddCopyMemory>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddDeadBlocks>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddDeadBreaks>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddDeadContinues>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddEquationInstructions>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddFunctionCalls>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddGlobalVariables>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddImageSampleUnusedComponents>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddLoads>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddLocalVariables>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddLoopPreheaders>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddLoopsToCreateIntConstantSynonyms>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddOpPhiSynonyms>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddParameters>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddRelaxedDecorations>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddStores>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddSynonyms>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassAddVectorShuffleInstructions>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassConstructComposites>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassCopyObjects>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassDonateModules>(&pass_instances_, | |
| donor_suppliers); | |
| MaybeAddRepeatedPass<FuzzerPassDuplicateRegionsWithSelections>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassExpandVectorReductions>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassFlattenConditionalBranches>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassInlineFunctions>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassInvertComparisonOperators>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassMakeVectorOperationsDynamic>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassMergeBlocks>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassMergeFunctionReturns>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassMutatePointers>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassOutlineFunctions>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassPermuteBlocks>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassPermuteFunctionParameters>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassPermuteInstructions>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsDown>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsUp>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassPushIdsThroughVariables>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceBranchesFromDeadBlocksWithExits>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceCopyMemoriesWithLoadsStores>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceCopyObjectsWithStoresLoads>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceLoadsStoresWithCopyMemories>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceParameterWithGlobal>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceLinearAlgebraInstructions>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceOpPhiIdsFromDeadPredecessors>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceOpSelectsWithConditionalBranches>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassReplaceParamsWithStruct>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassSplitBlocks>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassSwapBranchConditionalOperands>( | |
| &pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassWrapRegionsInSelections>(&pass_instances_); | |
| MaybeAddRepeatedPass<FuzzerPassWrapVectorSynonym>(&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<RepeatedPassRecommenderStandard>( | |
| &pass_instances_, fuzzer_context_.get()); | |
| repeated_pass_manager_ = RepeatedPassManager::Create( | |
| repeated_pass_strategy, fuzzer_context_.get(), &pass_instances_, | |
| repeated_pass_recommender_.get()); | |
| MaybeAddFinalPass<FuzzerPassAdjustBranchWeights>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassAdjustFunctionControls>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassAdjustLoopControls>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassAdjustMemoryOperandsMasks>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassAdjustSelectionControls>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassAddNoContractionDecorations>(&final_passes_); | |
| if (!fuzzer_context_->IsWgslCompatible()) { | |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/4214): | |
| // this is disabled temporarily due to some issues in the Tint compiler. | |
| // Enable it back when the issues are resolved. | |
| MaybeAddFinalPass<FuzzerPassInterchangeSignednessOfIntegerOperands>( | |
| &final_passes_); | |
| } | |
| MaybeAddFinalPass<FuzzerPassInterchangeZeroLikeConstants>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassPermuteFunctionVariables>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassPermutePhiOperands>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassSwapCommutableOperands>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassSwapFunctions>(&final_passes_); | |
| MaybeAddFinalPass<FuzzerPassToggleAccessChainInstruction>(&final_passes_); | |
| } | |
| Fuzzer::~Fuzzer() = default; | |
| template <typename FuzzerPassT, typename... Args> | |
| void Fuzzer::MaybeAddRepeatedPass(uint32_t percentage_chance_of_adding_pass, | |
| RepeatedPassInstances* pass_instances, | |
| Args&&... extra_args) { | |
| if (enable_all_passes_ || | |
| fuzzer_context_->ChoosePercentage(percentage_chance_of_adding_pass)) { | |
| pass_instances->SetPass(MakeUnique<FuzzerPassT>( | |
| ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(), | |
| &transformation_sequence_out_, ignore_inapplicable_transformations_, | |
| std::forward<Args>(extra_args)...)); | |
| } | |
| } | |
| template <typename FuzzerPassT, typename... Args> | |
| void Fuzzer::MaybeAddFinalPass(std::vector<std::unique_ptr<FuzzerPass>>* passes, | |
| Args&&... extra_args) { | |
| if (enable_all_passes_ || fuzzer_context_->ChooseEven()) { | |
| passes->push_back(MakeUnique<FuzzerPassT>( | |
| ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(), | |
| &transformation_sequence_out_, ignore_inapplicable_transformations_, | |
| std::forward<Args>(extra_args)...)); | |
| } | |
| } | |
| bool Fuzzer::ApplyPassAndCheckValidity(FuzzerPass* pass) const { | |
| pass->Apply(); | |
| return !validate_after_each_fuzzer_pass_ || | |
| fuzzerutil::IsValidAndWellFormed(ir_context_.get(), validator_options_, | |
| consumer_); | |
| } | |
| opt::IRContext* Fuzzer::GetIRContext() { return ir_context_.get(); } | |
| const protobufs::TransformationSequence& Fuzzer::GetTransformationSequence() | |
| const { | |
| return transformation_sequence_out_; | |
| } | |
| 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<uint32_t>(transformation_sequence_out_.transformation_size()); | |
| auto status = Status::kComplete; | |
| do { | |
| if (!ApplyPassAndCheckValidity( | |
| repeated_pass_manager_->ChoosePass(transformation_sequence_out_))) { | |
| status = Status::kFuzzerPassLedToInvalidModule; | |
| break; | |
| } | |
| // 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<uint32_t>( | |
| 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; | |
| } | |
| // Check that we've not got stuck (this can happen if the only available | |
| // fuzzer passes are not able to apply any transformations, or can only | |
| // apply very few transformations). | |
| 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; | |
| } | |
| } 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; | |
| } | |
| } | |
| } | |
| is_valid_ = status != Status::kFuzzerPassLedToInvalidModule; | |
| return {status, static_cast<uint32_t>( | |
| 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<uint32_t>( | |
| transformation_sequence_out_.transformation_size()); | |
| auto chance_of_continuing = static_cast<uint32_t>( | |
| 100.0 * | |
| (1.0 - (static_cast<double>(transformations_applied_so_far) / | |
| (2.0 * static_cast<double>( | |
| fuzzer_context_->GetTransformationLimit()))))); | |
| if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) { | |
| // We have probabilistically decided to stop. | |
| return false; | |
| } | |
| } | |
| // Continue fuzzing! | |
| num_repeated_passes_applied_++; | |
| return true; | |
| } | |
| } // namespace fuzz | |
| } // namespace spvtools |