Add validation for invalid layout decoration usage (#6012)

* Checks that Block, BufferBlock, Offset, ArrayStride, and MatrixStride
  are not used in a Vulkan environment in disallowed storage classes
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index fdbc151..d5d0c92 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -2083,6 +2083,197 @@
   return SPV_SUCCESS;
 }
 
+bool AllowsLayout(ValidationState_t& vstate, const spv::StorageClass sc) {
+  switch (sc) {
+    case spv::StorageClass::StorageBuffer:
+    case spv::StorageClass::Uniform:
+    case spv::StorageClass::PhysicalStorageBuffer:
+    case spv::StorageClass::PushConstant:
+      // Always explicitly laid out.
+      return true;
+    case spv::StorageClass::UniformConstant:
+      return false;
+    case spv::StorageClass::Workgroup:
+      return vstate.HasCapability(
+          spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
+    case spv::StorageClass::Function:
+    case spv::StorageClass::Private:
+      return vstate.version() < SPV_SPIRV_VERSION_WORD(1, 4);
+    case spv::StorageClass::Input:
+    case spv::StorageClass::Output:
+      // Block is used generally and mesh shaders use Offset.
+      return true;
+    default:
+      // TODO: Some storage classes in ray tracing use explicit layout
+      // decorations, but it is not well documented which. For now treat other
+      // storage classes as allowed to be laid out. See Vulkan internal issue
+      // 4192.
+      return true;
+  }
+}
+
+bool UsesExplicitLayout(ValidationState_t& vstate, uint32_t type_id,
+                        std::unordered_map<uint32_t, bool>& cache) {
+  if (type_id == 0) {
+    return false;
+  }
+
+  if (cache.count(type_id)) {
+    return cache[type_id];
+  }
+
+  bool res = false;
+  const auto type_inst = vstate.FindDef(type_id);
+  if (type_inst->opcode() == spv::Op::OpTypeStruct ||
+      type_inst->opcode() == spv::Op::OpTypeArray ||
+      type_inst->opcode() == spv::Op::OpTypeRuntimeArray ||
+      type_inst->opcode() == spv::Op::OpTypePointer ||
+      type_inst->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
+    const auto& id_decs = vstate.id_decorations();
+    const auto iter = id_decs.find(type_id);
+    if (iter != id_decs.end()) {
+      res = std::any_of(iter->second.begin(), iter->second.end(),
+                        [](const Decoration& d) {
+                          return d.dec_type() == spv::Decoration::Block ||
+                                 d.dec_type() == spv::Decoration::BufferBlock ||
+                                 d.dec_type() == spv::Decoration::Offset ||
+                                 d.dec_type() == spv::Decoration::ArrayStride ||
+                                 d.dec_type() == spv::Decoration::MatrixStride;
+                        });
+    }
+
+    if (!res) {
+      switch (type_inst->opcode()) {
+        case spv::Op::OpTypeStruct:
+          for (uint32_t i = 1; !res && i < type_inst->operands().size(); i++) {
+            res = UsesExplicitLayout(
+                vstate, type_inst->GetOperandAs<uint32_t>(i), cache);
+          }
+          break;
+        case spv::Op::OpTypeArray:
+        case spv::Op::OpTypeRuntimeArray:
+          res = UsesExplicitLayout(vstate, type_inst->GetOperandAs<uint32_t>(1),
+                                   cache);
+          break;
+        case spv::Op::OpTypePointer: {
+          const auto sc = type_inst->GetOperandAs<spv::StorageClass>(1);
+          if (!AllowsLayout(vstate, sc)) {
+            res = UsesExplicitLayout(
+                vstate, type_inst->GetOperandAs<uint32_t>(2), cache);
+          }
+        }
+        default:
+          break;
+      }
+    }
+  }
+
+  cache[type_id] = res;
+  return res;
+}
+
+spv_result_t CheckInvalidVulkanExplicitLayout(ValidationState_t& vstate) {
+  if (!spvIsVulkanEnv(vstate.context()->target_env)) {
+    return SPV_SUCCESS;
+  }
+
+  std::unordered_map<uint32_t, bool> cache;
+  for (const auto& inst : vstate.ordered_instructions()) {
+    const auto type_id = inst.type_id();
+    const auto type_inst = vstate.FindDef(type_id);
+    uint32_t fail_id = 0;
+    // Variables are the main place to check for improper decorations, but some
+    // untyped pointer instructions must also be checked since those types may
+    // never be instantiated by a variable. Unlike verifying a valid layout,
+    // physical storage buffer does not need checked here since it is always
+    // explicitly laid out.
+    switch (inst.opcode()) {
+      case spv::Op::OpVariable:
+      case spv::Op::OpUntypedVariableKHR: {
+        const auto sc = inst.GetOperandAs<spv::StorageClass>(2);
+        auto check_id = type_id;
+        if (inst.opcode() == spv::Op::OpUntypedVariableKHR) {
+          if (inst.operands().size() > 3) {
+            check_id = inst.GetOperandAs<uint32_t>(3);
+          }
+        }
+        if (!AllowsLayout(vstate, sc) &&
+            UsesExplicitLayout(vstate, check_id, cache)) {
+          fail_id = check_id;
+        }
+        break;
+      }
+      case spv::Op::OpUntypedAccessChainKHR:
+      case spv::Op::OpUntypedInBoundsAccessChainKHR:
+      case spv::Op::OpUntypedPtrAccessChainKHR:
+      case spv::Op::OpUntypedInBoundsPtrAccessChainKHR: {
+        // Check both the base type and return type. The return type may have an
+        // invalid array stride.
+        const auto sc = type_inst->GetOperandAs<spv::StorageClass>(1);
+        const auto base_type_id = inst.GetOperandAs<uint32_t>(2);
+        if (!AllowsLayout(vstate, sc)) {
+          if (UsesExplicitLayout(vstate, base_type_id, cache)) {
+            fail_id = base_type_id;
+          } else if (UsesExplicitLayout(vstate, type_id, cache)) {
+            fail_id = type_id;
+          }
+        }
+        break;
+      }
+      case spv::Op::OpUntypedArrayLengthKHR: {
+        // Check the data type.
+        const auto ptr_ty_id =
+            vstate.FindDef(inst.GetOperandAs<uint32_t>(3))->type_id();
+        const auto ptr_ty = vstate.FindDef(ptr_ty_id);
+        const auto sc = ptr_ty->GetOperandAs<spv::StorageClass>(1);
+        const auto base_type_id = inst.GetOperandAs<uint32_t>(2);
+        if (!AllowsLayout(vstate, sc) &&
+            UsesExplicitLayout(vstate, base_type_id, cache)) {
+          fail_id = base_type_id;
+        }
+        break;
+      }
+      case spv::Op::OpLoad: {
+        const auto ptr_id = inst.GetOperandAs<uint32_t>(2);
+        const auto ptr_type = vstate.FindDef(vstate.FindDef(ptr_id)->type_id());
+        if (ptr_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
+          // For untyped pointers check the return type for an invalid layout.
+          const auto sc = ptr_type->GetOperandAs<spv::StorageClass>(1);
+          if (!AllowsLayout(vstate, sc) &&
+              UsesExplicitLayout(vstate, type_id, cache)) {
+            fail_id = type_id;
+          }
+        }
+        break;
+      }
+      case spv::Op::OpStore: {
+        const auto ptr_id = inst.GetOperandAs<uint32_t>(1);
+        const auto ptr_type = vstate.FindDef(vstate.FindDef(ptr_id)->type_id());
+        if (ptr_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
+          // For untyped pointers, check the type of the data operand for an
+          // invalid layout.
+          const auto sc = ptr_type->GetOperandAs<spv::StorageClass>(1);
+          const auto data_type_id = vstate.GetOperandTypeId(&inst, 2);
+          if (!AllowsLayout(vstate, sc) &&
+              UsesExplicitLayout(vstate, data_type_id, cache)) {
+            fail_id = inst.GetOperandAs<uint32_t>(2);
+          }
+        }
+        break;
+      }
+      default:
+        break;
+    }
+    if (fail_id != 0) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Invalid explicit layout decorations on type for operand "
+             << vstate.getIdName(fail_id);
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 spv_result_t ValidateDecorations(ValidationState_t& vstate) {
@@ -2094,6 +2285,7 @@
   if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
     return error;
   if (auto error = CheckDecorationsFromDecoration(vstate)) return error;
+  if (auto error = CheckInvalidVulkanExplicitLayout(vstate)) return error;
   return SPV_SUCCESS;
 }
 
diff --git a/test/fuzz/transformation_set_memory_operands_mask_test.cpp b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
index 44901f9..d08b98c 100644
--- a/test/fuzz/transformation_set_memory_operands_mask_test.cpp
+++ b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
@@ -333,10 +333,12 @@
           %3 = OpTypeFunction %2
           %6 = OpTypeFloat 32
           %7 = OpTypeStruct %6 %6 %6
+      %1001 = OpTypeStruct %6 %6 %6
           %8 = OpTypeInt 32 0
           %9 = OpConstant %8 12
          %10 = OpTypeArray %7 %9
-         %11 = OpTypePointer Private %10
+     %1002 = OpTypeArray %1001 %9
+         %11 = OpTypePointer Private %1002
          %12 = OpVariable %11 Private
          %15 = OpTypeStruct %10 %7
          %16 = OpTypePointer Uniform %15
@@ -344,26 +346,32 @@
          %18 = OpTypeInt 32 1
          %19 = OpConstant %18 0
          %20 = OpTypePointer Uniform %10
-         %24 = OpTypePointer Private %7
+         %24 = OpTypePointer Private %1001
          %27 = OpTypePointer Private %6
          %30 = OpConstant %18 1
-        %132 = OpTypePointer Function %10
+        %132 = OpTypePointer Function %1002
         %135 = OpTypePointer Uniform %7
-        %145 = OpTypePointer Function %7
+        %145 = OpTypePointer Function %1001
           %4 = OpFunction %2 None %3
           %5 = OpLabel
         %133 = OpVariable %132 Function
          %21 = OpAccessChain %20 %17 %19
-               OpCopyMemory %12 %21 Aligned 16 Nontemporal|Aligned 16
+    %1003 = OpLoad %10 %21 Nontemporal|Aligned 16
+       %1004 = OpCopyLogical %1002 %1003
+               OpStore %12 %1004 Aligned 16
                OpCopyMemory %133 %12 Volatile
                OpCopyMemory %133 %12
                OpCopyMemory %133 %12
         %136 = OpAccessChain %135 %17 %30
         %138 = OpAccessChain %24 %12 %19
-               OpCopyMemory %138 %136 None Aligned 16
-               OpCopyMemory %138 %136 Aligned 16
+   %1005 = OpLoad %7 %136 Aligned 16
+      %1006 = OpCopyLogical %1001 %1005
+               OpStore %138 %1006
+  %1007 = OpLoad %7 %136
+      %1008 = OpCopyLogical %1001 %1007
+               OpStore %138 %1008 Aligned 16
         %146 = OpAccessChain %145 %133 %30
-        %147 = OpLoad %7 %146 Volatile|Nontemporal|Aligned 16
+        %147 = OpLoad %1001 %146 Volatile|Nontemporal|Aligned 16
         %148 = OpAccessChain %24 %12 %19
                OpStore %148 %147 Nontemporal
                OpReturn
@@ -382,14 +390,14 @@
         MakeUnique<FactManager>(context.get()), validator_options);
     {
       TransformationSetMemoryOperandsMask transformation(
-          MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0),
+          MakeInstructionDescriptor(21, spv::Op::OpLoad, 0),
           (uint32_t)spv::MemoryAccessMask::Aligned |
               (uint32_t)spv::MemoryAccessMask::Volatile,
-          1);
+          0);
       // Bad: cannot remove aligned
       ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                       MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0),
