Add fuzzing for transform::VertexPulling

Includes a significant refactoring of helper functions in
tint_common_fuzzer.cc/.h

BUG=tint:722

Change-Id: I1fdab0113bae02c4a0bf8da0d1b7729f05a2fc5b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/49902
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/fuzzers/BUILD.gn b/fuzzers/BUILD.gn
index 1aeacfd..74943fa 100644
--- a/fuzzers/BUILD.gn
+++ b/fuzzers/BUILD.gn
@@ -102,6 +102,11 @@
       deps = [ ":tint_fuzzer_common" ]
     }
 
+    fuzzer_test("tint_vertex_pulling_fuzzer") {
+      sources = [ "tint_vertex_pulling_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common" ]
+    }
+
     fuzzer_test("tint_wgsl_reader_spv_writer_fuzzer") {
       sources = [ "tint_wgsl_reader_spv_writer_fuzzer.cc" ]
       deps = [ ":tint_fuzzer_common" ]
@@ -196,6 +201,7 @@
         ":tint_renamer_fuzzer",
         ":tint_single_entry_point_fuzzer",
         ":tint_spirv_transform_fuzzer",
+        ":tint_vertex_pulling_fuzzer",
         ":tint_wgsl_reader_spv_writer_fuzzer",
       ]
     }
diff --git a/fuzzers/CMakeLists.txt b/fuzzers/CMakeLists.txt
index d5adea6..b5ef5bf 100644
--- a/fuzzers/CMakeLists.txt
+++ b/fuzzers/CMakeLists.txt
@@ -39,6 +39,7 @@
   add_tint_fuzzer(tint_renamer_fuzzer)
   add_tint_fuzzer(tint_single_entry_point_fuzzer)
   add_tint_fuzzer(tint_spirv_transform_fuzzer)
+  add_tint_fuzzer(tint_vertex_pulling_fuzzer)
   add_tint_fuzzer(tint_wgsl_reader_spv_writer_fuzzer)
 endif()
 
