2
0

shrinker.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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. #include "source/fuzz/shrinker.h"
  15. #include <sstream>
  16. #include "source/fuzz/pseudo_random_generator.h"
  17. #include "source/fuzz/replayer.h"
  18. #include "source/spirv_fuzzer_options.h"
  19. #include "source/util/make_unique.h"
  20. namespace spvtools {
  21. namespace fuzz {
  22. namespace {
  23. // A helper to get the size of a protobuf transformation sequence in a less
  24. // verbose manner.
  25. uint32_t NumRemainingTransformations(
  26. const protobufs::TransformationSequence& transformation_sequence) {
  27. return static_cast<uint32_t>(transformation_sequence.transformation_size());
  28. }
  29. // A helper to return a transformation sequence identical to |transformations|,
  30. // except that a chunk of size |chunk_size| starting from |chunk_index| x
  31. // |chunk_size| is removed (or as many transformations as available if the whole
  32. // chunk is not).
  33. protobufs::TransformationSequence RemoveChunk(
  34. const protobufs::TransformationSequence& transformations,
  35. uint32_t chunk_index, uint32_t chunk_size) {
  36. uint32_t lower = chunk_index * chunk_size;
  37. uint32_t upper = std::min((chunk_index + 1) * chunk_size,
  38. NumRemainingTransformations(transformations));
  39. assert(lower < upper);
  40. assert(upper <= NumRemainingTransformations(transformations));
  41. protobufs::TransformationSequence result;
  42. for (uint32_t j = 0; j < NumRemainingTransformations(transformations); j++) {
  43. if (j >= lower && j < upper) {
  44. continue;
  45. }
  46. protobufs::Transformation transformation =
  47. transformations.transformation()[j];
  48. *result.mutable_transformation()->Add() = transformation;
  49. }
  50. return result;
  51. }
  52. } // namespace
  53. struct Shrinker::Impl {
  54. Impl(spv_target_env env, uint32_t limit, bool validate,
  55. spv_validator_options options)
  56. : target_env(env),
  57. step_limit(limit),
  58. validate_during_replay(validate),
  59. validator_options(options) {}
  60. const spv_target_env target_env; // Target environment.
  61. MessageConsumer consumer; // Message consumer.
  62. const uint32_t step_limit; // Step limit for reductions.
  63. const bool validate_during_replay; // Determines whether to check for
  64. // validity during the replaying of
  65. // transformations.
  66. spv_validator_options validator_options; // Options to control validation.
  67. };
  68. Shrinker::Shrinker(spv_target_env env, uint32_t step_limit,
  69. bool validate_during_replay,
  70. spv_validator_options validator_options)
  71. : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay,
  72. validator_options)) {}
  73. Shrinker::~Shrinker() = default;
  74. void Shrinker::SetMessageConsumer(MessageConsumer c) {
  75. impl_->consumer = std::move(c);
  76. }
  77. Shrinker::ShrinkerResultStatus Shrinker::Run(
  78. const std::vector<uint32_t>& binary_in,
  79. const protobufs::FactSequence& initial_facts,
  80. const protobufs::TransformationSequence& transformation_sequence_in,
  81. const Shrinker::InterestingnessFunction& interestingness_function,
  82. std::vector<uint32_t>* binary_out,
  83. protobufs::TransformationSequence* transformation_sequence_out) const {
  84. // Check compatibility between the library version being linked with and the
  85. // header files being used.
  86. GOOGLE_PROTOBUF_VERIFY_VERSION;
  87. spvtools::SpirvTools tools(impl_->target_env);
  88. if (!tools.IsValid()) {
  89. impl_->consumer(SPV_MSG_ERROR, nullptr, {},
  90. "Failed to create SPIRV-Tools interface; stopping.");
  91. return Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface;
  92. }
  93. // Initial binary should be valid.
  94. if (!tools.Validate(&binary_in[0], binary_in.size(),
  95. impl_->validator_options)) {
  96. impl_->consumer(SPV_MSG_INFO, nullptr, {},
  97. "Initial binary is invalid; stopping.");
  98. return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid;
  99. }
  100. std::vector<uint32_t> current_best_binary;
  101. protobufs::TransformationSequence current_best_transformations;
  102. // Run a replay of the initial transformation sequence to (a) check that it
  103. // succeeds, (b) get the binary that results from running these
  104. // transformations, and (c) get the subsequence of the initial transformations
  105. // that actually apply (in principle this could be a strict subsequence).
  106. if (Replayer(impl_->target_env, impl_->validate_during_replay,
  107. impl_->validator_options)
  108. .Run(binary_in, initial_facts, transformation_sequence_in,
  109. transformation_sequence_in.transformation_size(),
  110. &current_best_binary, &current_best_transformations) !=
  111. Replayer::ReplayerResultStatus::kComplete) {
  112. return ShrinkerResultStatus::kReplayFailed;
  113. }
  114. // Check that the binary produced by applying the initial transformations is
  115. // indeed interesting.
  116. if (!interestingness_function(current_best_binary, 0)) {
  117. impl_->consumer(SPV_MSG_INFO, nullptr, {},
  118. "Initial binary is not interesting; stopping.");
  119. return ShrinkerResultStatus::kInitialBinaryNotInteresting;
  120. }
  121. uint32_t attempt = 0; // Keeps track of the number of shrink attempts that
  122. // have been tried, whether successful or not.
  123. uint32_t chunk_size =
  124. std::max(1u, NumRemainingTransformations(current_best_transformations) /
  125. 2); // The number of contiguous transformations that the
  126. // shrinker will try to remove in one go; starts
  127. // high and decreases during the shrinking process.
  128. // Keep shrinking until we:
  129. // - reach the step limit,
  130. // - run out of transformations to remove, or
  131. // - cannot make the chunk size any smaller.
  132. while (attempt < impl_->step_limit &&
  133. !current_best_transformations.transformation().empty() &&
  134. chunk_size > 0) {
  135. bool progress_this_round =
  136. false; // Used to decide whether to make the chunk size with which we
  137. // remove transformations smaller. If we managed to remove at
  138. // least one chunk of transformations at a particular chunk
  139. // size, we set this flag so that we do not yet decrease the
  140. // chunk size.
  141. assert(chunk_size <=
  142. NumRemainingTransformations(current_best_transformations) &&
  143. "Chunk size should never exceed the number of transformations that "
  144. "remain.");
  145. // The number of chunks is the ceiling of (#remaining_transformations /
  146. // chunk_size).
  147. const uint32_t num_chunks =
  148. (NumRemainingTransformations(current_best_transformations) +
  149. chunk_size - 1) /
  150. chunk_size;
  151. assert(num_chunks >= 1 && "There should be at least one chunk.");
  152. assert(num_chunks * chunk_size >=
  153. NumRemainingTransformations(current_best_transformations) &&
  154. "All transformations should be in some chunk.");
  155. // We go through the transformations in reverse, in chunks of size
  156. // |chunk_size|, using |chunk_index| to track which chunk to try removing
  157. // next. The loop exits early if we reach the shrinking step limit.
  158. for (int chunk_index = num_chunks - 1;
  159. attempt < impl_->step_limit && chunk_index >= 0; chunk_index--) {
  160. // Remove a chunk of transformations according to the current index and
  161. // chunk size.
  162. auto transformations_with_chunk_removed =
  163. RemoveChunk(current_best_transformations, chunk_index, chunk_size);
  164. // Replay the smaller sequence of transformations to get a next binary and
  165. // transformation sequence. Note that the transformations arising from
  166. // replay might be even smaller than the transformations with the chunk
  167. // removed, because removing those transformations might make further
  168. // transformations inapplicable.
  169. std::vector<uint32_t> next_binary;
  170. protobufs::TransformationSequence next_transformation_sequence;
  171. if (Replayer(impl_->target_env, impl_->validate_during_replay,
  172. impl_->validator_options)
  173. .Run(binary_in, initial_facts, transformations_with_chunk_removed,
  174. transformations_with_chunk_removed.transformation_size(),
  175. &next_binary, &next_transformation_sequence) !=
  176. Replayer::ReplayerResultStatus::kComplete) {
  177. // Replay should not fail; if it does, we need to abort shrinking.
  178. return ShrinkerResultStatus::kReplayFailed;
  179. }
  180. assert(NumRemainingTransformations(next_transformation_sequence) >=
  181. chunk_index * chunk_size &&
  182. "Removing this chunk of transformations should not have an effect "
  183. "on earlier chunks.");
  184. if (interestingness_function(next_binary, attempt)) {
  185. // If the binary arising from the smaller transformation sequence is
  186. // interesting, this becomes our current best binary and transformation
  187. // sequence.
  188. current_best_binary = next_binary;
  189. current_best_transformations = next_transformation_sequence;
  190. progress_this_round = true;
  191. }
  192. // Either way, this was a shrink attempt, so increment our count of shrink
  193. // attempts.
  194. attempt++;
  195. }
  196. if (!progress_this_round) {
  197. // If we didn't manage to remove any chunks at this chunk size, try a
  198. // smaller chunk size.
  199. chunk_size /= 2;
  200. }
  201. // Decrease the chunk size until it becomes no larger than the number of
  202. // remaining transformations.
  203. while (chunk_size >
  204. NumRemainingTransformations(current_best_transformations)) {
  205. chunk_size /= 2;
  206. }
  207. }
  208. // The output from the shrinker is the best binary we saw, and the
  209. // transformations that led to it.
  210. *binary_out = current_best_binary;
  211. *transformation_sequence_out = current_best_transformations;
  212. // Indicate whether shrinking completed or was truncated due to reaching the
  213. // step limit.
  214. assert(attempt <= impl_->step_limit);
  215. if (attempt == impl_->step_limit) {
  216. std::stringstream strstream;
  217. strstream << "Shrinking did not complete; step limit " << impl_->step_limit
  218. << " was reached.";
  219. impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
  220. return Shrinker::ShrinkerResultStatus::kStepLimitReached;
  221. }
  222. return Shrinker::ShrinkerResultStatus::kComplete;
  223. }
  224. } // namespace fuzz
  225. } // namespace spvtools