spv: reject invalid SPIR-V

Use the SPIRV-Tools validator, with the WebGPU0 environment.

Bug: tint:3
Change-Id: Id1132d209fd939ed68587034761e97da9b35b21d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/16821
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index df73e33..93001a6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -319,6 +319,7 @@
 if(${TINT_BUILD_SPV_PARSER})
   list (APPEND TINT_TEST_SRCS
     reader/spv/fail_stream_test.cc
+    reader/spv/parser_impl_test.cc
     reader/spv/parser_test.cc
   )
 endif()
diff --git a/src/reader/spv/parser_impl.cc b/src/reader/spv/parser_impl.cc
index f06ae08..560f3d1 100644
--- a/src/reader/spv/parser_impl.cc
+++ b/src/reader/spv/parser_impl.cc
@@ -14,6 +14,7 @@
 
 #include <cstring>
 
+#include "spirv-tools/libspirv.hpp"
 #include "src/reader/spv/parser_impl.h"
 
 namespace tint {
@@ -26,9 +27,33 @@
 ParserImpl::~ParserImpl() = default;
 
 bool ParserImpl::Parse() {
-  // Exit early if we've already failed.
+  if (!success_) {
+    return false;
+  }
+
+  // Set up use of SPIRV-Tools utilities.
+  // TODO(dneto): Add option to handle other environments.
+  spvtools::SpirvTools spv_tools(SPV_ENV_WEBGPU_0);
+
+  // Error messages from SPIRV-Tools are forwarded as failures.
+  auto message_consumer =
+      [this](spv_message_level_t level, const char* /*source*/,
+             const spv_position_t& position, const char* message) {
+        switch (level) {
+          // Drop info and warning message.
+          case SPV_MSG_WARNING:
+          case SPV_MSG_INFO:
+          default:
+            // For binary validation errors, we only have the instruction
+            // number.  It's not text, so there is no column number.
+            this->Fail() << "line:" << position.index << ": " << message;
+        }
+      };
+  spv_tools.SetMessageConsumer(message_consumer);
+
+  // Only consider valid modules.
   if (success_) {
-    Fail() << "SPIR-V parsing is not supported yet";
+    success_ = spv_tools.Validate(spv_binary_);
   }
 
   return success_;
diff --git a/src/reader/spv/parser_impl.h b/src/reader/spv/parser_impl.h
index ac5a38b..33c84c2 100644
--- a/src/reader/spv/parser_impl.h
+++ b/src/reader/spv/parser_impl.h
@@ -33,6 +33,7 @@
   /// Creates a new parser
   /// @param input the input data to parse
   explicit ParserImpl(const std::vector<uint32_t>& input);
+  /// Destructor
   ~ParserImpl() override;
 
   /// Run the parser
diff --git a/src/reader/spv/parser_impl_test.cc b/src/reader/spv/parser_impl_test.cc
new file mode 100644
index 0000000..9881fa3
--- /dev/null
+++ b/src/reader/spv/parser_impl_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2020 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.
+
+#include "src/reader/spv/parser_impl.h"
+
+#include <cstdint>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace tint {
+namespace reader {
+namespace spv {
+
+namespace {
+
+using ::testing::HasSubstr;
+
+using SpvParserImplTest = testing::Test;
+
+TEST_F(SpvParserImplTest, Uint32VecEmpty) {
+  std::vector<uint32_t> data;
+  ParserImpl p{data};
+  EXPECT_FALSE(p.Parse());
+  // TODO(dneto): What message?
+}
+
+/// @returns the SPIR-V module assembled from the given text.  Numeric IDs
+/// are preserved.
+std::vector<uint32_t> Assemble(const std::string& spirv_assembly) {
+  // TODO(dneto): Use ScopedTrace?
+
+  // (The target environment doesn't affect assembly.
+  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+  std::stringstream errors;
+  std::vector<uint32_t> result;
+  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
+                                     const spv_position_t& position,
+                                     const char* message) {
+    errors << "assembly error:" << position.line << ":" << position.column
+           << ": " << message;
+  });
+
+  const auto success = tools.Assemble(
+      spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_TRUE(success) << errors.str();
+
+  return result;
+}
+
+TEST_F(SpvParserImplTest, InvalidModuleFails) {
+  auto invalid_spv = Assemble("%ty = OpTypeInt 3 0");
+  ParserImpl p{invalid_spv};
+  EXPECT_FALSE(p.Parse());
+  EXPECT_THAT(
+      p.error(),
+      HasSubstr("TypeInt cannot appear before the memory model instruction"));
+  EXPECT_THAT(p.error(), HasSubstr("OpTypeInt 3 0"));
+}
+
+// TODO(dneto): uint32 vec, valid SPIR-V
+
+}  // namespace
+
+}  // namespace spv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/reader/spv/parser_test.cc b/src/reader/spv/parser_test.cc
index 717034b..035f3c8 100644
--- a/src/reader/spv/parser_test.cc
+++ b/src/reader/spv/parser_test.cc
@@ -14,8 +14,7 @@
 
 #include "src/reader/spv/parser.h"
 
-#include <stdint.h>
-
+#include <cstdint>
 #include <vector>
 
 #include "gtest/gtest.h"