|  | // 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 <cmath> | 
|  | #include <memory> | 
|  |  | 
|  | #include "source/fuzz/fuzzer_util.h" | 
|  | #include "source/fuzz/transformation_construct_composite.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<uint32_t> 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<std::vector<uint32_t>> 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. | 
|  | TransformationConstructComposite 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<std::vector<uint32_t>> | 
|  | 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<std::vector<uint32_t>>(); | 
|  |  | 
|  | // 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<std::vector<uint32_t>> | 
|  | FuzzerPassConstructComposites::TryConstructingMatrixComposite( | 
|  | const opt::analysis::Matrix& matrix_type, | 
|  | const TypeIdToInstructions& type_id_to_available_instructions) { | 
|  | auto result = MakeUnique<std::vector<uint32_t>>(); | 
|  |  | 
|  | // 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<std::vector<uint32_t>> | 
|  | FuzzerPassConstructComposites::TryConstructingStructComposite( | 
|  | const opt::analysis::Struct& struct_type, | 
|  | const TypeIdToInstructions& type_id_to_available_instructions) { | 
|  | auto result = MakeUnique<std::vector<uint32_t>>(); | 
|  | // 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<std::vector<uint32_t>> | 
|  | 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<uint32_t, uint32_t> 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<opt::Instruction*> instructions_to_use; | 
|  |  | 
|  | while (vector_slots_used < vector_type.element_count()) { | 
|  | std::vector<opt::Instruction*> 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<uint32_t>>(); | 
|  | std::vector<uint32_t> 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 |