Check that certain decorations cannot be used more than once and/or are mutually exclusive (#2171)

Fixes #1636

* Add a hash functor for decoration types for c++11 compliance
* Change non-POD static variables and add test for Block+BufferBlock

diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index f12c30f..582d11c 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -17,7 +17,9 @@
 #include <algorithm>
 #include <cassert>
 #include <string>
+#include <tuple>
 #include <unordered_map>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -44,6 +46,13 @@
   }
 };
 
+// A functor for hashing decoration types.
+struct SpvDecorationHash {
+  std::size_t operator()(SpvDecoration dec) const {
+    return static_cast<std::size_t>(dec);
+  }
+};
+
 // Struct member layout attributes that are inherited through arrays.
 struct LayoutConstraints {
   explicit LayoutConstraints(
@@ -941,6 +950,109 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t CheckDecorationsCompatibility(ValidationState_t& vstate) {
+  using AtMostOnceSet = std::unordered_set<SpvDecoration, SpvDecorationHash>;
+  using MutuallyExclusiveSets =
+      std::vector<std::unordered_set<SpvDecoration, SpvDecorationHash>>;
+  using PerIDKey = std::tuple<SpvDecoration, uint32_t>;
+  using PerMemberKey = std::tuple<SpvDecoration, uint32_t, uint32_t>;
+  using DecorationNameTable =
+      std::unordered_map<SpvDecoration, std::string, SpvDecorationHash>;
+
+  static const auto* const at_most_once_per_id = new AtMostOnceSet{
+      SpvDecorationArrayStride,
+  };
+  static const auto* const at_most_once_per_member = new AtMostOnceSet{
+      SpvDecorationOffset,
+      SpvDecorationMatrixStride,
+      SpvDecorationRowMajor,
+      SpvDecorationColMajor,
+  };
+  static const auto* const mutually_exclusive_per_id =
+      new MutuallyExclusiveSets{
+          {SpvDecorationBlock, SpvDecorationBufferBlock},
+      };
+  static const auto* const mutually_exclusive_per_member =
+      new MutuallyExclusiveSets{
+          {SpvDecorationRowMajor, SpvDecorationColMajor},
+      };
+  // For printing the decoration name.
+  static const auto* const decoration_name = new DecorationNameTable{
+      {SpvDecorationArrayStride, "ArrayStride"},
+      {SpvDecorationOffset, "Offset"},
+      {SpvDecorationMatrixStride, "MatrixStride"},
+      {SpvDecorationRowMajor, "RowMajor"},
+      {SpvDecorationColMajor, "ColMajor"},
+      {SpvDecorationBlock, "Block"},
+      {SpvDecorationBufferBlock, "BufferBlock"},
+  };
+
+  std::set<PerIDKey> seen_per_id;
+  std::set<PerMemberKey> seen_per_member;
+
+  for (const auto& inst : vstate.ordered_instructions()) {
+    const auto& words = inst.words();
+    if (SpvOpDecorate == inst.opcode()) {
+      const auto id = words[1];
+      const auto dec_type = static_cast<SpvDecoration>(words[2]);
+      const auto k = PerIDKey(dec_type, id);
+      const auto already_used = !seen_per_id.insert(k).second;
+      if (already_used &&
+          at_most_once_per_id->find(dec_type) != at_most_once_per_id->end()) {
+        return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+               << "ID '" << id << "' decorated with "
+               << decoration_name->at(dec_type)
+               << " multiple times is not allowed.";
+      }
+      // Verify certain mutually exclusive decorations are not both applied on
+      // an ID.
+      for (const auto& s : *mutually_exclusive_per_id) {
+        if (s.find(dec_type) == s.end()) continue;
+        for (auto excl_dec_type : s) {
+          if (excl_dec_type == dec_type) continue;
+          const auto excl_k = PerIDKey(excl_dec_type, id);
+          if (seen_per_id.find(excl_k) != seen_per_id.end()) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                   << "ID '" << id << "' decorated with both "
+                   << decoration_name->at(dec_type) << " and "
+                   << decoration_name->at(excl_dec_type) << " is not allowed.";
+          }
+        }
+      }
+    } else if (SpvOpMemberDecorate == inst.opcode()) {
+      const auto id = words[1];
+      const auto member_id = words[2];
+      const auto dec_type = static_cast<SpvDecoration>(words[3]);
+      const auto k = PerMemberKey(dec_type, id, member_id);
+      const auto already_used = !seen_per_member.insert(k).second;
+      if (already_used && at_most_once_per_member->find(dec_type) !=
+                              at_most_once_per_member->end()) {
+        return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+               << "ID '" << id << "', member '" << member_id
+               << "' decorated with " << decoration_name->at(dec_type)
+               << " multiple times is not allowed.";
+      }
+      // Verify certain mutually exclusive decorations are not both applied on
+      // a (ID, member) tuple.
+      for (const auto& s : *mutually_exclusive_per_member) {
+        if (s.find(dec_type) == s.end()) continue;
+        for (auto excl_dec_type : s) {
+          if (excl_dec_type == dec_type) continue;
+          const auto excl_k = PerMemberKey(excl_dec_type, id, member_id);
+          if (seen_per_member.find(excl_k) != seen_per_member.end()) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                   << "ID '" << id << "', member '" << member_id
+                   << "' decorated with both " << decoration_name->at(dec_type)
+                   << " and " << decoration_name->at(excl_dec_type)
+                   << " is not allowed.";
+          }
+        }
+      }
+    }
+  }
+  return SPV_SUCCESS;
+}
+
 spv_result_t CheckVulkanMemoryModelDeprecatedDecorations(
     ValidationState_t& vstate) {
   if (vstate.memory_model() != SpvMemoryModelVulkanKHR) return SPV_SUCCESS;
@@ -1108,6 +1220,7 @@
   if (auto error = CheckImportedVariableInitialization(vstate)) return error;
   if (auto error = CheckDecorationsOfEntryPoints(vstate)) return error;
   if (auto error = CheckDecorationsOfBuffers(vstate)) return error;
+  if (auto error = CheckDecorationsCompatibility(vstate)) return error;
   if (auto error = CheckLinkageAttrOfFunctions(vstate)) return error;
   if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
     return error;
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 6db3d12..3a4320d 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -4590,6 +4590,226 @@
                         "  %call = OpFunctionCall %void %myfunc"));
 }
 
+TEST_F(ValidateDecorations, MultipleOffsetDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 Offset 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with Offset multiple "
+                        "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleArrayStrideDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %array ArrayStride 4
+            OpDecorate %array ArrayStride 4
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+  %uint_4 = OpConstant %uint 4
+   %array = OpTypeArray %float %uint_4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2' decorated with ArrayStride multiple "
+                        "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleMatrixStrideDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 ColMajor
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 MatrixStride 16
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with MatrixStride "
+                        "multiple times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleRowMajorDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 RowMajor
+            OpMemberDecorate %struct 0 RowMajor
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with RowMajor multiple "
+                        "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleColMajorDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 ColMajor
+            OpMemberDecorate %struct 0 ColMajor
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with ColMajor multiple "
+                        "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, RowMajorAndColMajorDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 ColMajor
+            OpMemberDecorate %struct 0 RowMajor
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with both RowMajor and "
+                        "ColMajor is not allowed."));
+}
+
+TEST_F(ValidateDecorations, BlockAndBufferBlockDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpDecorate %struct BufferBlock
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 RowMajor
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "ID '2' decorated with both BufferBlock and Block is not allowed."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools