SPIRV-Tools/source/fuzz/fuzzer.cpp
Alastair Donaldson 9c4481419e
spirv-fuzz: Allow inapplicable transformations to be ignored (#4407)
spirv-fuzz features transformations that should be applicable by
construction. Assertions are used to detect when such transformations
turn out to be inapplicable. Failures of such assertions indicate bugs
in the fuzzer. However, when using the fuzzer at scale (e.g. in
ClusterFuzz) reports of these assertion failures create noise, and
cause the fuzzer to exit early. This change adds an option whereby
inapplicable transformations can be ignored. This reduces noise and
allows fuzzing to continue even when a transformation that should be
applicable but is not has been erroneously created.
2021-07-28 22:59:37 +01:00

395 lines
19 KiB
C++

// 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)
: 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