Skip to content

Commit

Permalink
spirv-fuzz: Fuzzer pass that adds access chains (#3182)
Browse files Browse the repository at this point in the history
This change adds a fuzzer pass that sprinkles access chain
instructions into a module at random. This allows other passes to
have a richer set of pointers available to them, in particular the
passes that add loads and stores.
  • Loading branch information
afd authored Feb 11, 2020
1 parent 77fb303 commit 6c218ec
Show file tree
Hide file tree
Showing 16 changed files with 1,102 additions and 46 deletions.
4 changes: 4 additions & 0 deletions source/fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer.h
fuzzer_context.h
fuzzer_pass.h
fuzzer_pass_add_access_chains.h
fuzzer_pass_add_composite_types.h
fuzzer_pass_add_dead_blocks.h
fuzzer_pass_add_dead_breaks.h
Expand Down Expand Up @@ -71,6 +72,7 @@ if(SPIRV_BUILD_FUZZER)
replayer.h
shrinker.h
transformation.h
transformation_access_chain.h
transformation_add_constant_boolean.h
transformation_add_constant_composite.h
transformation_add_constant_scalar.h
Expand Down Expand Up @@ -119,6 +121,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer.cpp
fuzzer_context.cpp
fuzzer_pass.cpp
fuzzer_pass_add_access_chains.cpp
fuzzer_pass_add_composite_types.cpp
fuzzer_pass_add_dead_blocks.cpp
fuzzer_pass_add_dead_breaks.cpp
Expand Down Expand Up @@ -152,6 +155,7 @@ if(SPIRV_BUILD_FUZZER)
replayer.cpp
shrinker.cpp
transformation.cpp
transformation_access_chain.cpp
transformation_add_constant_boolean.cpp
transformation_add_constant_composite.cpp
transformation_add_constant_scalar.cpp
Expand Down
4 changes: 4 additions & 0 deletions source/fuzz/fuzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "fuzzer_pass_adjust_memory_operands_masks.h"
#include "source/fuzz/fact_manager.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_pass_add_access_chains.h"
#include "source/fuzz/fuzzer_pass_add_composite_types.h"
#include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
Expand Down Expand Up @@ -184,6 +185,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
// Apply some semantics-preserving passes.
std::vector<std::unique_ptr<FuzzerPass>> passes;
while (passes.empty()) {
MaybeAddPass<FuzzerPassAddAccessChains>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddCompositeTypes>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
Expand Down
15 changes: 11 additions & 4 deletions source/fuzz/fuzzer_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace {
// Default <minimum, maximum> pairs of probabilities for applying various
// transformations. All values are percentages. Keep them in alphabetical order.

const std::pair<uint32_t, uint32_t> kChanceOfAddingAccessChain = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90};
Expand Down Expand Up @@ -50,6 +51,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfChoosingStructTypeVsArrayType = {
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
{50, 95};
const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
Expand Down Expand Up @@ -81,8 +84,14 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
uint32_t min_fresh_id)
: random_generator_(random_generator),
next_fresh_id_(min_fresh_id),
max_loop_control_partial_count_(kDefaultMaxLoopControlPartialCount),
max_loop_control_peel_count_(kDefaultMaxLoopControlPeelCount),
max_loop_limit_(kDefaultMaxLoopLimit),
max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit),
go_deeper_in_constant_obfuscation_(
kDefaultGoDeeperInConstantObfuscation) {
chance_of_adding_access_chain_ =
ChooseBetweenMinAndMax(kChanceOfAddingAccessChain);
chance_of_adding_another_struct_field_ =
ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
chance_of_adding_array_or_struct_type_ =
Expand Down Expand Up @@ -122,6 +131,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
chance_of_donating_additional_module_ =
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
chance_of_going_deeper_when_making_access_chain_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
chance_of_making_donor_livesafe_ =
ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe);
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
Expand All @@ -134,10 +145,6 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_replacing_id_with_synonym_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount;
max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount;
max_loop_limit_ = kDefaultMaxLoopLimit;
max_new_array_size_limit_ = kDefaultMaxNewArraySizeLimit;
}

