| // 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, 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 FuzzerPassAddAccessChains::Apply() { | |
| ForEachInstructionWithInstructionDescriptor( | |
| [this](opt::Function* function, opt::BasicBlock* block, | |
| opt::BasicBlock::iterator inst_it, | |
| const protobufs::InstructionDescriptor& instruction_descriptor) | |
| -> void { | |
| assert( | |
| inst_it->opcode() == | |
| spv::Op(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( | |
| spv::Op::OpAccessChain, 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 spv::Op::OpConstantNull: | |
| case spv::Op::OpUndef: | |
| // 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() == spv::Op::OpTypePointer; | |
| }); | |
| // 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; | |
| // Each index accessing a non-struct composite will be clamped, thus | |
| // needing a pair of fresh ids | |
| std::vector<std::pair<uint32_t, uint32_t>> fresh_ids_for_clamping; | |
| 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 spv::Op::OpTypeArray: | |
| bound = fuzzerutil::GetArraySize(*subobject_type, GetIRContext()); | |
| break; | |
| case spv::Op::OpTypeMatrix: | |
| case spv::Op::OpTypeVector: | |
| bound = subobject_type->GetSingleWordInOperand(1); | |
| break; | |
| case spv::Op::OpTypeStruct: | |
| 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; | |
| } | |
| uint32_t index_value = | |
| GetFuzzerContext()->GetRandomIndexForAccessChain(bound); | |
| switch (subobject_type->opcode()) { | |
| case spv::Op::OpTypeArray: | |
| case spv::Op::OpTypeMatrix: | |
| case spv::Op::OpTypeVector: { | |
| // The index will be clamped | |
| bool is_signed = GetFuzzerContext()->ChooseEven(); | |
| // Make the constant ready for clamping. We need: | |
| // - an OpTypeBool to be present in the module | |
| // - an OpConstant with the same type as the index and value | |
| // the maximum value for an index | |
| // - a new pair of fresh ids for the clamping instructions | |
| FindOrCreateBoolType(); | |
| FindOrCreateIntegerConstant({bound - 1}, 32, is_signed, false); | |
| std::pair<uint32_t, uint32_t> fresh_pair_of_ids = { | |
| GetFuzzerContext()->GetFreshId(), | |
| GetFuzzerContext()->GetFreshId()}; | |
| fresh_ids_for_clamping.emplace_back(fresh_pair_of_ids); | |
| index_ids.push_back(FindOrCreateIntegerConstant( | |
| {index_value}, 32, is_signed, false)); | |
| subobject_type_id = subobject_type->GetSingleWordInOperand(0); | |
| } break; | |
| case spv::Op::OpTypeStruct: | |
| index_ids.push_back(FindOrCreateIntegerConstant( | |
| {index_value}, 32, GetFuzzerContext()->ChooseEven(), false)); | |
| 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<spv::StorageClass>( | |
| pointer_type->GetSingleWordInOperand(0))); | |
| // Apply the transformation to add an access chain. | |
| ApplyTransformation(TransformationAccessChain( | |
| GetFuzzerContext()->GetFreshId(), chosen_pointer->result_id(), | |
| index_ids, instruction_descriptor, fresh_ids_for_clamping)); | |
| }); | |
| } | |
| } // namespace fuzz | |
| } // namespace spvtools |