SPIR-V Tools fuzzer: validate before mutation

ClusterFuzz will provide inputs to a fuzzer that did not necessarily
come from the current fuzzing run, thus the SPIR-V Tools mutator can be
presented with arbitrary inputs. This change causes it to validate
inputs before mutation, and reject invalid inputs.

Change-Id: Ic90e62e4f80f38826765b0d815e4f41de915b5df
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/59661
Auto-Submit: Alastair Donaldson <afdx@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Alastair Donaldson <afdx@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc b/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
index be6fd89..5910e98 100644
--- a/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
+++ b/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
@@ -25,6 +25,7 @@
 #include "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
 #include "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
 #include "fuzzers/tint_spirv_tools_fuzzer/util.h"
+#include "spirv-tools/libspirv.hpp"
 
 namespace tint {
 namespace fuzzers {
@@ -96,15 +97,45 @@
   }
 }
 
+void CLIMessageConsumer(spv_message_level_t level,
+                        const char*,
+                        const spv_position_t& position,
+                        const char* message) {
+  switch (level) {
+    case SPV_MSG_FATAL:
+    case SPV_MSG_INTERNAL_ERROR:
+    case SPV_MSG_ERROR:
+      std::cerr << "error: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_WARNING:
+      std::cout << "warning: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_INFO:
+      std::cout << "info: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    default:
+      break;
+  }
+}
+
+bool IsValid(const std::vector<uint32_t>& binary) {
+  spvtools::SpirvTools tools(context->params.mutator_params.target_env);
+  tools.SetMessageConsumer(CLIMessageConsumer);
+  return tools.IsValid() && tools.Validate(binary.data(), binary.size(),
+                                           spvtools::ValidatorOptions());
+}
+
 extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
                                           size_t size,
                                           size_t max_size,
                                           unsigned seed) {
-  if ((size % 4) != 0) {
-    // A valid SPIR-V binary's size must be a multiple of 4, and the SPIR-V
-    // Tools fuzzer should only work with valid binaries.
-    // TODO(afdx): Change this to an assertion once sure that this fuzzer is
-    //  configured correctly in ClusterFuzz.
+  if ((size % sizeof(uint32_t)) != 0) {
+    // A valid SPIR-V binary's size must be a multiple of the size of a 32-bit
+    // word, and the SPIR-V Tools fuzzer is only designed to work with valid
+    // binaries.
     return 0;
   }
 
@@ -114,13 +145,18 @@
   MutatorCache dummy_cache(1);
   auto* mutator_cache = context->mutator_cache.get();
   if (!mutator_cache) {
-    // Use a dummy cache if the user has decided not to use a real cache.
-    // The dummy cache will be destroyed when we return from this function but
-    // it will save us from writing all the `if (mutator_cache)` below.
+    // Use a placeholder cache if the user has decided not to use a real cache.
+    // The placeholder cache will be destroyed when we return from this function
+    // but it will save us from writing all the `if (mutator_cache)` below.
     mutator_cache = &dummy_cache;
   }
 
   if (!mutator_cache->Get(binary)) {
+    // This is an unknown binary, so its validity must be checked before
+    // proceeding.
+    if (!IsValid(binary)) {
+      return 0;
+    }
     // Assign a mutator to the binary if it doesn't have one yet.
     mutator_cache->Put(binary, CreateMutator(binary, seed));
   }
@@ -178,11 +214,10 @@
     return 0;
   }
 
-  if ((size % 4) != 0) {
-    // By design, the SPIR-V Tools fuzzer should only ever test using valid
-    // SPIR-V binaries, whose sizes should be multiples of 4 bytes.
-    // TODO(afdx): Change this to an assertion once sure that this fuzzer is
-    //  configured correctly in ClusterFuzz.
+  if ((size % sizeof(uint32_t)) != 0) {
+    // The SPIR-V Tools fuzzer has been designed to work with valid
+    // SPIR-V binaries, whose sizes should be multiples of the size of a 32-bit
+    // word.
     return 0;
   }