FuzzerContext::~FuzzerContext() = default;
Expand Down
15 changes: 13 additions & 2 deletions source/fuzz/fuzzer_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class FuzzerContext {

// Probabilities associated with applying various transformations.
// Keep them in alphabetical order.
uint32_t GetChanceOfAddingAccessChain() {
return chance_of_adding_access_chain_;
}
uint32_t GetChanceOfAddingAnotherStructField() {
return chance_of_adding_another_struct_field_;
}
Expand Down Expand Up @@ -119,6 +122,9 @@ class FuzzerContext {
uint32_t GetChanceOfDonatingAdditionalModule() {
return chance_of_donating_additional_module_;
}
uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
return chance_of_going_deeper_when_making_access_chain_;
}
uint32_t ChanceOfMakingDonorLivesafe() {
return chance_of_making_donor_livesafe_;
}
Expand Down Expand Up @@ -148,8 +154,11 @@ class FuzzerContext {
return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
}

// Functions to control how deeply to recurse.
// Keep them in alphabetical order.
// Other functions to control transformations. Keep them in alphabetical
// order.
uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) {
return random_generator_->RandomUint32(composite_size_bound);
}
bool GoDeeperInConstantObfuscation(uint32_t depth) {
return go_deeper_in_constant_obfuscation_(depth, random_generator_);
}
Expand All @@ -162,6 +171,7 @@ class FuzzerContext {

// Probabilities associated with applying various transformations.
// Keep them in alphabetical order.
uint32_t chance_of_adding_access_chain_;
uint32_t chance_of_adding_another_struct_field_;
uint32_t chance_of_adding_array_or_struct_type_;
uint32_t chance_of_adding_dead_block_;
Expand All @@ -183,6 +193,7 @@ class FuzzerContext {
uint32_t chance_of_constructing_composite_;
uint32_t chance_of_copying_object_;
uint32_t chance_of_donating_additional_module_;
uint32_t chance_of_going_deeper_when_making_access_chain_;
uint32_t chance_of_making_donor_livesafe_;
uint32_t chance_of_merging_blocks_;
uint32_t chance_of_moving_block_down_;
Expand Down
20 changes: 13 additions & 7 deletions source/fuzz/fuzzer_pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,21 +219,27 @@ uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
return result;
}

uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType(
bool is_signed, SpvStorageClass storage_class) {
auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed);
opt::analysis::Pointer pointer_type(
GetIRContext()->get_type_mgr()->GetType(uint32_type_id), storage_class);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&pointer_type);
uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
SpvStorageClass storage_class) {
// We do not use the type manager here, due to problems related to isomorphic
// but distinct structs not being regarded as different.
auto existing_id = fuzzerutil::MaybeGetPointerType(
GetIRContext(), base_type_id, storage_class);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypePointer(result, storage_class, uint32_type_id));
TransformationAddTypePointer(result, storage_class, base_type_id));
return result;
}

uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType(
bool is_signed, SpvStorageClass storage_class) {
return FindOrCreatePointerType(FindOrCreate32BitIntegerType(is_signed),
storage_class);
}

uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word,
bool is_signed) {
auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed);
Expand Down
6 changes: 6 additions & 0 deletions source/fuzz/fuzzer_pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ class FuzzerPass {
// type itself do not exist, transformations are applied to add them.
uint32_t FindOrCreateMatrixType(uint32_t column_count, uint32_t row_count);

// Returns the id of a pointer type with base type |base_type_id| (which must
// already exist) and storage class |storage_class|. A transformation is
// applied to add the pointer if it does not already exist.
uint32_t FindOrCreatePointerType(uint32_t base_type_id,
SpvStorageClass storage_class);

// Returns the id of an OpTypePointer instruction, with a 32-bit integer base
// type of signedness specified by |is_signed|. If the pointer type or
// required integer base type do not exist, transformations are applied to add
Expand Down
169 changes: 169 additions & 0 deletions source/fuzz/fuzzer_pass_add_access_chains.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright (c) 2020 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_add_access_chains.h"

#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_access_chain.h"

namespace spvtools {
namespace fuzz {

FuzzerPassAddAccessChains::FuzzerPassAddAccessChains(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}

FuzzerPassAddAccessChains::~FuzzerPassAddAccessChains() = default;

void FuzzerPassAddAccessChains::Apply() {
MaybeAddTransformationBeforeEachInstruction(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
assert(inst_it->opcode() ==
instruction_descriptor.target_instruction_opcode() &&
"The opcode of the instruction we might insert before must be "
"the same as the opcode in the descriptor for the instruction");

// Check whether it is legitimate to insert an access chain
// instruction before this instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAccessChain,
inst_it)) {
return;
}

// Randomly decide whether to try inserting a load here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingAccessChain())) {
return;
}

// Get all of the pointers that are currently in scope, excluding
// explicitly null and undefined pointers.
std::vector<opt::Instruction*> relevant_pointer_instructions =
FindAvailableInstructions(
function, block, inst_it,
[](opt::IRContext* context,
opt::Instruction* instruction) -> bool {
if (!instruction->result_id() || !instruction->type_id()) {
// A pointer needs both a result and type id.
return false;
}
switch (instruction->opcode()) {
case SpvOpConstantNull:
case SpvOpUndef:
// Do not allow making an access chain from a null or
// undefined pointer. (We can eliminate these cases
// before actually checking that the instruction is a
// pointer.)
return false;
default:
break;
}
// If the instruction has pointer type, we can legitimately
// make an access chain from it.
return context->get_def_use_mgr()
->GetDef(instruction->type_id())
->opcode() == SpvOpTypePointer;
});

// At this point, |relevant_instructions| contains all the pointers
// we might think of making an access chain from.
if (relevant_pointer_instructions.empty()) {
return;
}

auto chosen_pointer =
relevant_pointer_instructions[GetFuzzerContext()->RandomIndex(
relevant_pointer_instructions)];
std::vector<uint32_t> index_ids;
auto pointer_type = GetIRContext()->get_def_use_mgr()->GetDef(
chosen_pointer->type_id());
uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
while (true) {
auto subobject_type =
GetIRContext()->get_def_use_mgr()->GetDef(subobject_type_id);
if (!spvOpcodeIsComposite(subobject_type->opcode())) {
break;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfGoingDeeperWhenMakingAccessChain())) {
break;
}
uint32_t bound;
switch (subobject_type->opcode()) {
case SpvOpTypeArray:
bound = fuzzerutil::GetArraySize(*subobject_type, GetIRContext());
break;
case SpvOpTypeMatrix:
case SpvOpTypeVector:
bound = subobject_type->GetSingleWordInOperand(1);
break;
case SpvOpTypeStruct:
bound = fuzzerutil::GetNumberOfStructMembers(*subobject_type);
break;
default:
assert(false && "Not a composite type opcode.");
// Set the bound to a value in order to keep release compilers
// happy.
bound = 0;
break;
}
if (bound == 0) {
// It is possible for a composite type to legitimately have zero
// sub-components, at least in the case of a struct, which
// can have no fields.
break;
}

// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We
// could allow non-constant indices when looking up non-structs,
// using clamping to ensure they are in-bounds.
uint32_t index_value =
GetFuzzerContext()->GetRandomIndexForAccessChain(bound);
index_ids.push_back(FindOrCreate32BitIntegerConstant(
index_value, GetFuzzerContext()->ChooseEven()));
switch (subobject_type->opcode()) {
case SpvOpTypeArray:
case SpvOpTypeMatrix:
case SpvOpTypeVector:
subobject_type_id = subobject_type->GetSingleWordInOperand(0);
break;
case SpvOpTypeStruct:
subobject_type_id =
subobject_type->GetSingleWordInOperand(index_value);
break;
default:
assert(false && "Not a composite type opcode.");
}
}
// The transformation we are about to create will only apply if a
// pointer suitable for the access chain's result type exists, so we
// create one if it does not.
FindOrCreatePointerType(subobject_type_id,
static_cast<SpvStorageClass>(
pointer_type->GetSingleWordInOperand(0)));
// Apply the transformation to add an access chain.
ApplyTransformation(TransformationAccessChain(
GetFuzzerContext()->GetFreshId(), chosen_pointer->result_id(),
index_ids, instruction_descriptor));
});
}

} // namespace fuzz
} // namespace spvtools
Loading

0 comments on commit 6c218ec

Please sign in to comment.