SPIRV-Tools/source/fuzz/fuzzer_pass_split_blocks.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

104 lines
4.1 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_pass_split_blocks.h"
#include <vector>
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_split_block.h"
namespace spvtools {
namespace fuzz {
FuzzerPassSplitBlocks::FuzzerPassSplitBlocks(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations,
bool ignore_inapplicable_transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations, ignore_inapplicable_transformations) {}
void FuzzerPassSplitBlocks::Apply() {
// Gather up pointers to all the blocks in the module. We are then able to
// iterate over these pointers and split the blocks to which they point;
// we cannot safely split blocks while we iterate through the module.
std::vector<opt::BasicBlock*> blocks;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
blocks.push_back(&block);
}
}
// Now go through all the block pointers that were gathered.
for (auto& block : blocks) {
// Probabilistically decide whether to try to split this block.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfSplittingBlock())) {
// We are not going to try to split this block.
continue;
}
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2964): consider
// taking a simpler approach to identifying the instruction before which
// to split a block.
// We are going to try to split this block. We now need to choose where
// to split it. We describe the instruction before which we would like to
// split a block via an InstructionDescriptor, details of which are
// commented in the protobufs definition file.
std::vector<protobufs::InstructionDescriptor> instruction_descriptors;
// The initial base instruction is the block label.
uint32_t base = block->id();
// Counts the number of times we have seen each opcode since we reset the
// base instruction.
std::map<SpvOp, uint32_t> skip_count;
// Consider every instruction in the block. The label is excluded: it is
// only necessary to consider it as a base in case the first instruction
// in the block does not have a result id.
for (auto& inst : *block) {
if (inst.HasResultId()) {
// In the case that the instruction has a result id, we use the
// instruction as its own base, and clear the skip counts we have
// collected.
base = inst.result_id();
skip_count.clear();
}
const SpvOp opcode = inst.opcode();
instruction_descriptors.emplace_back(MakeInstructionDescriptor(
base, opcode, skip_count.count(opcode) ? skip_count.at(opcode) : 0));
if (!inst.HasResultId()) {
skip_count[opcode] =
skip_count.count(opcode) ? skip_count.at(opcode) + 1 : 1;
}
}
// Having identified all the places we might be able to split the block,
// we choose one of them.
auto transformation = TransformationSplitBlock(
instruction_descriptors[GetFuzzerContext()->RandomIndex(
instruction_descriptors)],
GetFuzzerContext()->GetFreshId());
// If the position we have chosen turns out to be a valid place to split
// the block, we apply the split. Otherwise the block just doesn't get
// split.
MaybeApplyTransformation(transformation);
}
}
} // namespace fuzz
} // namespace spvtools