diff --git a/fuzzers/tint_all_transforms_fuzzer.cc b/fuzzers/tint_all_transforms_fuzzer.cc
index 2e9d94c..bb096f4 100644
--- a/fuzzers/tint_all_transforms_fuzzer.cc
+++ b/fuzzers/tint_all_transforms_fuzzer.cc
@@ -18,27 +18,17 @@
 namespace fuzzers {
 
 struct Config {
-  const uint8_t* data;
-  size_t size;
+  Config(const uint8_t* data, size_t size) : reader(data, size) {}
+  Reader reader;
   transform::Manager manager;
   transform::DataMap inputs;
 };
 
 bool AddPlatformIndependentPasses(Config* config) {
-  if (!ExtractFirstIndexOffsetInputs(&config->data, &config->size,
-                                     &config->inputs)) {
-    return false;
-  }
-
-  if (!ExtractBindingRemapperInputs(&config->data, &config->size,
-                                    &config->inputs)) {
-    return false;
-  }
-
-  if (!ExtractSingleEntryPointInputs(&config->data, &config->size,
-                                     &config->inputs)) {
-    return 0;
-  }
+  ExtractFirstIndexOffsetInputs(&config->reader, &config->inputs);
+  ExtractBindingRemapperInputs(&config->reader, &config->inputs);
+  ExtractSingleEntryPointInputs(&config->reader, &config->inputs);
+  ExtractVertexPullingInputs(&config->reader, &config->inputs);
 
   config->manager.Add<transform::BoundArrayAccessors>();
   config->manager
@@ -48,15 +38,14 @@
   config->manager.Add<transform::BindingRemapper>();
   config->manager.Add<transform::Renamer>();
   config->manager.Add<tint::transform::SingleEntryPoint>();
+  config->manager.Add<tint::transform::VertexPulling>();
 
-  return true;
+  return !config->reader.failed();
 }
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   {
-    Config config;
-    config.data = data;
-    config.size = size;
+    Config config(data, size);
 
     if (!AddPlatformIndependentPasses(&config)) {
       return 0;
@@ -65,14 +54,12 @@
     fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
     fuzzer.SetTransformManager(&(config.manager), std::move(config.inputs));
 
-    fuzzer.Run(config.data, config.size);
+    fuzzer.Run(config.reader.data(), config.reader.size());
   }
 
 #if TINT_BUILD_HLSL_WRITER
   {
-    Config config;
-    config.data = data;
-    config.size = size;
+    Config config(data, size);
 
     if (!AddPlatformIndependentPasses(&config)) {
       return 0;
@@ -83,15 +70,13 @@
     fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kHLSL);
     fuzzer.SetTransformManager(&config.manager, std::move(config.inputs));
 
-    fuzzer.Run(config.data, config.size);
+    fuzzer.Run(config.reader.data(), config.reader.size());
   }
 #endif  // TINT_BUILD_HLSL_WRITER
 
 #if TINT_BUILD_MSL_WRITER
   {
-    Config config;
-    config.data = data;
-    config.size = size;
+    Config config(data, size);
 
     if (!AddPlatformIndependentPasses(&config)) {
       return 0;
@@ -102,14 +87,12 @@
     fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kMSL);
     fuzzer.SetTransformManager(&config.manager, std::move(config.inputs));
 
-    fuzzer.Run(config.data, config.size);
+    fuzzer.Run(config.reader.data(), config.reader.size());
   }
 #endif  // TINT_BUILD_MSL_WRITER
 #if TINT_BUILD_SPV_WRITER
   {
-    Config config;
-    config.data = data;
-    config.size = size;
+    Config config(data, size);
 
     if (!AddPlatformIndependentPasses(&config)) {
       return 0;
@@ -120,7 +103,7 @@
     fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
     fuzzer.SetTransformManager(&config.manager, std::move(config.inputs));
 
-    fuzzer.Run(config.data, config.size);
+    fuzzer.Run(config.reader.data(), config.reader.size());
   }
 #endif  // TINT_BUILD_SPV_WRITER
 
diff --git a/fuzzers/tint_binding_remapper_fuzzer.cc b/fuzzers/tint_binding_remapper_fuzzer.cc
index 492174d..b1b566e 100644
--- a/fuzzers/tint_binding_remapper_fuzzer.cc
+++ b/fuzzers/tint_binding_remapper_fuzzer.cc
@@ -20,8 +20,10 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   transform::Manager transform_manager;
   transform::DataMap transform_inputs;
+  Reader r(data, size);
 
-  if (!ExtractBindingRemapperInputs(&data, &size, &transform_inputs)) {
+  ExtractBindingRemapperInputs(&r, &transform_inputs);
+  if (r.failed()) {
     return 0;
   }
 
@@ -30,7 +32,7 @@
   fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
   fuzzer.SetTransformManager(&transform_manager, std::move(transform_inputs));
 
-  return fuzzer.Run(data, size);
+  return fuzzer.Run(r.data(), r.size());
 }
 
 }  // namespace fuzzers
diff --git a/fuzzers/tint_common_fuzzer.cc b/fuzzers/tint_common_fuzzer.cc
index b47a327..38d4a7c 100644
--- a/fuzzers/tint_common_fuzzer.cc
+++ b/fuzzers/tint_common_fuzzer.cc
@@ -26,6 +26,8 @@
 namespace tint {
 namespace fuzzers {
 
+namespace {
+
 [[noreturn]] void TintInternalCompilerErrorReporter(
     const tint::diag::List& diagnostics) {
   auto printer = tint::diag::Printer::create(stderr, true);
@@ -37,41 +39,72 @@
   auto printer = tint::diag::Printer::create(stderr, true);
   printer->write(
       "Fuzzing detected valid input program being transformed into an invalid "
-      "output progam",
+      "output progam\n",
       {diag::Color::kRed, true});
   __builtin_trap();
 }
 
-bool ExtractBindingRemapperInputs(const uint8_t** data,
-                                  size_t* size,
-                                  tint::transform::DataMap* inputs) {
-  if ((*size) < sizeof(uint8_t)) {
-    return false;
+transform::VertexAttributeDescriptor ExtractVertexAttributeDescriptor(
+    Reader* r) {
+  transform::VertexAttributeDescriptor desc;
+  desc.format = r->enum_class<transform::VertexFormat>(
+      static_cast<uint8_t>(transform::VertexFormat::kLastEntry) + 1);
+  desc.offset = r->read<uint64_t>();
+  desc.shader_location = r->read<uint32_t>();
+  return desc;
+}
+
+transform::VertexBufferLayoutDescriptor ExtractVertexBufferLayoutDescriptor(
+    Reader* r) {
+  transform::VertexBufferLayoutDescriptor desc;
+  desc.array_stride = r->read<uint64_t>();
+  desc.step_mode = r->enum_class<transform::InputStepMode>(
+      static_cast<uint8_t>(transform::InputStepMode::kLastEntry) + 1);
+  desc.attributes = r->vector(ExtractVertexAttributeDescriptor);
+  return desc;
+}
+
+}  // namespace
+
+Reader::Reader(const uint8_t* data, size_t size) : data_(data), size_(size) {}
+
+std::string Reader::string() {
+  auto count = read<uint8_t>();
+  if (failed_ || size_ < count) {
+    mark_failed();
+    return "";
   }
+  std::string out(data_, data_ + count);
+  data_ += count;
+  size_ -= count;
+  return out;
+}
 
-  auto count = *reinterpret_cast<const uint8_t*>(*data);
-  (*data) += sizeof(uint8_t);
-  (*size) -= sizeof(uint8_t);
+void Reader::mark_failed() {
+  size_ = 0;
+  failed_ = true;
+}
 
+void Reader::read(void* out, size_t n) {
+  if (n > size_) {
+    mark_failed();
+    return;
+  }
+  memcpy(&out, data_, n);
+  data_ += n;
+  size_ -= n;
+}
+
+void ExtractBindingRemapperInputs(Reader* r, tint::transform::DataMap* inputs) {
   struct Config {
-    uint32_t old_group;
-    uint32_t old_binding;
-    uint32_t new_group;
-    uint32_t new_binding;
+    uint8_t old_group;
+    uint8_t old_binding;
+    uint8_t new_group;
+    uint8_t new_binding;
     ast::AccessControl::Access new_ac;
   };
 
-  if ((*size) < count * sizeof(Config)) {
-    return false;
-  }
-
-  std::vector<Config> configs(count);
-
-  memcpy(configs.data(), *data, count * sizeof(Config));
-
-  (*data) += count * sizeof(Config);
-  (*size) -= count * sizeof(Config);
-
+  std::vector<Config> configs = r->vector<Config>();
   transform::BindingRemapper::BindingPoints binding_points;
   transform::BindingRemapper::AccessControls access_controls;
   for (const auto& config : configs) {
@@ -82,59 +115,33 @@
 
   inputs->Add<transform::BindingRemapper::Remappings>(binding_points,
                                                       access_controls);
-
-  return true;
 }
 
-bool ExtractFirstIndexOffsetInputs(const uint8_t** data,
-                                   size_t* size,
+void ExtractFirstIndexOffsetInputs(Reader* r,
                                    tint::transform::DataMap* inputs) {
   struct Config {
     uint32_t group;
     uint32_t binding;
   };
 
-  if ((*size) < sizeof(Config)) {
-    return false;
-  }
-
-  Config config;
-  memcpy(&config, data, sizeof(config));
-
-  (*data) += sizeof(Config);
-  (*size) -= sizeof(Config);
-
+  Config config = r->read<Config>();
   inputs->Add<tint::transform::FirstIndexOffset::BindingPoint>(config.binding,
                                                                config.group);
-
-  return true;
 }
 
-bool ExtractSingleEntryPointInputs(const uint8_t** data,
-                                   size_t* size,
+void ExtractSingleEntryPointInputs(Reader* r,
                                    tint::transform::DataMap* inputs) {
-  if ((*size) < sizeof(uint8_t)) {
-    return false;
-  }
-
-  auto count = *reinterpret_cast<const uint8_t*>(*data);
-  (*data) += sizeof(uint8_t);
-  (*size) -= sizeof(uint8_t);
-
-  if ((*size) < count) {
-    return false;
-  }
-
-  auto* c = reinterpret_cast<const char*>(*data);
-  std::string input(c, c + count);
-
-  (*data) += count * sizeof(char);
-  (*size) -= count * sizeof(char);
-
+  std::string input = r->string();
   transform::SingleEntryPoint::Config cfg(input);
   inputs->Add<transform::SingleEntryPoint::Config>(cfg);
+}
 
-  return true;
+void ExtractVertexPullingInputs(Reader* r, tint::transform::DataMap* inputs) {
+  transform::VertexPulling::Config cfg;
+  cfg.entry_point_name = r->string();
+  cfg.vertex_state = r->vector(ExtractVertexBufferLayoutDescriptor);
+  cfg.pulling_group = r->read<uint32_t>();
+  inputs->Add<transform::VertexPulling::Config>(cfg);
 }
 
 CommonFuzzer::CommonFuzzer(InputFormat input, OutputFormat output)
@@ -158,7 +165,6 @@
 #if TINT_BUILD_WGSL_READER
     case InputFormat::kWGSL: {
       std::string str(reinterpret_cast<const char*>(data), size);
-
       file = std::make_unique<Source::File>("test.wgsl", str);
       program = reader::wgsl::Parse(file.get());
       break;
diff --git a/fuzzers/tint_common_fuzzer.h b/fuzzers/tint_common_fuzzer.h
index 7c3cd6b..3b9c137 100644
--- a/fuzzers/tint_common_fuzzer.h
+++ b/fuzzers/tint_common_fuzzer.h
@@ -15,23 +15,80 @@
 #ifndef FUZZERS_TINT_COMMON_FUZZER_H_
 #define FUZZERS_TINT_COMMON_FUZZER_H_
 
+#include <string>
 #include <utility>
+#include <vector>
 
 #include "include/tint/tint.h"
 
 namespace tint {
 namespace fuzzers {
 
-bool ExtractBindingRemapperInputs(const uint8_t** data,
-                                  size_t* size,
-                                  tint::transform::DataMap* inputs);
-bool ExtractFirstIndexOffsetInputs(const uint8_t** data,
-                                   size_t* size,
-                                   tint::transform::DataMap* inputs);
+class Reader {
+ public:
+  Reader(const uint8_t* data, size_t size);
 
-bool ExtractSingleEntryPointInputs(const uint8_t** data,
-                                   size_t* size,
-                                   tint::transform::DataMap* inputs);
+  bool failed() { return failed_; }
+  const uint8_t* data() { return data_; }
+  size_t size() { return size_; }
+
+  template <typename T>
+  T read() {
+    T out{};
+    read(&out, sizeof(T));
+    return out;
+  }
+
+  std::string string();
+
+  template <typename T>
+  std::vector<T> vector() {
+    auto count = read<uint8_t>();
+    if (failed_ || size_ < count) {
+      mark_failed();
+      return {};
+    }
+    std::vector<T> out(count);
+    memcpy(out.data(), data_, count * sizeof(T));
+    data_ += count * sizeof(T);
+    size_ -= count * sizeof(T);
+    return out;
+  }
+
+  template <typename T>
+  std::vector<T> vector(T (*extract)(Reader*)) {
+    auto count = read<uint8_t>();
+    if (size_ < count) {
+      mark_failed();
+      return {};
+    }
+    std::vector<T> out(count);
+    for (uint8_t i = 0; i < count; i++) {
+      out[i] = extract(this);
+    }
+    return out;
+  }
+  template <typename T>
+  T enum_class(uint8_t count) {
+    auto val = read<uint8_t>();
+    return static_cast<T>(val % count);
+  }
+
+ private:
+  void mark_failed();
+  void read(void* out, size_t n);
+
+  const uint8_t* data_;
+  size_t size_;
+  bool failed_ = false;
+};
+
+void ExtractBindingRemapperInputs(Reader* r, tint::transform::DataMap* inputs);
+void ExtractFirstIndexOffsetInputs(Reader* r, tint::transform::DataMap* inputs);
+
+void ExtractSingleEntryPointInputs(Reader* r, tint::transform::DataMap* inputs);
+
+void ExtractVertexPullingInputs(Reader* r, tint::transform::DataMap* inputs);
 
 enum class InputFormat { kWGSL, kSpv, kNone };
 
diff --git a/fuzzers/tint_first_index_offset_fuzzer.cc b/fuzzers/tint_first_index_offset_fuzzer.cc
index 8f441f1..78d6164 100644
--- a/fuzzers/tint_first_index_offset_fuzzer.cc
+++ b/fuzzers/tint_first_index_offset_fuzzer.cc
@@ -20,8 +20,10 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   tint::transform::Manager transform_manager;
   tint::transform::DataMap transform_inputs;
+  Reader r(data, size);
 
-  if (!ExtractFirstIndexOffsetInputs(&data, &size, &transform_inputs)) {
+  ExtractFirstIndexOffsetInputs(&r, &transform_inputs);
+  if (r.failed()) {
     return 0;
   }
 
@@ -30,7 +32,7 @@
   tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
   fuzzer.SetTransformManager(&transform_manager, std::move(transform_inputs));
 
-  return fuzzer.Run(data, size);
+  return fuzzer.Run(r.data(), r.size());
 }
 
 }  // namespace fuzzers
diff --git a/fuzzers/tint_single_entry_point_fuzzer.cc b/fuzzers/tint_single_entry_point_fuzzer.cc
index 906dac4..41a6aa3 100644
--- a/fuzzers/tint_single_entry_point_fuzzer.cc
+++ b/fuzzers/tint_single_entry_point_fuzzer.cc
@@ -20,8 +20,10 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   tint::transform::Manager transform_manager;
   tint::transform::DataMap transform_inputs;
+  Reader r(data, size);
 
-  if (!ExtractSingleEntryPointInputs(&data, &size, &transform_inputs)) {
+  ExtractSingleEntryPointInputs(&r, &transform_inputs);
+  if (r.failed()) {
     return 0;
   }
 
@@ -30,7 +32,7 @@
   tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
   fuzzer.SetTransformManager(&transform_manager, std::move(transform_inputs));
 
-  return fuzzer.Run(data, size);
+  return fuzzer.Run(r.data(), r.size());
 }
 
 }  // namespace fuzzers
diff --git a/fuzzers/tint_vertex_pulling_fuzzer.cc b/fuzzers/tint_vertex_pulling_fuzzer.cc
new file mode 100644
index 0000000..62a68ab
--- /dev/null
+++ b/fuzzers/tint_vertex_pulling_fuzzer.cc
@@ -0,0 +1,42 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// TODO(tint:753): Remove this fuzzer once that transform is only
+// being used from sanitizers.
+
+#include "fuzzers/tint_common_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  tint::transform::Manager transform_manager;
+  tint::transform::DataMap transform_inputs;
+  Reader r(data, size);
+
+  ExtractVertexPullingInputs(&r, &transform_inputs);
+  if (r.failed()) {
+    return 0;
+  }
+
+  transform_manager.Add<tint::transform::VertexPulling>();
+
+  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
+  fuzzer.SetTransformManager(&transform_manager, {});
+
+  return fuzzer.Run(r.data(), r.size());
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
index 88b16a4..31523b4 100644
--- a/src/transform/vertex_pulling.cc
+++ b/src/transform/vertex_pulling.cc
@@ -28,6 +28,7 @@
 
 namespace tint {
 namespace transform {
+
 namespace {
 
 struct State {
diff --git a/src/transform/vertex_pulling.h b/src/transform/vertex_pulling.h
index 7dded37..15ea211 100644
--- a/src/transform/vertex_pulling.h
+++ b/src/transform/vertex_pulling.h
@@ -56,12 +56,13 @@
   kI32,
   kVec2I32,
   kVec3I32,
-  kVec4I32
+  kVec4I32,
+  kLastEntry = kVec4I32
 };
 
 /// Describes if a vertex attributes increments with vertex index or instance
 /// index
-enum class InputStepMode { kVertex, kInstance };
+enum class InputStepMode { kVertex, kInstance, kLastEntry = kInstance };
 
 /// Describes a vertex attribute within a buffer
 struct VertexAttributeDescriptor {