transformation_access_chain.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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. #include "source/fuzz/transformation_access_chain.h"
  15. #include <vector>
  16. #include "source/fuzz/fuzzer_util.h"
  17. #include "source/fuzz/instruction_descriptor.h"
  18. namespace spvtools {
  19. namespace fuzz {
  20. TransformationAccessChain::TransformationAccessChain(
  21. protobufs::TransformationAccessChain message)
  22. : message_(std::move(message)) {}
  23. TransformationAccessChain::TransformationAccessChain(
  24. uint32_t fresh_id, uint32_t pointer_id,
  25. const std::vector<uint32_t>& index_id,
  26. const protobufs::InstructionDescriptor& instruction_to_insert_before,
  27. const std::vector<std::pair<uint32_t, uint32_t>>& fresh_ids_for_clamping) {
  28. message_.set_fresh_id(fresh_id);
  29. message_.set_pointer_id(pointer_id);
  30. for (auto id : index_id) {
  31. message_.add_index_id(id);
  32. }
  33. *message_.mutable_instruction_to_insert_before() =
  34. instruction_to_insert_before;
  35. for (auto clamping_ids_pair : fresh_ids_for_clamping) {
  36. protobufs::UInt32Pair pair;
  37. pair.set_first(clamping_ids_pair.first);
  38. pair.set_second(clamping_ids_pair.second);
  39. *message_.add_fresh_ids_for_clamping() = pair;
  40. }
  41. }
  42. bool TransformationAccessChain::IsApplicable(
  43. opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
  44. // Keep track of the fresh ids used to make sure that they are distinct.
  45. std::set<uint32_t> fresh_ids_used;
  46. // The result id must be fresh.
  47. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  48. message_.fresh_id(), ir_context, &fresh_ids_used)) {
  49. return false;
  50. }
  51. // The pointer id must exist and have a type.
  52. auto pointer = ir_context->get_def_use_mgr()->GetDef(message_.pointer_id());
  53. if (!pointer || !pointer->type_id()) {
  54. return false;
  55. }
  56. // The type must indeed be a pointer.
  57. auto pointer_type = ir_context->get_def_use_mgr()->GetDef(pointer->type_id());
  58. if (pointer_type->opcode() != spv::Op::OpTypePointer) {
  59. return false;
  60. }
  61. // The described instruction to insert before must exist and be a suitable
  62. // point where an OpAccessChain instruction could be inserted.
  63. auto instruction_to_insert_before =
  64. FindInstruction(message_.instruction_to_insert_before(), ir_context);
  65. if (!instruction_to_insert_before) {
  66. return false;
  67. }
  68. if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
  69. spv::Op::OpAccessChain, instruction_to_insert_before)) {
  70. return false;
  71. }
  72. // Do not allow making an access chain from a null or undefined pointer, as
  73. // we do not want to allow accessing such pointers. This might be acceptable
  74. // in dead blocks, but we conservatively avoid it.
  75. switch (pointer->opcode()) {
  76. case spv::Op::OpConstantNull:
  77. case spv::Op::OpUndef:
  78. assert(
  79. false &&
  80. "Access chains should not be created from null/undefined pointers");
  81. return false;
  82. default:
  83. break;
  84. }
  85. // The pointer on which the access chain is to be based needs to be available
  86. // (according to dominance rules) at the insertion point.
  87. if (!fuzzerutil::IdIsAvailableBeforeInstruction(
  88. ir_context, instruction_to_insert_before, message_.pointer_id())) {
  89. return false;
  90. }
  91. // We now need to use the given indices to walk the type structure of the
  92. // base type of the pointer, making sure that (a) the indices correspond to
  93. // integers, and (b) these integer values are in-bounds.
  94. // Start from the base type of the pointer.
  95. uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
  96. int id_pairs_used = 0;
  97. // Consider the given index ids in turn.
  98. for (auto index_id : message_.index_id()) {
  99. // The index value will correspond to the value of the index if the object
  100. // is a struct, otherwise the value 0 will be used.
  101. uint32_t index_value;
  102. // Check whether the object is a struct.
  103. if (ir_context->get_def_use_mgr()->GetDef(subobject_type_id)->opcode() ==
  104. spv::Op::OpTypeStruct) {
  105. // It is a struct: we need to retrieve the integer value.
  106. bool successful;
  107. std::tie(successful, index_value) =
  108. GetStructIndexValue(ir_context, index_id, subobject_type_id);
  109. if (!successful) {
  110. return false;
  111. }
  112. } else {
  113. // It is not a struct: the index will need clamping.
  114. if (message_.fresh_ids_for_clamping().size() <= id_pairs_used) {
  115. // We don't have enough ids
  116. return false;
  117. }
  118. // Get two new ids to use and update the amount used.
  119. protobufs::UInt32Pair fresh_ids =
  120. message_.fresh_ids_for_clamping()[id_pairs_used++];
  121. // Valid ids need to have been given
  122. if (fresh_ids.first() == 0 || fresh_ids.second() == 0) {
  123. return false;
  124. }
  125. // Check that the ids are actually fresh and not already used by this
  126. // transformation.
  127. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  128. fresh_ids.first(), ir_context, &fresh_ids_used) ||
  129. !CheckIdIsFreshAndNotUsedByThisTransformation(
  130. fresh_ids.second(), ir_context, &fresh_ids_used)) {
  131. return false;
  132. }
  133. if (!ValidIndexToComposite(ir_context, index_id, subobject_type_id)) {
  134. return false;
  135. }
  136. // Perform the clamping using the fresh ids at our disposal.
  137. auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id);
  138. uint32_t bound = fuzzerutil::GetBoundForCompositeIndex(
  139. *ir_context->get_def_use_mgr()->GetDef(subobject_type_id),
  140. ir_context);
  141. // The module must have an integer constant of value bound-1 of the same
  142. // type as the index.
  143. if (!fuzzerutil::MaybeGetIntegerConstantFromValueAndType(
  144. ir_context, bound - 1, index_instruction->type_id())) {
  145. return false;
  146. }
  147. // The module must have the definition of bool type to make a comparison.
  148. if (!fuzzerutil::MaybeGetBoolType(ir_context)) {
  149. return false;
  150. }
  151. // The index is not necessarily a constant, so we may not know its value.
  152. // We can use index 0 because the components of a non-struct composite
  153. // all have the same type, and index 0 is always in bounds.
  154. index_value = 0;
  155. }
  156. // Try to walk down the type using this index. This will yield 0 if the
  157. // type is not a composite or the index is out of bounds, and the id of
  158. // the next type otherwise.
  159. subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
  160. ir_context, subobject_type_id, index_value);
  161. if (!subobject_type_id) {
  162. // Either the type was not a composite (so that too many indices were
  163. // provided), or the index was out of bounds.
  164. return false;
  165. }
  166. }
  167. // At this point, |subobject_type_id| is the type of the value targeted by
  168. // the new access chain. The result type of the access chain should be a
  169. // pointer to this type, with the same storage class as for the original
  170. // pointer. Such a pointer type needs to exist in the module.
  171. //
  172. // We do not use the type manager to look up this type, due to problems
  173. // associated with pointers to isomorphic structs being regarded as the same.
  174. return fuzzerutil::MaybeGetPointerType(
  175. ir_context, subobject_type_id,
  176. static_cast<spv::StorageClass>(
  177. pointer_type->GetSingleWordInOperand(0))) != 0;
  178. }
  179. void TransformationAccessChain::Apply(
  180. opt::IRContext* ir_context,
  181. TransformationContext* transformation_context) const {
  182. // The operands to the access chain are the pointer followed by the indices.
  183. // The result type of the access chain is determined by where the indices
  184. // lead. We thus push the pointer to a sequence of operands, and then follow
  185. // the indices, pushing each to the operand list and tracking the type
  186. // obtained by following it. Ultimately this yields the type of the
  187. // component reached by following all the indices, and the result type is
  188. // a pointer to this component type.
  189. opt::Instruction::OperandList operands;
  190. // Add the pointer id itself.
  191. operands.push_back({SPV_OPERAND_TYPE_ID, {message_.pointer_id()}});
  192. // Start walking the indices, starting with the pointer's base type.
  193. auto pointer_type = ir_context->get_def_use_mgr()->GetDef(
  194. ir_context->get_def_use_mgr()->GetDef(message_.pointer_id())->type_id());
  195. uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
  196. uint32_t id_pairs_used = 0;
  197. opt::Instruction* instruction_to_insert_before =
  198. FindInstruction(message_.instruction_to_insert_before(), ir_context);
  199. opt::BasicBlock* enclosing_block =
  200. ir_context->get_instr_block(instruction_to_insert_before);
  201. // Go through the index ids in turn.
  202. for (auto index_id : message_.index_id()) {
  203. uint32_t index_value;
  204. // Actual id to be used in the instruction: the original id
  205. // or the clamped one.
  206. uint32_t new_index_id;
  207. // Check whether the object is a struct.
  208. if (ir_context->get_def_use_mgr()->GetDef(subobject_type_id)->opcode() ==
  209. spv::Op::OpTypeStruct) {
  210. // It is a struct: we need to retrieve the integer value.
  211. index_value =
  212. GetStructIndexValue(ir_context, index_id, subobject_type_id).second;
  213. new_index_id = index_id;
  214. } else {
  215. // It is not a struct: the index will need clamping.
  216. // Get two new ids to use and update the amount used.
  217. protobufs::UInt32Pair fresh_ids =
  218. message_.fresh_ids_for_clamping()[id_pairs_used++];
  219. // Perform the clamping using the fresh ids at our disposal.
  220. // The module will not be changed if |add_clamping_instructions| is not
  221. // set.
  222. auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id);
  223. uint32_t bound = fuzzerutil::GetBoundForCompositeIndex(
  224. *ir_context->get_def_use_mgr()->GetDef(subobject_type_id),
  225. ir_context);
  226. auto bound_minus_one_id =
  227. fuzzerutil::MaybeGetIntegerConstantFromValueAndType(
  228. ir_context, bound - 1, index_instruction->type_id());
  229. assert(bound_minus_one_id &&
  230. "A constant of value bound - 1 and the same type as the index "
  231. "must exist as a precondition.");
  232. uint32_t bool_type_id = fuzzerutil::MaybeGetBoolType(ir_context);
  233. assert(bool_type_id &&
  234. "An OpTypeBool instruction must exist as a precondition.");
  235. auto int_type_inst =
  236. ir_context->get_def_use_mgr()->GetDef(index_instruction->type_id());
  237. // Clamp the integer and add the corresponding instructions in the module
  238. // if |add_clamping_instructions| is set.
  239. // Compare the index with the bound via an instruction of the form:
  240. // %fresh_ids.first = OpULessThanEqual %bool %int_id %bound_minus_one.
  241. fuzzerutil::UpdateModuleIdBound(ir_context, fresh_ids.first());
  242. auto comparison_instruction = MakeUnique<opt::Instruction>(
  243. ir_context, spv::Op::OpULessThanEqual, bool_type_id,
  244. fresh_ids.first(),
  245. opt::Instruction::OperandList(
  246. {{SPV_OPERAND_TYPE_ID, {index_instruction->result_id()}},
  247. {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}}));
  248. auto comparison_instruction_ptr = comparison_instruction.get();
  249. instruction_to_insert_before->InsertBefore(
  250. std::move(comparison_instruction));
  251. ir_context->get_def_use_mgr()->AnalyzeInstDefUse(
  252. comparison_instruction_ptr);
  253. ir_context->set_instr_block(comparison_instruction_ptr, enclosing_block);
  254. // Select the index if in-bounds, otherwise one less than the bound:
  255. // %fresh_ids.second = OpSelect %int_type %fresh_ids.first %int_id
  256. // %bound_minus_one
  257. fuzzerutil::UpdateModuleIdBound(ir_context, fresh_ids.second());
  258. auto select_instruction = MakeUnique<opt::Instruction>(
  259. ir_context, spv::Op::OpSelect, int_type_inst->result_id(),
  260. fresh_ids.second(),
  261. opt::Instruction::OperandList(
  262. {{SPV_OPERAND_TYPE_ID, {fresh_ids.first()}},
  263. {SPV_OPERAND_TYPE_ID, {index_instruction->result_id()}},
  264. {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}}));
  265. auto select_instruction_ptr = select_instruction.get();
  266. instruction_to_insert_before->InsertBefore(std::move(select_instruction));
  267. ir_context->get_def_use_mgr()->AnalyzeInstDefUse(select_instruction_ptr);
  268. ir_context->set_instr_block(select_instruction_ptr, enclosing_block);
  269. new_index_id = fresh_ids.second();
  270. index_value = 0;
  271. }
  272. // Add the correct index id to the operands.
  273. operands.push_back({SPV_OPERAND_TYPE_ID, {new_index_id}});
  274. // Walk to the next type in the composite object using this index.
  275. subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
  276. ir_context, subobject_type_id, index_value);
  277. }
  278. // The access chain's result type is a pointer to the composite component
  279. // that was reached after following all indices. The storage class is that
  280. // of the original pointer.
  281. uint32_t result_type = fuzzerutil::MaybeGetPointerType(
  282. ir_context, subobject_type_id,
  283. static_cast<spv::StorageClass>(pointer_type->GetSingleWordInOperand(0)));
  284. // Add the access chain instruction to the module, and update the module's
  285. // id bound.
  286. fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
  287. auto access_chain_instruction =
  288. MakeUnique<opt::Instruction>(ir_context, spv::Op::OpAccessChain,
  289. result_type, message_.fresh_id(), operands);
  290. auto access_chain_instruction_ptr = access_chain_instruction.get();
  291. instruction_to_insert_before->InsertBefore(
  292. std::move(access_chain_instruction));
  293. ir_context->get_def_use_mgr()->AnalyzeInstDefUse(
  294. access_chain_instruction_ptr);
  295. ir_context->set_instr_block(access_chain_instruction_ptr, enclosing_block);
  296. // If the base pointer's pointee value was irrelevant, the same is true of
  297. // the pointee value of the result of this access chain.
  298. if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
  299. message_.pointer_id())) {
  300. transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
  301. message_.fresh_id());
  302. }
  303. }
  304. protobufs::Transformation TransformationAccessChain::ToMessage() const {
  305. protobufs::Transformation result;
  306. *result.mutable_access_chain() = message_;
  307. return result;
  308. }
  309. std::pair<bool, uint32_t> TransformationAccessChain::GetStructIndexValue(
  310. opt::IRContext* ir_context, uint32_t index_id,
  311. uint32_t object_type_id) const {
  312. assert(ir_context->get_def_use_mgr()->GetDef(object_type_id)->opcode() ==
  313. spv::Op::OpTypeStruct &&
  314. "Precondition: the type must be a struct type.");
  315. if (!ValidIndexToComposite(ir_context, index_id, object_type_id)) {
  316. return {false, 0};
  317. }
  318. auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id);
  319. uint32_t bound = fuzzerutil::GetBoundForCompositeIndex(
  320. *ir_context->get_def_use_mgr()->GetDef(object_type_id), ir_context);
  321. // Ensure that the index given must represent a constant.
  322. assert(spvOpcodeIsConstant(index_instruction->opcode()) &&
  323. "A non-constant index should already have been rejected.");
  324. // The index must be in bounds.
  325. uint32_t value = index_instruction->GetSingleWordInOperand(0);
  326. if (value >= bound) {
  327. return {false, 0};
  328. }
  329. return {true, value};
  330. }
  331. bool TransformationAccessChain::ValidIndexToComposite(
  332. opt::IRContext* ir_context, uint32_t index_id, uint32_t object_type_id) {
  333. auto object_type_def = ir_context->get_def_use_mgr()->GetDef(object_type_id);
  334. // The object being indexed must be a composite.
  335. if (!spvOpcodeIsComposite(object_type_def->opcode())) {
  336. return false;
  337. }
  338. // Get the defining instruction of the index.
  339. auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id);
  340. if (!index_instruction) {
  341. return false;
  342. }
  343. // The index type must be 32-bit integer.
  344. auto index_type =
  345. ir_context->get_def_use_mgr()->GetDef(index_instruction->type_id());
  346. if (index_type->opcode() != spv::Op::OpTypeInt ||
  347. index_type->GetSingleWordInOperand(0) != 32) {
  348. return false;
  349. }
  350. // If the object being traversed is a struct, the id must correspond to an
  351. // in-bound constant.
  352. if (object_type_def->opcode() == spv::Op::OpTypeStruct) {
  353. if (!spvOpcodeIsConstant(index_instruction->opcode())) {
  354. return false;
  355. }
  356. }
  357. return true;
  358. }
  359. std::unordered_set<uint32_t> TransformationAccessChain::GetFreshIds() const {
  360. std::unordered_set<uint32_t> result = {message_.fresh_id()};
  361. for (auto& fresh_ids_for_clamping : message_.fresh_ids_for_clamping()) {
  362. result.insert(fresh_ids_for_clamping.first());
  363. result.insert(fresh_ids_for_clamping.second());
  364. }
  365. return result;
  366. }
  367. } // namespace fuzz
  368. } // namespace spvtools