1 // Copyright (c) 2020 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_add_composite_inserts.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/fuzz/instruction_descriptor.h"
19 #include "source/fuzz/pseudo_random_generator.h"
20 #include "source/fuzz/transformation_composite_insert.h"
21
22 namespace spvtools {
23 namespace fuzz {
24
FuzzerPassAddCompositeInserts(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations)25 FuzzerPassAddCompositeInserts::FuzzerPassAddCompositeInserts(
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
Apply()32 void FuzzerPassAddCompositeInserts::Apply() {
33 ForEachInstructionWithInstructionDescriptor(
34 [this](opt::Function* function, opt::BasicBlock* block,
35 opt::BasicBlock::iterator instruction_iterator,
36 const protobufs::InstructionDescriptor& instruction_descriptor)
37 -> void {
38 assert(instruction_iterator->opcode() ==
39 instruction_descriptor.target_instruction_opcode() &&
40 "The opcode of the instruction we might insert before must be "
41 "the same as the opcode in the descriptor for the instruction");
42
43 // Randomly decide whether to try adding an OpCompositeInsert
44 // instruction.
45 if (!GetFuzzerContext()->ChoosePercentage(
46 GetFuzzerContext()->GetChanceOfAddingCompositeInsert())) {
47 return;
48 }
49
50 // It must be possible to insert an OpCompositeInsert instruction
51 // before |instruction_iterator|.
52 if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
53 SpvOpCompositeInsert, instruction_iterator)) {
54 return;
55 }
56
57 // Look for available values that have composite type.
58 std::vector<opt::Instruction*> available_composites =
59 FindAvailableInstructions(
60 function, block, instruction_iterator,
61 [instruction_descriptor](
62 opt::IRContext* ir_context,
63 opt::Instruction* instruction) -> bool {
64 // |instruction| must be a supported instruction of composite
65 // type.
66 if (!TransformationCompositeInsert::
67 IsCompositeInstructionSupported(ir_context,
68 instruction)) {
69 return false;
70 }
71
72 auto instruction_type = ir_context->get_type_mgr()->GetType(
73 instruction->type_id());
74
75 // No components of the composite can have type
76 // OpTypeRuntimeArray.
77 if (ContainsRuntimeArray(*instruction_type)) {
78 return false;
79 }
80
81 // No components of the composite can be pointers.
82 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3658):
83 // Structs can have components of pointer type.
84 // FindOrCreateZeroConstant cannot be called on a
85 // pointer. We ignore pointers for now. Consider adding
86 // support for pointer types.
87 if (ContainsPointer(*instruction_type)) {
88 return false;
89 }
90
91 return true;
92 });
93
94 // If there are no available values, then return.
95 if (available_composites.empty()) {
96 return;
97 }
98
99 // Choose randomly one available composite value.
100 auto available_composite =
101 available_composites[GetFuzzerContext()->RandomIndex(
102 available_composites)];
103
104 // Take a random component of the chosen composite value. If the chosen
105 // component is itself a composite, then randomly decide whether to take
106 // its component and repeat.
107 uint32_t current_node_type_id = available_composite->type_id();
108 std::vector<uint32_t> path_to_replaced;
109 while (true) {
110 auto current_node_type_inst =
111 GetIRContext()->get_def_use_mgr()->GetDef(current_node_type_id);
112 uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex(
113 *current_node_type_inst, GetIRContext());
114
115 // If the composite is empty, then end the iteration.
116 if (num_of_components == 0) {
117 break;
118 }
119 uint32_t one_selected_index =
120 GetFuzzerContext()->GetRandomIndexForCompositeInsert(
121 num_of_components);
122
123 // Construct a final index by appending the current index.
124 path_to_replaced.push_back(one_selected_index);
125 current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
126 GetIRContext(), current_node_type_id, one_selected_index);
127
128 // If the component is not a composite then end the iteration.
129 if (!fuzzerutil::IsCompositeType(
130 GetIRContext()->get_type_mgr()->GetType(
131 current_node_type_id))) {
132 break;
133 }
134
135 // If the component is a composite, but we decide not to go deeper,
136 // then end the iteration.
137 if (!GetFuzzerContext()->ChoosePercentage(
138 GetFuzzerContext()
139 ->GetChanceOfGoingDeeperToInsertInComposite())) {
140 break;
141 }
142 }
143
144 // Look for available objects that have the type id
145 // |current_node_type_id| and can be inserted.
146 std::vector<opt::Instruction*> available_objects =
147 FindAvailableInstructions(
148 function, block, instruction_iterator,
149 [instruction_descriptor, current_node_type_id](
150 opt::IRContext* /*unused*/,
151 opt::Instruction* instruction) -> bool {
152 if (instruction->result_id() == 0 ||
153 instruction->type_id() == 0) {
154 return false;
155 }
156 if (instruction->type_id() != current_node_type_id) {
157 return false;
158 }
159 return true;
160 });
161
162 // If there are no objects of the specific type available, check if
163 // FindOrCreateZeroConstant can be called and create a zero constant of
164 // this type.
165 uint32_t available_object_id;
166 if (available_objects.empty()) {
167 if (!fuzzerutil::CanCreateConstant(GetIRContext(),
168 current_node_type_id)) {
169 return;
170 }
171 available_object_id =
172 FindOrCreateZeroConstant(current_node_type_id, false);
173 } else {
174 available_object_id =
175 available_objects[GetFuzzerContext()->RandomIndex(
176 available_objects)]
177 ->result_id();
178 }
179 auto new_result_id = GetFuzzerContext()->GetFreshId();
180
181 // Insert an OpCompositeInsert instruction which copies
182 // |available_composite| and in the copy inserts the object
183 // of type |available_object_id| at index |index_to_replace|.
184 ApplyTransformation(TransformationCompositeInsert(
185 instruction_descriptor, new_result_id,
186 available_composite->result_id(), available_object_id,
187 path_to_replaced));
188 });
189 }
190
ContainsPointer(const opt::analysis::Type & type)191 bool FuzzerPassAddCompositeInserts::ContainsPointer(
192 const opt::analysis::Type& type) {
193 switch (type.kind()) {
194 case opt::analysis::Type::kPointer:
195 return true;
196 case opt::analysis::Type::kArray:
197 return ContainsPointer(*type.AsArray()->element_type());
198 case opt::analysis::Type::kMatrix:
199 return ContainsPointer(*type.AsMatrix()->element_type());
200 case opt::analysis::Type::kVector:
201 return ContainsPointer(*type.AsVector()->element_type());
202 case opt::analysis::Type::kStruct:
203 return std::any_of(type.AsStruct()->element_types().begin(),
204 type.AsStruct()->element_types().end(),
205 [](const opt::analysis::Type* element_type) {
206 return ContainsPointer(*element_type);
207 });
208 default:
209 return false;
210 }
211 }
212
ContainsRuntimeArray(const opt::analysis::Type & type)213 bool FuzzerPassAddCompositeInserts::ContainsRuntimeArray(
214 const opt::analysis::Type& type) {
215 switch (type.kind()) {
216 case opt::analysis::Type::kRuntimeArray:
217 return true;
218 case opt::analysis::Type::kStruct:
219 // If any component of a struct is of type OpTypeRuntimeArray, return
220 // true.
221 return std::any_of(type.AsStruct()->element_types().begin(),
222 type.AsStruct()->element_types().end(),
223 [](const opt::analysis::Type* element_type) {
224 return ContainsRuntimeArray(*element_type);
225 });
226 default:
227 return false;
228 }
229 }
230
231 } // namespace fuzz
232 } // namespace spvtools
233