-                       (uint32_t)spv::MemoryAccessMask::Volatile, 1)
+                       MakeInstructionDescriptor(21, spv::Op::OpLoad, 0),
+                       (uint32_t)spv::MemoryAccessMask::Volatile, 0)
                        .IsApplicable(context.get(), transformation_context));
       ASSERT_TRUE(
           transformation.IsApplicable(context.get(), transformation_context));
@@ -399,13 +407,13 @@
 
     {
       TransformationSetMemoryOperandsMask transformation(
-          MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1),
+          MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0),
           (uint32_t)spv::MemoryAccessMask::Nontemporal |
               (uint32_t)spv::MemoryAccessMask::Volatile,
           1);
       // Bad: cannot remove volatile
       ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                       MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1),
+                       MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0),
                        (uint32_t)spv::MemoryAccessMask::Nontemporal, 0)
                        .IsApplicable(context.get(), transformation_context));
       ASSERT_TRUE(
@@ -417,7 +425,7 @@
     {
       // Creates the first operand.
       TransformationSetMemoryOperandsMask transformation(
-          MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 2),
+          MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1),
           (uint32_t)spv::MemoryAccessMask::Nontemporal |
               (uint32_t)spv::MemoryAccessMask::Volatile,
           0);
@@ -430,7 +438,7 @@
     {
       // Creates both operands.
       TransformationSetMemoryOperandsMask transformation(
-          MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 3),
+          MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 2),
           (uint32_t)spv::MemoryAccessMask::Nontemporal |
               (uint32_t)spv::MemoryAccessMask::Volatile,
           1);
