// 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_construct_composites.h" #include #include #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/transformation_composite_construct.h" #include "source/util/make_unique.h" namespace spvtools { namespace fuzz { FuzzerPassConstructComposites::FuzzerPassConstructComposites( opt::IRContext* ir_context, FactManager* fact_manager, FuzzerContext* fuzzer_context, protobufs::TransformationSequence* transformations) : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default; void FuzzerPassConstructComposites::Apply() { // Gather up the ids of all composite types. std::vector composite_type_ids; for (auto& inst : GetIRContext()->types_values()) { if (fuzzerutil::IsCompositeType( GetIRContext()->get_type_mgr()->GetType(inst.result_id()))) { composite_type_ids.push_back(inst.result_id()); } } MaybeAddTransformationBeforeEachInstruction( [this, &composite_type_ids]( const opt::Function& function, opt::BasicBlock* block, opt::BasicBlock::iterator inst_it, const protobufs::InstructionDescriptor& instruction_descriptor) -> void { // Check whether it is legitimate to insert a composite construction // before the instruction. if (!fuzzerutil::CanInsertOpcodeBeforeInstruction( SpvOpCompositeConstruct, inst_it)) { return; } // Randomly decide whether to try inserting an object copy here. if (!GetFuzzerContext()->ChoosePercentage( GetFuzzerContext()->GetChanceOfConstructingComposite())) { return; } // For each instruction that is available at this program point (i.e. an // instruction that is global or whose definition strictly dominates the // program point) and suitable for making a synonym of, associate it // with the id of its result type. TypeIdToInstructions type_id_to_available_instructions; for (auto instruction : FindAvailableInstructions( function, block, inst_it, fuzzerutil::CanMakeSynonymOf)) { RecordAvailableInstruction(instruction, &type_id_to_available_instructions); } // At this point, |composite_type_ids| captures all the composite types // we could try to create, while |type_id_to_available_instructions| // captures all the available result ids we might use, organized by // type. // Now we try to find a composite that we can construct. We might not // manage, if there is a paucity of available ingredients in the module // (e.g. if our only available composite was a boolean vector and we had // no instructions generating boolean result types available). // // If we succeed, |chosen_composite_type| will end up being non-zero, // and |constructor_arguments| will end up giving us result ids suitable // for constructing a composite of that type. Otherwise these variables // will remain 0 and null respectively. uint32_t chosen_composite_type = 0; std::unique_ptr> constructor_arguments = nullptr; // Initially, all composite type ids are available for us to try. Keep // trying until we run out of options. auto composites_to_try_constructing = composite_type_ids; while (!composites_to_try_constructing.empty()) { // Remove a composite type from the composite types left for us to // try. auto index = GetFuzzerContext()->RandomIndex(composites_to_try_constructing); auto next_composite_to_try_constructing = composites_to_try_constructing[index]; composites_to_try_constructing.erase( composites_to_try_constructing.begin() + index); // Now try to construct a composite of this type, using an appropriate // helper method depending on the kind of composite type. auto composite_type = GetIRContext()->get_type_mgr()->GetType( next_composite_to_try_constructing); if (auto array_type = composite_type->AsArray()) { constructor_arguments = TryConstructingArrayComposite( *array_type, type_id_to_available_instructions); } else if (auto matrix_type = composite_type->AsMatrix()) { constructor_arguments = TryConstructingMatrixComposite( *matrix_type, type_id_to_available_instructions); } else if (auto struct_type = composite_type->AsStruct()) { constructor_arguments = TryConstructingStructComposite( *struct_type, type_id_to_available_instructions); } else { auto vector_type = composite_type->AsVector(); assert(vector_type && "The space of possible composite types should be covered by " "the above cases."); constructor_arguments = TryConstructingVectorComposite( *vector_type, type_id_to_available_instructions); } if (constructor_arguments != nullptr) { // We succeeded! Note the composite type we finally settled on, and // exit from the loop. chosen_composite_type = next_composite_to_try_constructing; break; } } if (!chosen_composite_type) { // We did not manage to make a composite; return 0 to indicate that no // instructions were added. assert(constructor_arguments == nullptr); return; } assert(constructor_arguments != nullptr); // Make and apply a transformation. TransformationCompositeConstruct transformation( chosen_composite_type, *constructor_arguments, instruction_descriptor, GetFuzzerContext()->GetFreshId()); assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) && "This transformation should be applicable by construction."); transformation.Apply(GetIRContext(), GetFactManager()); *GetTransformations()->add_transformation() = transformation.ToMessage(); // Indicate that one instruction was added. }); } void FuzzerPassConstructComposites::RecordAvailableInstruction( opt::Instruction* inst, TypeIdToInstructions* type_id_to_available_instructions) { if (type_id_to_available_instructions->count(inst->type_id()) == 0) { (*type_id_to_available_instructions)[inst->type_id()] = {}; } type_id_to_available_instructions->at(inst->type_id()).push_back(inst); } std::unique_ptr> FuzzerPassConstructComposites::TryConstructingArrayComposite( const opt::analysis::Array& array_type, const TypeIdToInstructions& type_id_to_available_instructions) { // At present we assume arrays have a constant size. assert(array_type.length_info().words.size() == 2); assert(array_type.length_info().words[0] == opt::analysis::Array::LengthInfo::kConstant); auto result = MakeUnique>(); // Get the element type for the array. auto element_type_id = GetIRContext()->get_type_mgr()->GetId(array_type.element_type()); // Get all instructions at our disposal that compute something of this element // type. auto available_instructions = type_id_to_available_instructions.find(element_type_id); if (available_instructions == type_id_to_available_instructions.cend()) { // If there are not any instructions available that compute the element type // of the array then we are not in a position to construct a composite with // this array type. return nullptr; } for (uint32_t index = 0; index < array_type.length_info().words[1]; index++) { result->push_back(available_instructions ->second[GetFuzzerContext()->RandomIndex( available_instructions->second)] ->result_id()); } return result; } std::unique_ptr> FuzzerPassConstructComposites::TryConstructingMatrixComposite( const opt::analysis::Matrix& matrix_type, const TypeIdToInstructions& type_id_to_available_instructions) { auto result = MakeUnique>(); // Get the element type for the matrix. auto element_type_id = GetIRContext()->get_type_mgr()->GetId(matrix_type.element_type()); // Get all instructions at our disposal that compute something of this element // type. auto available_instructions = type_id_to_available_instructions.find(element_type_id); if (available_instructions == type_id_to_available_instructions.cend()) { // If there are not any instructions available that compute the element type // of the matrix then we are not in a position to construct a composite with // this matrix type. return nullptr; } for (uint32_t index = 0; index < matrix_type.element_count(); index++) { result->push_back(available_instructions ->second[GetFuzzerContext()->RandomIndex( available_instructions->second)] ->result_id()); } return result; } std::unique_ptr> FuzzerPassConstructComposites::TryConstructingStructComposite( const opt::analysis::Struct& struct_type, const TypeIdToInstructions& type_id_to_available_instructions) { auto result = MakeUnique>(); // Consider the type of each field of the struct. for (auto element_type : struct_type.element_types()) { auto element_type_id = GetIRContext()->get_type_mgr()->GetId(element_type); // Find the instructions at our disposal that compute something of the field // type. auto available_instructions = type_id_to_available_instructions.find(element_type_id); if (available_instructions == type_id_to_available_instructions.cend()) { // If there are no such instructions, we cannot construct a composite of // this struct type. return nullptr; } result->push_back(available_instructions ->second[GetFuzzerContext()->RandomIndex( available_instructions->second)] ->result_id()); } return result; } std::unique_ptr> FuzzerPassConstructComposites::TryConstructingVectorComposite( const opt::analysis::Vector& vector_type, const TypeIdToInstructions& type_id_to_available_instructions) { // Get details of the type underlying the vector, and the width of the vector, // for convenience. auto element_type = vector_type.element_type(); auto element_count = vector_type.element_count(); // Collect a mapping, from type id to width, for scalar/vector types that are // smaller in width than |vector_type|, but that have the same underlying // type. For example, if |vector_type| is vec4, the mapping will be: // { float -> 1, vec2 -> 2, vec3 -> 3 } // The mapping will have missing entries if some of these types do not exist. std::map smaller_vector_type_id_to_width; // Add the underlying type. This id must exist, in order for |vector_type| to // exist. auto scalar_type_id = GetIRContext()->get_type_mgr()->GetId(element_type); smaller_vector_type_id_to_width[scalar_type_id] = 1; // Now add every vector type with width at least 2, and less than the width of // |vector_type|. for (uint32_t width = 2; width < element_count; width++) { opt::analysis::Vector smaller_vector_type(vector_type.element_type(), width); auto smaller_vector_type_id = GetIRContext()->get_type_mgr()->GetId(&smaller_vector_type); // We might find that there is no declared type of this smaller width. // For example, a module can declare vec4 without having declared vec2 or // vec3. if (smaller_vector_type_id) { smaller_vector_type_id_to_width[smaller_vector_type_id] = width; } } // Now we know the types that are available to us, we set about populating a // vector of the right length. We do this by deciding, with no order in mind, // which instructions we will use to populate the vector, and subsequently // randomly choosing an order. This is to avoid biasing construction of // vectors with smaller vectors to the left and scalars to the right. That is // a concern because, e.g. in the case of populating a vec4, if we populate // the constructor instructions left-to-right, we can always choose a vec3 to // construct the first three elements, but can only choose a vec3 to construct // the last three elements if we chose a float to construct the first element // (otherwise there will not be space left for a vec3). uint32_t vector_slots_used = 0; // The instructions we will use to construct the vector, in no particular // order at this stage. std::vector instructions_to_use; while (vector_slots_used < vector_type.element_count()) { std::vector instructions_to_choose_from; for (auto& entry : smaller_vector_type_id_to_width) { if (entry.second > std::min(vector_type.element_count() - 1, vector_type.element_count() - vector_slots_used)) { continue; } auto available_instructions = type_id_to_available_instructions.find(entry.first); if (available_instructions == type_id_to_available_instructions.cend()) { continue; } instructions_to_choose_from.insert(instructions_to_choose_from.end(), available_instructions->second.begin(), available_instructions->second.end()); } if (instructions_to_choose_from.empty()) { // We may get unlucky and find that there are not any instructions to // choose from. In this case we give up constructing a composite of this // vector type. It might be that we could construct the composite in // another manner, so we could opt to retry a few times here, but it is // simpler to just give up on the basis that this will not happen // frequently. return nullptr; } auto instruction_to_use = instructions_to_choose_from[GetFuzzerContext()->RandomIndex( instructions_to_choose_from)]; instructions_to_use.push_back(instruction_to_use); auto chosen_type = GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id()); if (chosen_type->AsVector()) { assert(chosen_type->AsVector()->element_type() == element_type); assert(chosen_type->AsVector()->element_count() < element_count); assert(chosen_type->AsVector()->element_count() <= element_count - vector_slots_used); vector_slots_used += chosen_type->AsVector()->element_count(); } else { assert(chosen_type == element_type); vector_slots_used += 1; } } assert(vector_slots_used == vector_type.element_count()); auto result = MakeUnique>(); std::vector operands; while (!instructions_to_use.empty()) { auto index = GetFuzzerContext()->RandomIndex(instructions_to_use); result->push_back(instructions_to_use[index]->result_id()); instructions_to_use.erase(instructions_to_use.begin() + index); } assert(result->size() > 1); return result; } } // namespace fuzz } // namespace spvtools