transformation_access_chain.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. const spvtools::fuzz::protobufs::TransformationAccessChain& message)
  22. : message_(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() != SpvOpTypePointer) {
  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. SpvOpAccessChain, 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 SpvOpConstantNull:
  77. case SpvOpUndef:
  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. SpvOpTypeStruct) {
  105. // It is a struct: we need to retrieve the integer value.
  106. bool successful;
  107. std::tie(successful, index_value) =
  108. GetIndexValue(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<SpvStorageClass>(
  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. // Go through the index ids in turn.
  198. for (auto index_id : message_.index_id()) {
  199. uint32_t index_value;
  200. // Actual id to be used in the instruction: the original id
  201. // or the clamped one.
  202. uint32_t new_index_id;
  203. // Check whether the object is a struct.
  204. if (ir_context->get_def_use_mgr()->GetDef(subobject_type_id)->opcode() ==
  205. SpvOpTypeStruct) {
  206. // It is a struct: we need to retrieve the integer value.
  207. index_value =
  208. GetIndexValue(ir_context, index_id, subobject_type_id).second;
  209. new_index_id = index_id;
  210. } else {
  211. // It is not a struct: the index will need clamping.
  212. // Get two new ids to use and update the amount used.
  213. protobufs::UInt32Pair fresh_ids =
  214. message_.fresh_ids_for_clamping()[id_pairs_used++];
  215. // Perform the clamping using the fresh ids at our disposal.
  216. // The module will not be changed if |add_clamping_instructions| is not
  217. // set.
  218. auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id);
  219. uint32_t bound = fuzzerutil::GetBoundForCompositeIndex(
  220. *ir_context->get_def_use_mgr()->GetDef(subobject_type_id),
  221. ir_context);
  222. auto bound_minus_one_id =
  223. fuzzerutil::MaybeGetIntegerConstantFromValueAndType(
  224. ir_context, bound - 1, index_instruction->type_id());
  225. assert(bound_minus_one_id &&
  226. "A constant of value bound - 1 and the same type as the index "
  227. "must exist as a precondition.");
  228. uint32_t bool_type_id = fuzzerutil::MaybeGetBoolType(ir_context);
  229. assert(bool_type_id &&
  230. "An OpTypeBool instruction must exist as a precondition.");
  231. auto int_type_inst =
  232. ir_context->get_def_use_mgr()->GetDef(index_instruction->type_id());
  233. // Clamp the integer and add the corresponding instructions in the module
  234. // if |add_clamping_instructions| is set.
  235. auto instruction_to_insert_before =
  236. FindInstruction(message_.instruction_to_insert_before(), ir_context);
  237. // Compare the index with the bound via an instruction of the form:
  238. // %fresh_ids.first = OpULessThanEqual %bool %int_id %bound_minus_one.
  239. fuzzerutil::UpdateModuleIdBound(ir_context, fresh_ids.first());
  240. instruction_to_insert_before->InsertBefore(MakeUnique<opt::Instruction>(
  241. ir_context, SpvOpULessThanEqual, bool_type_id, fresh_ids.first(),
  242. opt::Instruction::OperandList(
  243. {{SPV_OPERAND_TYPE_ID, {index_instruction->result_id()}},
  244. {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}})));
  245. // Select the index if in-bounds, otherwise one less than the bound:
  246. // %fresh_ids.second = OpSelect %int_type %fresh_ids.first %int_id
  247. // %bound_minus_one
  248. fuzzerutil::UpdateModuleIdBound(ir_context, fresh_ids.second());
  249. instruction_to_insert_before->InsertBefore(MakeUnique<opt::Instruction>(
  250. ir_context, SpvOpSelect, int_type_inst->result_id(),
  251. fresh_ids.second(),
  252. opt::Instruction::OperandList(
  253. {{SPV_OPERAND_TYPE_ID, {fresh_ids.first()}},
  254. {SPV_OPERAND_TYPE_ID, {index_instruction->result_id()}},
  255. {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}})));
  256. new_index_id = fresh_ids.second();
  257. index_value = 0;
  258. }
  259. // Add the correct index id to the operands.
  260. operands.push_back({SPV_OPERAND_TYPE_ID, {new_index_id}});
  261. // Walk to the next type in the composite object using this index.
  262. subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
  263. ir_context, subobject_type_id, index_value);
  264. }
  265. // The access chain's result type is a pointer to the composite component
  266. // that was reached after following all indices. The storage class is that
  267. // of the original pointer.
  268. uint32_t result_type = fuzzerutil::MaybeGetPointerType(
  269. ir_context, subobject_type_id,
  270. static_cast<SpvStorageClass>(pointer_type->GetSingleWordInOperand(0)));
  271. // Add the access chain instruction to the module, and update the module's
  272. // id bound.
  273. fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
  274. FindInstruction(message_.instruction_to_insert_before(), ir_context)
  275. ->InsertBefore(MakeUnique<opt::Instruction>(
  276. ir_context, SpvOpAccessChain, result_type, message_.fresh_id(),
  277. operands));
  278. // Conservatively invalidate all analyses.
  279. ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
  280. // If the base pointer's pointee value was irrelevant, the same is true of
  281. // the pointee value of the result of this access chain.
  282. if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
  283. message_.pointer_id())) {
  284. transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
  285. message_.fresh_id());
  286. }
  287. }
  288. protobufs::Transformation TransformationAccessChain::ToMessage() const {
  289. protobufs::Transformation result;
  290. *result.mutable_access_chain() = message_;
  291. return result;
  292. }
  293. std::pair<bool, uint32_t> TransformationAccessChain::GetIndexValue(
  294. opt::IRContext* ir_context, uint32_t index_id,
  295. uint32_t object_type_id) const {
  296. if (!ValidIndexToComposite(ir_context, index_id, object_type_id)) {
  297. return {false, 0};
  298. }
  299. auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id);
  300. uint32_t bound = fuzzerutil::GetBoundForCompositeIndex(
  301. *ir_context->get_def_use_mgr()->GetDef(object_type_id), ir_context);
  302. // The index must be a constant
  303. if (!spvOpcodeIsConstant(index_instruction->opcode())) {
  304. return {false, 0};
  305. }
  306. // The index must be in bounds.
  307. uint32_t value = index_instruction->GetSingleWordInOperand(0);
  308. if (value >= bound) {
  309. return {false, 0};
  310. }
  311. return {true, value};
  312. }
  313. bool TransformationAccessChain::ValidIndexToComposite(
  314. opt::IRContext* ir_context, uint32_t index_id, uint32_t object_type_id) {
  315. auto object_type_def = ir_context->get_def_use_mgr()->GetDef(object_type_id);
  316. // The object being indexed must be a composite.
  317. if (!spvOpcodeIsComposite(object_type_def->opcode())) {
  318. return false;
  319. }
  320. // Get the defining instruction of the index.
  321. auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id);
  322. if (!index_instruction) {
  323. return false;
  324. }
  325. // The index type must be 32-bit integer.
  326. auto index_type =
  327. ir_context->get_def_use_mgr()->GetDef(index_instruction->type_id());
  328. if (index_type->opcode() != SpvOpTypeInt ||
  329. index_type->GetSingleWordInOperand(0) != 32) {
  330. return false;
  331. }
  332. // If the object being traversed is a struct, the id must correspond to an
  333. // in-bound constant.
  334. if (object_type_def->opcode() == SpvOpTypeStruct) {
  335. if (!spvOpcodeIsConstant(index_instruction->opcode())) {
  336. return false;
  337. }
  338. }
  339. return true;
  340. }
  341. std::unordered_set<uint32_t> TransformationAccessChain::GetFreshIds() const {
  342. std::unordered_set<uint32_t> result = {message_.fresh_id()};
  343. for (auto& fresh_ids_for_clamping : message_.fresh_ids_for_clamping()) {
  344. result.insert(fresh_ids_for_clamping.first());
  345. result.insert(fresh_ids_for_clamping.second());
  346. }
  347. return result;
  348. }
  349. } // namespace fuzz
  350. } // namespace spvtools