spirv-fuzz: Avoid using block-decorated structs in transformations (#3877)

Fixes #3875.
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index b1f39b7..da6b9a5 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -439,17 +439,21 @@
         }
         break;
       case SpvOpTypeStruct: {
-        // A struct type is basic if all of its members are basic.
-        bool all_members_are_basic_types = true;
-        for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
-          if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
-            all_members_are_basic_types = false;
-            break;
+        // A struct type is basic if it does not have the Block/BufferBlock
+        // decoration, and if all of its members are basic.
+        if (!fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
+                                                         inst.result_id())) {
+          bool all_members_are_basic_types = true;
+          for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
+            if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
+              all_members_are_basic_types = false;
+              break;
+            }
           }
-        }
-        if (all_members_are_basic_types) {
-          basic_types.insert(inst.result_id());
-          basic_type_to_pointers.insert({inst.result_id(), {}});
+          if (all_members_are_basic_types) {
+            basic_types.insert(inst.result_id());
+            basic_type_to_pointers.insert({inst.result_id(), {}});
+          }
         }
         break;
       }
@@ -515,6 +519,10 @@
           scalar_or_composite_type_id, is_irrelevant);
     }
     case SpvOpTypeStruct: {
+      assert(!fuzzerutil::HasBlockOrBufferBlockDecoration(
+                 GetIRContext(), scalar_or_composite_type_id) &&
+             "We do not construct constants of struct types decorated with "
+             "Block or BufferBlock.");
       std::vector<uint32_t> field_zero_ids;
       for (uint32_t index = 0; index < type_instruction->NumInOperands();
            index++) {
diff --git a/source/fuzz/fuzzer_pass_construct_composites.cpp b/source/fuzz/fuzzer_pass_construct_composites.cpp
index 6443e89..584fa1a 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.cpp
+++ b/source/fuzz/fuzzer_pass_construct_composites.cpp
@@ -32,11 +32,14 @@
 FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
 
 void FuzzerPassConstructComposites::Apply() {
-  // Gather up the ids of all composite types.
+  // Gather up the ids of all composite types, but skip block-/buffer
+  // block-decorated struct types.
   std::vector<uint32_t> composite_type_ids;
   for (auto& inst : GetIRContext()->types_values()) {
     if (fuzzerutil::IsCompositeType(
-            GetIRContext()->get_type_mgr()->GetType(inst.result_id()))) {
+            GetIRContext()->get_type_mgr()->GetType(inst.result_id())) &&
+        !fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
+                                                     inst.result_id())) {
       composite_type_ids.push_back(inst.result_id());
     }
   }
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 48aee58..99d64eb 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -1550,6 +1550,18 @@
   return builtin_count != 0;
 }
 
