• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "source/fuzz/fuzzer_pass_construct_composites.h"
16 
17 #include <memory>
18 
19 #include "source/fuzz/fuzzer_util.h"
20 #include "source/fuzz/transformation_composite_construct.h"
21 
22 namespace spvtools {
23 namespace fuzz {
24 
FuzzerPassConstructComposites(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations)25 FuzzerPassConstructComposites::FuzzerPassConstructComposites(
26     opt::IRContext* ir_context, TransformationContext* transformation_context,
27     FuzzerContext* fuzzer_context,
28     protobufs::TransformationSequence* transformations)
29     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
30                  transformations) {}
31 
32 FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
33 
Apply()34 void FuzzerPassConstructComposites::Apply() {
35   // Gather up the ids of all composite types, but skip block-/buffer
36   // block-decorated struct types.
37   std::vector<uint32_t> composite_type_ids;
38   for (auto& inst : GetIRContext()->types_values()) {
39     if (fuzzerutil::IsCompositeType(
40             GetIRContext()->get_type_mgr()->GetType(inst.result_id())) &&
41         !fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
42                                                      inst.result_id())) {
43       composite_type_ids.push_back(inst.result_id());
44     }
45   }
46 
47   ForEachInstructionWithInstructionDescriptor(
48       [this, &composite_type_ids](
49           opt::Function* function, opt::BasicBlock* block,
50           opt::BasicBlock::iterator inst_it,
51           const protobufs::InstructionDescriptor& instruction_descriptor)
52           -> void {
53         // Check whether it is legitimate to insert a composite construction
54         // before the instruction.
55         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
56                 SpvOpCompositeConstruct, inst_it)) {
57           return;
58         }
59 
60         // Randomly decide whether to try inserting an object copy here.
61         if (!GetFuzzerContext()->ChoosePercentage(
62                 GetFuzzerContext()->GetChanceOfConstructingComposite())) {
63           return;
64         }
65 
66         // For each instruction that is available at this program point (i.e. an
67         // instruction that is global or whose definition strictly dominates the
68         // program point) and suitable for making a synonym of, associate it
69         // with the id of its result type.
70         TypeIdToInstructions type_id_to_available_instructions;
71         auto available_instructions = FindAvailableInstructions(
72             function, block, inst_it,
73             [this](opt::IRContext* ir_context, opt::Instruction* inst) {
74               if (!inst->result_id() || !inst->type_id()) {
75                 return false;
76               }
77 
78               // If the id is irrelevant, we can use it since it will not
79               // participate in DataSynonym fact. Otherwise, we should be able
80               // to produce a synonym out of the id.
81               return GetTransformationContext()
82                          ->GetFactManager()
83                          ->IdIsIrrelevant(inst->result_id()) ||
84                      fuzzerutil::CanMakeSynonymOf(
85                          ir_context, *GetTransformationContext(), inst);
86             });
87         for (auto instruction : available_instructions) {
88           RecordAvailableInstruction(instruction,
89                                      &type_id_to_available_instructions);
90         }
91 
92         // At this point, |composite_type_ids| captures all the composite types
93         // we could try to create, while |type_id_to_available_instructions|
94         // captures all the available result ids we might use, organized by
95         // type.
96 
97         // Now we try to find a composite that we can construct.  We might not
98         // manage, if there is a paucity of available ingredients in the module
99         // (e.g. if our only available composite was a boolean vector and we had
100         // no instructions generating boolean result types available).
101         //
102         // If we succeed, |chosen_composite_type| will end up being non-zero,
103         // and |constructor_arguments| will end up giving us result ids suitable
104         // for constructing a composite of that type.  Otherwise these variables
105         // will remain 0 and null respectively.
106         uint32_t chosen_composite_type = 0;
107         std::vector<uint32_t> constructor_arguments;
108 
109         // Initially, all composite type ids are available for us to try.  Keep
110         // trying until we run out of options.
111         auto composites_to_try_constructing = composite_type_ids;
112         while (!composites_to_try_constructing.empty()) {
113           // Remove a composite type from the composite types left for us to
114           // try.
115           auto next_composite_to_try_constructing =
116               GetFuzzerContext()->RemoveAtRandomIndex(
117                   &composites_to_try_constructing);
118 
119           // Now try to construct a composite of this type, using an appropriate
120           // helper method depending on the kind of composite type.
121           auto composite_type_inst = GetIRContext()->get_def_use_mgr()->GetDef(
122               next_composite_to_try_constructing);
123           switch (composite_type_inst->opcode()) {
124             case SpvOpTypeArray:
125               constructor_arguments = FindComponentsToConstructArray(
126                   *composite_type_inst, type_id_to_available_instructions);
127               break;
128             case SpvOpTypeMatrix:
129               constructor_arguments = FindComponentsToConstructMatrix(
130                   *composite_type_inst, type_id_to_available_instructions);
131               break;
132             case SpvOpTypeStruct:
133               constructor_arguments = FindComponentsToConstructStruct(
134                   *composite_type_inst, type_id_to_available_instructions);
135               break;
136             case SpvOpTypeVector:
137               constructor_arguments = FindComponentsToConstructVector(
138                   *composite_type_inst, type_id_to_available_instructions);
139               break;
140             default:
141               assert(false &&
142                      "The space of possible composite types should be covered "
143                      "by the above cases.");
144               break;
145           }
146           if (!constructor_arguments.empty()) {
147             // We succeeded!  Note the composite type we finally settled on, and
148             // exit from the loop.
149             chosen_composite_type = next_composite_to_try_constructing;
150             break;
151           }
152         }
153 
154         if (!chosen_composite_type) {
155           // We did not manage to make a composite; return 0 to indicate that no
156           // instructions were added.
157           assert(constructor_arguments.empty());
158           return;
159         }
160         assert(!constructor_arguments.empty());
161 
162         // Make and apply a transformation.
163         ApplyTransformation(TransformationCompositeConstruct(
164             chosen_composite_type, constructor_arguments,
165             instruction_descriptor, GetFuzzerContext()->GetFreshId()));
166       });
167 }
168 
RecordAvailableInstruction(opt::Instruction * inst,TypeIdToInstructions * type_id_to_available_instructions)169 void FuzzerPassConstructComposites::RecordAvailableInstruction(
170     opt::Instruction* inst,
171     TypeIdToInstructions* type_id_to_available_instructions) {
172   if (type_id_to_available_instructions->count(inst->type_id()) == 0) {
173     (*type_id_to_available_instructions)[inst->type_id()] = {};
174   }
175   type_id_to_available_instructions->at(inst->type_id()).push_back(inst);
176 }
177 
178 std::vector<uint32_t>
FindComponentsToConstructArray(const opt::Instruction & array_type_instruction,const TypeIdToInstructions & type_id_to_available_instructions)179 FuzzerPassConstructComposites::FindComponentsToConstructArray(
180     const opt::Instruction& array_type_instruction,
181     const TypeIdToInstructions& type_id_to_available_instructions) {
182   assert(array_type_instruction.opcode() == SpvOpTypeArray &&
183          "Precondition: instruction must be an array type.");
184 
185   // Get the element type for the array.
186   auto element_type_id = array_type_instruction.GetSingleWordInOperand(0);
187 
188   // Get all instructions at our disposal that compute something of this element
189   // type.
190   auto available_instructions =
191       type_id_to_available_instructions.find(element_type_id);
192 
193   if (available_instructions == type_id_to_available_instructions.cend()) {
194     // If there are not any instructions available that compute the element type
195     // of the array then we are not in a position to construct a composite with
196     // this array type.
197     return {};
198   }
199 
200   uint32_t array_length =
201       GetIRContext()
202           ->get_def_use_mgr()
203           ->GetDef(array_type_instruction.GetSingleWordInOperand(1))
204           ->GetSingleWordInOperand(0);
205 
206   std::vector<uint32_t> result;
207   for (uint32_t index = 0; index < array_length; index++) {
208     result.push_back(available_instructions
209                          ->second[GetFuzzerContext()->RandomIndex(
210                              available_instructions->second)]
211                          ->result_id());
212   }
213   return result;
214 }
215 
216 std::vector<uint32_t>
FindComponentsToConstructMatrix(const opt::Instruction & matrix_type_instruction,const TypeIdToInstructions & type_id_to_available_instructions)217 FuzzerPassConstructComposites::FindComponentsToConstructMatrix(
218     const opt::Instruction& matrix_type_instruction,
219     const TypeIdToInstructions& type_id_to_available_instructions) {
220   assert(matrix_type_instruction.opcode() == SpvOpTypeMatrix &&
221          "Precondition: instruction must be a matrix type.");
222 
223   // Get the element type for the matrix.
224   auto element_type_id = matrix_type_instruction.GetSingleWordInOperand(0);
225 
226   // Get all instructions at our disposal that compute something of this element
227   // type.
228   auto available_instructions =
229       type_id_to_available_instructions.find(element_type_id);
230 
231   if (available_instructions == type_id_to_available_instructions.cend()) {
232     // If there are not any instructions available that compute the element type
233     // of the matrix then we are not in a position to construct a composite with
234     // this matrix type.
235     return {};
236   }
237   std::vector<uint32_t> result;
238   for (uint32_t index = 0;
239        index < matrix_type_instruction.GetSingleWordInOperand(1); index++) {
240     result.push_back(available_instructions
241                          ->second[GetFuzzerContext()->RandomIndex(
242                              available_instructions->second)]
243                          ->result_id());
244   }
245   return result;
246 }
247 
248 std::vector<uint32_t>
FindComponentsToConstructStruct(const opt::Instruction & struct_type_instruction,const TypeIdToInstructions & type_id_to_available_instructions)249 FuzzerPassConstructComposites::FindComponentsToConstructStruct(
250     const opt::Instruction& struct_type_instruction,
251     const TypeIdToInstructions& type_id_to_available_instructions) {
252   assert(struct_type_instruction.opcode() == SpvOpTypeStruct &&
253          "Precondition: instruction must be a struct type.");
254   std::vector<uint32_t> result;
255   // Consider the type of each field of the struct.
256   for (uint32_t in_operand_index = 0;
257        in_operand_index < struct_type_instruction.NumInOperands();
258        in_operand_index++) {
259     auto element_type_id =
260         struct_type_instruction.GetSingleWordInOperand(in_operand_index);
261     // Find the instructions at our disposal that compute something of the field
262     // type.
263     auto available_instructions =
264         type_id_to_available_instructions.find(element_type_id);
265     if (available_instructions == type_id_to_available_instructions.cend()) {
266       // If there are no such instructions, we cannot construct a composite of
267       // this struct type.
268       return {};
269     }
270     result.push_back(available_instructions
271                          ->second[GetFuzzerContext()->RandomIndex(
272                              available_instructions->second)]
273                          ->result_id());
274   }
275   return result;
276 }
277 
278 std::vector<uint32_t>
FindComponentsToConstructVector(const opt::Instruction & vector_type_instruction,const TypeIdToInstructions & type_id_to_available_instructions)279 FuzzerPassConstructComposites::FindComponentsToConstructVector(
280     const opt::Instruction& vector_type_instruction,
281     const TypeIdToInstructions& type_id_to_available_instructions) {
282   assert(vector_type_instruction.opcode() == SpvOpTypeVector &&
283          "Precondition: instruction must be a vector type.");
284 
285   // Get details of the type underlying the vector, and the width of the vector,
286   // for convenience.
287   auto element_type_id = vector_type_instruction.GetSingleWordInOperand(0);
288   auto element_type = GetIRContext()->get_type_mgr()->GetType(element_type_id);
289   auto element_count = vector_type_instruction.GetSingleWordInOperand(1);
290 
291   // Collect a mapping, from type id to width, for scalar/vector types that are
292   // smaller in width than |vector_type|, but that have the same underlying
293   // type.  For example, if |vector_type| is vec4, the mapping will be:
294   //   { float -> 1, vec2 -> 2, vec3 -> 3 }
295   // The mapping will have missing entries if some of these types do not exist.
296 
297   std::map<uint32_t, uint32_t> smaller_vector_type_id_to_width;
298   // Add the underlying type.  This id must exist, in order for |vector_type| to
299   // exist.
300   smaller_vector_type_id_to_width[element_type_id] = 1;
301 
302   // Now add every vector type with width at least 2, and less than the width of
303   // |vector_type|.
304   for (uint32_t width = 2; width < element_count; width++) {
305     opt::analysis::Vector smaller_vector_type(element_type, width);
306     auto smaller_vector_type_id =
307         GetIRContext()->get_type_mgr()->GetId(&smaller_vector_type);
308     // We might find that there is no declared type of this smaller width.
309     // For example, a module can declare vec4 without having declared vec2 or
310     // vec3.
311     if (smaller_vector_type_id) {
312       smaller_vector_type_id_to_width[smaller_vector_type_id] = width;
313     }
314   }
315 
316   // Now we know the types that are available to us, we set about populating a
317   // vector of the right length.  We do this by deciding, with no order in mind,
318   // which instructions we will use to populate the vector, and subsequently
319   // randomly choosing an order.  This is to avoid biasing construction of
320   // vectors with smaller vectors to the left and scalars to the right.  That is
321   // a concern because, e.g. in the case of populating a vec4, if we populate
322   // the constructor instructions left-to-right, we can always choose a vec3 to
323   // construct the first three elements, but can only choose a vec3 to construct
324   // the last three elements if we chose a float to construct the first element
325   // (otherwise there will not be space left for a vec3).
326 
327   uint32_t vector_slots_used = 0;
328   // The instructions we will use to construct the vector, in no particular
329   // order at this stage.
330   std::vector<opt::Instruction*> instructions_to_use;
331 
332   while (vector_slots_used < element_count) {
333     std::vector<opt::Instruction*> instructions_to_choose_from;
334     for (auto& entry : smaller_vector_type_id_to_width) {
335       if (entry.second >
336           std::min(element_count - 1, element_count - vector_slots_used)) {
337         continue;
338       }
339       auto available_instructions =
340           type_id_to_available_instructions.find(entry.first);
341       if (available_instructions == type_id_to_available_instructions.cend()) {
342         continue;
343       }
344       instructions_to_choose_from.insert(instructions_to_choose_from.end(),
345                                          available_instructions->second.begin(),
346                                          available_instructions->second.end());
347     }
348     if (instructions_to_choose_from.empty()) {
349       // We may get unlucky and find that there are not any instructions to
350       // choose from.  In this case we give up constructing a composite of this
351       // vector type.  It might be that we could construct the composite in
352       // another manner, so we could opt to retry a few times here, but it is
353       // simpler to just give up on the basis that this will not happen
354       // frequently.
355       return {};
356     }
357     auto instruction_to_use =
358         instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
359             instructions_to_choose_from)];
360     instructions_to_use.push_back(instruction_to_use);
361     auto chosen_type =
362         GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id());
363     if (chosen_type->AsVector()) {
364       assert(chosen_type->AsVector()->element_type() == element_type);
365       assert(chosen_type->AsVector()->element_count() < element_count);
366       assert(chosen_type->AsVector()->element_count() <=
367              element_count - vector_slots_used);
368       vector_slots_used += chosen_type->AsVector()->element_count();
369     } else {
370       assert(chosen_type == element_type);
371       vector_slots_used += 1;
372     }
373   }
374   assert(vector_slots_used == element_count);
375 
376   std::vector<uint32_t> result;
377   std::vector<uint32_t> operands;
378   while (!instructions_to_use.empty()) {
379     auto index = GetFuzzerContext()->RandomIndex(instructions_to_use);
380     result.push_back(instructions_to_use[index]->result_id());
381     instructions_to_use.erase(instructions_to_use.begin() + index);
382   }
383   assert(result.size() > 1);
384   return result;
385 }
386 
387 }  // namespace fuzz
388 }  // namespace spvtools
389