@@ -442,13 +450,13 @@
 
     {
       TransformationSetMemoryOperandsMask transformation(
-          MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 0),
+          MakeInstructionDescriptor(138, spv::Op::OpLoad, 0),
           (uint32_t)spv::MemoryAccessMask::Aligned |
               (uint32_t)spv::MemoryAccessMask::Nontemporal,
-          1);
+          0);
       // Bad: the first mask is None, so Aligned cannot be added to it.
       ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                       MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 0),
+                       MakeInstructionDescriptor(138, spv::Op::OpStore, 0),
                        (uint32_t)spv::MemoryAccessMask::Aligned |
                            (uint32_t)spv::MemoryAccessMask::Nontemporal,
                        0)
@@ -461,8 +469,8 @@
 
     {
       TransformationSetMemoryOperandsMask transformation(
-          MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 1),
-          (uint32_t)spv::MemoryAccessMask::Volatile, 1);
+          MakeInstructionDescriptor(138, spv::Op::OpLoad, 1),
+          (uint32_t)spv::MemoryAccessMask::Volatile, 0);
       ASSERT_TRUE(
           transformation.IsApplicable(context.get(), transformation_context));
       ApplyAndCheckFreshIds(transformation, context.get(),
@@ -522,10 +530,12 @@
             %3 = OpTypeFunction %2
             %6 = OpTypeFloat 32
             %7 = OpTypeStruct %6 %6 %6
+         %1001 = OpTypeStruct %6 %6 %6
             %8 = OpTypeInt 32 0
             %9 = OpConstant %8 12
            %10 = OpTypeArray %7 %9
-           %11 = OpTypePointer Private %10
+         %1002 = OpTypeArray %1001 %9
+           %11 = OpTypePointer Private %1002
            %12 = OpVariable %11 Private
            %15 = OpTypeStruct %10 %7
            %16 = OpTypePointer Uniform %15
@@ -533,26 +543,32 @@
            %18 = OpTypeInt 32 1
            %19 = OpConstant %18 0
            %20 = OpTypePointer Uniform %10
-           %24 = OpTypePointer Private %7
+           %24 = OpTypePointer Private %1001
            %27 = OpTypePointer Private %6
            %30 = OpConstant %18 1
-          %132 = OpTypePointer Function %10
+          %132 = OpTypePointer Function %1002
           %135 = OpTypePointer Uniform %7
-          %145 = OpTypePointer Function %7
+          %145 = OpTypePointer Function %1001
             %4 = OpFunction %2 None %3
             %5 = OpLabel
           %133 = OpVariable %132 Function
            %21 = OpAccessChain %20 %17 %19
-                 OpCopyMemory %12 %21 Aligned 16 Aligned|Volatile 16
+         %1003 = OpLoad %10 %21 Aligned|Volatile 16
+         %1004 = OpCopyLogical %1002 %1003
+                 OpStore %12 %1004 Aligned 16
                  OpCopyMemory %133 %12 Volatile Nontemporal|Volatile
                  OpCopyMemory %133 %12 Nontemporal|Volatile
                  OpCopyMemory %133 %12 None Nontemporal|Volatile
           %136 = OpAccessChain %135 %17 %30
           %138 = OpAccessChain %24 %12 %19
-                 OpCopyMemory %138 %136 None Aligned|Nontemporal 16
-                 OpCopyMemory %138 %136 Aligned 16 Volatile
+         %1005 = OpLoad %7 %136 Aligned|Nontemporal 16
+         %1006 = OpCopyLogical %1001 %1005
+                 OpStore %138 %1006
+         %1007 = OpLoad %7 %136 Volatile
+         %1008 = OpCopyLogical %1001 %1007
+                 OpStore %138 %1008 Aligned 16
           %146 = OpAccessChain %145 %133 %30
-          %147 = OpLoad %7 %146 Volatile|Aligned 16
+          %147 = OpLoad %1001 %146 Volatile|Aligned 16
           %148 = OpAccessChain %24 %12 %19
                  OpStore %148 %147 None
                  OpReturn
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index ecfa7c7..272caeb 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -4433,10 +4433,6 @@
                OpMemoryModel PhysicalStorageBuffer64 GLSL450
                OpEntryPoint GLCompute %1 "main"
                OpExecutionMode %1 LocalSize 8 8 1
-               OpDecorate %_ptr_PhysicalStorageBuffer__struct_5 ArrayStride 8
-               OpMemberDecorate %_struct_3 0 Offset 0
-               OpMemberDecorate %_struct_3 1 Offset 8
-               OpDecorate %_ptr_PhysicalStorageBuffer_int ArrayStride 4
                OpMemberDecorate %_struct_5 0 Offset 0
                OpMemberDecorate %_struct_5 1 Offset 4
                OpDecorate %6 Aliased
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index ee9b68d..d2a77e2 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -1423,16 +1423,6 @@
                           "to be used only with Fragment execution model"))));
 
 INSTANTIATE_TEST_SUITE_P(
-    SampleMaskWrongStorageClass,
-    ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(Values("SampleMask"), Values("Fragment"), Values("Workgroup"),
-            Values("%u32arr2"), Values("VUID-SampleMask-SampleMask-04358"),
-            Values(TestResult(
-                SPV_ERROR_INVALID_DATA,
-                "Vulkan spec allows BuiltIn SampleMask to be only used for "
-                "variables with Input or Output storage class"))));
-
-INSTANTIATE_TEST_SUITE_P(
     SampleMaskNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 84e9021..2682377 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -10697,6 +10697,273 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateDecorations, InvalidLayoutBlockFunctionPre1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_function_block = OpTypePointer Function %block
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_function_block Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutBlockFunctionPost1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_function_block = OpTypePointer Function %block
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_function_block Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutOffsetPrivatePre1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_private_block = OpTypePointer Private %block
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_private_block Private
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutOffsetPrivatePost1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_private_block = OpTypePointer Private %block
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_private_block Private
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutArrayStrideWorkgroupExplicitLayout) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability WorkgroupMemoryExplicitLayoutKHR
+OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %array ArrayStride 4
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array = OpTypeArray %int %int_4
+%ptr_wg_block = OpTypePointer Workgroup %array
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_wg_block Workgroup
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutArrayStrideWorkgroup) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %array ArrayStride 4
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array = OpTypeArray %int %int_4
+%ptr_wg_block = OpTypePointer Workgroup %array
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_wg_block Workgroup
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutArrayStrideUniformConstant) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %array ArrayStride 4
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%sampler = OpTypeSampler
+%array = OpTypeArray %sampler %int_4
+%ptr_uc_block = OpTypePointer UniformConstant %array
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_uc_block UniformConstant
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutMatrixStrideFunctionPost1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %block 0 MatrixStride 16
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%mat4x4 = OpTypeMatrix %v4float 4
+%block = OpTypeStruct %mat4x4
+%ptr_function_block = OpTypePointer Function %block
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_function_block Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutNestedMatrixStrideFunctionPost1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %block 0 MatrixStride 16
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%mat4x4 = OpTypeMatrix %v4float 4
+%block = OpTypeStruct %mat4x4
+%block2 = OpTypeStruct %block
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%array = OpTypeArray %block2 %int_2
+%ptr_function_array = OpTypePointer Function %array
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_function_array Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutBufferBlockWorkgroup) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block BufferBlock
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_wg_block = OpTypePointer Workgroup %block
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_wg_block Workgroup
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools