spirv-fuzz: Limit adding of new variables to 'basic' types (#3257)

To avoid problems where global and local variables of opaque or
runtime-sized types are added to a module, this change introduces the
notion of a 'basic type' -- a type made up from floats, ints, bools,
or vectors, matrices, structs and fixed-size arrays of basic types.
Added variables have to be of basic type.
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 4ab2946..dbe5143 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/fuzzer_pass.h"
 
+#include <set>
+
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/transformation_add_constant_boolean.h"
@@ -329,43 +331,72 @@
 }
 
 std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
-FuzzerPass::GetAvailableBaseTypesAndPointers(
+FuzzerPass::GetAvailableBasicTypesAndPointers(
     SpvStorageClass storage_class) const {
-  // Records all of the base types available in the module.
-  std::vector<uint32_t> base_types;
+  // Records all of the basic types available in the module.
+  std::set<uint32_t> basic_types;
 
-  // For each base type, records all the associated pointer types that target
-  // that base type and that have |storage_class| as their storage class.
-  std::map<uint32_t, std::vector<uint32_t>> base_type_to_pointers;
+  // For each basic type, records all the associated pointer types that target
+  // the basic type and that have |storage_class| as their storage class.
+  std::map<uint32_t, std::vector<uint32_t>> basic_type_to_pointers;
 
   for (auto& inst : GetIRContext()->types_values()) {
+    // For each basic type that we come across, record type, and the fact that
+    // we cannot yet have seen any pointers that use the basic type as its
+    // pointee type.
+    //
+    // For pointer types with basic pointee types, associate the pointer type
+    // with the basic type.
     switch (inst.opcode()) {
-      case SpvOpTypeArray:
       case SpvOpTypeBool:
       case SpvOpTypeFloat:
       case SpvOpTypeInt:
       case SpvOpTypeMatrix:
-      case SpvOpTypeStruct:
       case SpvOpTypeVector:
-        // These types are suitable as pointer base types.  Record the type,
-        // and the fact that we cannot yet have seen any pointers that use this
-        // as its base type.
-        base_types.push_back(inst.result_id());
-        base_type_to_pointers.insert({inst.result_id(), {}});
+        // These are all basic types.
+        basic_types.insert(inst.result_id());
+        basic_type_to_pointers.insert({inst.result_id(), {}});
         break;
-      case SpvOpTypePointer:
-        if (inst.GetSingleWordInOperand(0) == storage_class) {
-          // The pointer has the desired storage class, so we are interested in
-          // it.  Associate it with its base type.
-          base_type_to_pointers.at(inst.GetSingleWordInOperand(1))
-              .push_back(inst.result_id());
+      case SpvOpTypeArray:
+        // An array type is basic if its base type is basic.
+        if (basic_types.count(inst.GetSingleWordInOperand(0))) {
+          basic_types.insert(inst.result_id());
+          basic_type_to_pointers.insert({inst.result_id(), {}});
         }
         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;
+          }
+        }
+        if (all_members_are_basic_types) {
+          basic_types.insert(inst.result_id());
+          basic_type_to_pointers.insert({inst.result_id(), {}});
+        }
+        break;
+      }
+      case SpvOpTypePointer: {
+        // We are interested in the pointer if its pointee type is basic and it
+        // has the right storage class.
+        auto pointee_type = inst.GetSingleWordInOperand(1);
+        if (inst.GetSingleWordInOperand(0) == storage_class &&
+            basic_types.count(pointee_type)) {
+          // The pointer has the desired storage class, and its pointee type is
+          // a basic type, so we are interested in it.  Associate it with its
+          // basic type.
+          basic_type_to_pointers.at(pointee_type).push_back(inst.result_id());
+        }
+        break;
+      }
       default:
         break;
     }
   }
-  return {base_types, base_type_to_pointers};
+  return {{basic_types.begin(), basic_types.end()}, basic_type_to_pointers};
 }
 
 uint32_t FuzzerPass::FindOrCreateZeroConstant(
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 436fd77..94b8dfa 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -169,18 +169,21 @@
   // If no such instruction exists, a transformation is applied to add it.
   uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
 
-  // Yields a pair, (base_type_ids, base_type_ids_to_pointers), such that:
-  // - base_type_ids captures every scalar or composite type declared in the
-  //   module (i.e., all int, bool, float, vector, matrix, struct and array
-  //   types
-  // - base_type_ids_to_pointers maps every such base type to the sequence
+  // Define a *basic type* to be an integer, boolean or floating-point type,
+  // or a matrix, vector, struct or fixed-size array built from basic types.  In
+  // particular, a basic type cannot contain an opaque type (such as an image),
+  // or a runtime-sized array.
+  //
+  // Yields a pair, (basic_type_ids, basic_type_ids_to_pointers), such that:
+  // - basic_type_ids captures every basic type declared in the module.
+  // - basic_type_ids_to_pointers maps every such basic type to the sequence
   //   of all pointer types that have storage class |storage_class| and the
-  //   given base type as their pointee type.  The sequence may be empty for
-  //   some base types if no pointers to those types are defined for the given
+  //   given basic type as their pointee type.  The sequence may be empty for
+  //   some basic types if no pointers to those types are defined for the given
   //   storage class, and the sequence will have multiple elements if there are
-  //   repeated pointer declarations for the same base type and storage class.
+  //   repeated pointer declarations for the same basic type and storage class.
   std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
-  GetAvailableBaseTypesAndPointers(SpvStorageClass storage_class) const;
+  GetAvailableBasicTypesAndPointers(SpvStorageClass storage_class) const;
 
   // Given a type id, |scalar_or_composite_type_id|, which must correspond to
   // some scalar or composite type, returns the result id of an instruction
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.cpp b/source/fuzz/fuzzer_pass_add_global_variables.cpp
index ce2b8eb..80708ed 100644
--- a/source/fuzz/fuzzer_pass_add_global_variables.cpp
+++ b/source/fuzz/fuzzer_pass_add_global_variables.cpp
@@ -30,45 +30,45 @@
 FuzzerPassAddGlobalVariables::~FuzzerPassAddGlobalVariables() = default;
 
 void FuzzerPassAddGlobalVariables::Apply() {
-  auto base_type_ids_and_pointers =
-      GetAvailableBaseTypesAndPointers(SpvStorageClassPrivate);
+  auto basic_type_ids_and_pointers =
+      GetAvailableBasicTypesAndPointers(SpvStorageClassPrivate);
 
-  // These are the base types that are available to this fuzzer pass.
-  auto& base_types = base_type_ids_and_pointers.first;
+  // These are the basic types that are available to this fuzzer pass.
+  auto& basic_types = basic_type_ids_and_pointers.first;
 
-  // These are the pointers to those base types that are *initially* available
+  // These are the pointers to those basic types that are *initially* available
   // to the fuzzer pass.  The fuzzer pass might add pointer types in cases where
-  // none are available for a given base type.
-  auto& base_type_to_pointers = base_type_ids_and_pointers.second;
+  // none are available for a given basic type.
+  auto& basic_type_to_pointers = basic_type_ids_and_pointers.second;
 
   // Probabilistically keep adding global variables.
   while (GetFuzzerContext()->ChoosePercentage(
       GetFuzzerContext()->GetChanceOfAddingGlobalVariable())) {
-    // Choose a random base type; the new variable's type will be a pointer to
-    // this base type.
-    uint32_t base_type =
-        base_types[GetFuzzerContext()->RandomIndex(base_types)];
+    // Choose a random basic type; the new variable's type will be a pointer to
+    // this basic type.
+    uint32_t basic_type =
+        basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
     uint32_t pointer_type_id;
-    std::vector<uint32_t>& available_pointers_to_base_type =
-        base_type_to_pointers.at(base_type);
-    // Determine whether there is at least one pointer to this base type.
-    if (available_pointers_to_base_type.empty()) {
+    std::vector<uint32_t>& available_pointers_to_basic_type =
+        basic_type_to_pointers.at(basic_type);
+    // Determine whether there is at least one pointer to this basic type.
+    if (available_pointers_to_basic_type.empty()) {
       // There is not.  Make one, to use here, and add it to the available
-      // pointers for the base type so that future variables can potentially
+      // pointers for the basic type so that future variables can potentially
       // use it.
       pointer_type_id = GetFuzzerContext()->GetFreshId();
-      available_pointers_to_base_type.push_back(pointer_type_id);
+      available_pointers_to_basic_type.push_back(pointer_type_id);
       ApplyTransformation(TransformationAddTypePointer(
-          pointer_type_id, SpvStorageClassPrivate, base_type));
+          pointer_type_id, SpvStorageClassPrivate, basic_type));
     } else {
       // There is - grab one.
       pointer_type_id =
-          available_pointers_to_base_type[GetFuzzerContext()->RandomIndex(
-              available_pointers_to_base_type)];
+          available_pointers_to_basic_type[GetFuzzerContext()->RandomIndex(
+              available_pointers_to_basic_type)];
     }
     ApplyTransformation(TransformationAddGlobalVariable(
         GetFuzzerContext()->GetFreshId(), pointer_type_id,
-        FindOrCreateZeroConstant(base_type), true));
+        FindOrCreateZeroConstant(basic_type), true));
   }
 }
 
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.cpp b/source/fuzz/fuzzer_pass_add_local_variables.cpp
index ace9be2..661159e 100644
--- a/source/fuzz/fuzzer_pass_add_local_variables.cpp
+++ b/source/fuzz/fuzzer_pass_add_local_variables.cpp
@@ -31,47 +31,47 @@
 FuzzerPassAddLocalVariables::~FuzzerPassAddLocalVariables() = default;
 
 void FuzzerPassAddLocalVariables::Apply() {
-  auto base_type_ids_and_pointers =
-      GetAvailableBaseTypesAndPointers(SpvStorageClassFunction);
+  auto basic_type_ids_and_pointers =
+      GetAvailableBasicTypesAndPointers(SpvStorageClassFunction);
 
-  // These are the base types that are available to this fuzzer pass.
-  auto& base_types = base_type_ids_and_pointers.first;
+  // These are the basic types that are available to this fuzzer pass.
+  auto& basic_types = basic_type_ids_and_pointers.first;
 
-  // These are the pointers to those base types that are *initially* available
+  // These are the pointers to those basic types that are *initially* available
   // to the fuzzer pass.  The fuzzer pass might add pointer types in cases where
-  // none are available for a given base type.
-  auto& base_type_to_pointers = base_type_ids_and_pointers.second;
+  // none are available for a given basic type.
+  auto& basic_type_to_pointers = basic_type_ids_and_pointers.second;
 
   // Consider every function in the module.
   for (auto& function : *GetIRContext()->module()) {
     // Probabilistically keep adding random variables to this function.
     while (GetFuzzerContext()->ChoosePercentage(
         GetFuzzerContext()->GetChanceOfAddingLocalVariable())) {
-      // Choose a random base type; the new variable's type will be a pointer to
-      // this base type.
-      uint32_t base_type =
-          base_types[GetFuzzerContext()->RandomIndex(base_types)];
+      // Choose a random basic type; the new variable's type will be a pointer
+      // to this basic type.
+      uint32_t basic_type =
+          basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
       uint32_t pointer_type;
-      std::vector<uint32_t>& available_pointers_to_base_type =
-          base_type_to_pointers.at(base_type);
-      // Determine whether there is at least one pointer to this base type.
-      if (available_pointers_to_base_type.empty()) {
+      std::vector<uint32_t>& available_pointers_to_basic_type =
+          basic_type_to_pointers.at(basic_type);
+      // Determine whether there is at least one pointer to this basic type.
+      if (available_pointers_to_basic_type.empty()) {
         // There is not.  Make one, to use here, and add it to the available
-        // pointers for the base type so that future variables can potentially
+        // pointers for the basic type so that future variables can potentially
         // use it.
         pointer_type = GetFuzzerContext()->GetFreshId();
         ApplyTransformation(TransformationAddTypePointer(
-            pointer_type, SpvStorageClassFunction, base_type));
-        available_pointers_to_base_type.push_back(pointer_type);
+            pointer_type, SpvStorageClassFunction, basic_type));
+        available_pointers_to_basic_type.push_back(pointer_type);
       } else {
         // There is - grab one.
         pointer_type =
-            available_pointers_to_base_type[GetFuzzerContext()->RandomIndex(
-                available_pointers_to_base_type)];
+            available_pointers_to_basic_type[GetFuzzerContext()->RandomIndex(
+                available_pointers_to_basic_type)];
       }
       ApplyTransformation(TransformationAddLocalVariable(
           GetFuzzerContext()->GetFreshId(), pointer_type, function.result_id(),
-          FindOrCreateZeroConstant(base_type), true));
+          FindOrCreateZeroConstant(basic_type), true));
     }
   }
 }