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