+bool HasBlockOrBufferBlockDecoration(opt::IRContext* ir_context, uint32_t id) {
+  for (auto decoration : {SpvDecorationBlock, SpvDecorationBufferBlock}) {
+    if (!ir_context->get_decoration_mgr()->WhileEachDecoration(
+            id, decoration, [](const opt::Instruction & /*unused*/) -> bool {
+              return false;
+            })) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
     opt::BasicBlock* block_to_split, opt::Instruction* split_before) {
   std::set<uint32_t> sampled_image_result_ids;
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index c7e55e2..b0141f8 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -541,6 +541,12 @@
 bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context,
                                   uint32_t struct_type_id);
 
+// Returns true if and only if |id| is decorated with either Block or
+// BufferBlock.  Even though these decorations are only allowed on struct types,
+// for convenience |id| can be any result id so that it is possible to call this
+// method on something that *might* be a struct type.
+bool HasBlockOrBufferBlockDecoration(opt::IRContext* ir_context, uint32_t id);
+
 // Returns true iff splitting block |block_to_split| just before the instruction
 // |split_before| would separate an OpSampledImage instruction from its usage.
 bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
diff --git a/source/fuzz/transformation_add_constant_composite.cpp b/source/fuzz/transformation_add_constant_composite.cpp
index ac4d599..0260321 100644
--- a/source/fuzz/transformation_add_constant_composite.cpp
+++ b/source/fuzz/transformation_add_constant_composite.cpp
@@ -77,14 +77,9 @@
       // BufferBlock.  The SPIR-V spec does not explicitly disallow this, but it
       // seems like a strange thing to do, so we disallow it to avoid triggering
       // low priorty edge case issues related to it.
-      for (auto decoration : {SpvDecorationBlock, SpvDecorationBufferBlock}) {
-        if (!ir_context->get_decoration_mgr()->WhileEachDecoration(
-                composite_type_instruction->result_id(), decoration,
-                [](const opt::Instruction & /*unused*/) -> bool {
-                  return false;
-                })) {
-          return false;
-        }
+      if (fuzzerutil::HasBlockOrBufferBlockDecoration(
+              ir_context, composite_type_instruction->result_id())) {
+        return false;
       }
       composite_type_instruction->ForEachInOperand(
           [&constituent_type_ids](const uint32_t* member_type_id) {
diff --git a/source/fuzz/transformation_add_constant_composite.h b/source/fuzz/transformation_add_constant_composite.h
index 56ac52a..9e9222d 100644
--- a/source/fuzz/transformation_add_constant_composite.h
+++ b/source/fuzz/transformation_add_constant_composite.h
@@ -38,7 +38,7 @@
   // - |message_.type_id| must be the id of a composite type
   // - |message_.constituent_id| must refer to ids that match the constituent
   //   types of this composite type
-  // - If |message_.type_id| is a struc type, it must not have the Block or
+  // - If |message_.type_id| is a struct type, it must not have the Block or
   //   BufferBlock decoration
   bool IsApplicable(
       opt::IRContext* ir_context,
diff --git a/source/fuzz/transformation_add_copy_memory.cpp b/source/fuzz/transformation_add_copy_memory.cpp
index e2c7eee..44bb9c5 100644
--- a/source/fuzz/transformation_add_copy_memory.cpp
+++ b/source/fuzz/transformation_add_copy_memory.cpp
@@ -162,8 +162,21 @@
   const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
   assert(type && "Instruction must have a valid type");
 
-  return type->AsPointer() &&
-         CanUsePointeeWithCopyMemory(*type->AsPointer()->pointee_type());
+  if (!type->AsPointer()) {
+    return false;
+  }
+
+  // We do not support copying memory from a pointer to a block-/buffer
+  // block-decorated struct.
+  auto pointee_type_inst = ir_context->get_def_use_mgr()
+                               ->GetDef(inst->type_id())
+                               ->GetSingleWordInOperand(1);
+  if (fuzzerutil::HasBlockOrBufferBlockDecoration(ir_context,
+                                                  pointee_type_inst)) {
+    return false;
+  }
+
+  return CanUsePointeeWithCopyMemory(*type->AsPointer()->pointee_type());
 }
 
 bool TransformationAddCopyMemory::CanUsePointeeWithCopyMemory(
diff --git a/source/fuzz/transformation_add_copy_memory.h b/source/fuzz/transformation_add_copy_memory.h
index b3f58c0..cc42f1e 100644
--- a/source/fuzz/transformation_add_copy_memory.h
+++ b/source/fuzz/transformation_add_copy_memory.h
@@ -41,6 +41,8 @@
   // - |fresh_id| must be a fresh id to copy memory into.
   // - type of |source_id| must be OpTypePointer where pointee can be used with
   //   OpCopyMemory.
+  // - If the pointee type of |source_id| is a struct type, it must not have the
+  //   Block or BufferBlock decoration.
   // - |storage_class| must be either Private or Function.
   // - type ids of instructions with result ids |source_id| and |initialize_id|
   //   must be the same.
diff --git a/test/fuzz/transformation_add_copy_memory_test.cpp b/test/fuzz/transformation_add_copy_memory_test.cpp
index 10487e6..be4a300 100644
--- a/test/fuzz/transformation_add_copy_memory_test.cpp
+++ b/test/fuzz/transformation_add_copy_memory_test.cpp
@@ -381,6 +381,98 @@
   ASSERT_TRUE(IsEqual(env, expected, context.get()));
 }
 
+TEST(TransformationAddCopyMemoryTest, DisallowBufferBlockDecoration) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 4
+               OpDecorate %7 BufferBlock
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %10 = OpConstant %6 42
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %50 = OpUndef %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_0;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(5, SpvOpReturn, 0),
+                                  100, 9, SpvStorageClassPrivate, 50)
+          .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddCopyMemoryTest, DisallowBlockDecoration) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %9
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 4
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %10 = OpConstant %6 42
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer StorageBuffer %7
+          %9 = OpVariable %8 StorageBuffer
+         %50 = OpUndef %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(5, SpvOpReturn, 0),
+                                  100, 9, SpvStorageClassPrivate, 50)
+          .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
\ No newline at end of file