spirv-fuzz: Tolerate absent ids in data synonym fact management (#3966)

Fixes #3952.
diff --git a/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp
index 04726a0..0308d50 100644
--- a/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp
+++ b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp
@@ -271,7 +271,9 @@
 void DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive(
     const protobufs::DataDescriptor& dd1,
     const protobufs::DataDescriptor& dd2) {
-  assert(DataDescriptorsAreWellFormedAndComparable(dd1, dd2));
+  assert((!ObjectStillExists(dd1) || !ObjectStillExists(dd2) ||
+          DataDescriptorsAreWellFormedAndComparable(dd1, dd2)) &&
+         "Mismatched data descriptors.");
 
   // Record that the data descriptors provided in the fact are equivalent.
   MakeEquivalent(dd1, dd2);
@@ -292,6 +294,11 @@
   assert(synonymous_.Exists(dd) &&
          "|dd| should've been registered in the equivalence relation");
 
+  if (!ObjectStillExists(dd)) {
+    // The object is gone from the module, so we cannot proceed.
+    return;
+  }
+
   const auto* type =
       ir_context_->get_type_mgr()->GetType(fuzzerutil::WalkCompositeTypeIndices(
           ir_context_, fuzzerutil::GetTypeId(ir_context_, dd.object()),
@@ -308,12 +315,11 @@
 
     for (const auto& fact : id_equations_) {
       auto equivalence_class = synonymous_.GetEquivalenceClass(*fact.first);
-      auto dd_it = std::find_if(
-          equivalence_class.begin(), equivalence_class.end(),
-          [this](const protobufs::DataDescriptor* a) {
-            return ir_context_->get_def_use_mgr()->GetDef(a->object()) !=
-                   nullptr;
-          });
+      auto dd_it =
+          std::find_if(equivalence_class.begin(), equivalence_class.end(),
+                       [this](const protobufs::DataDescriptor* a) {
+                         return ObjectStillExists(*a);
+                       });
       if (dd_it == equivalence_class.end()) {
         // Skip |equivalence_class| if it has no valid ids.
         continue;
@@ -581,18 +587,15 @@
               synonymous_.IsEquivalent(dd1_prefix, dd2_prefix)) {
             continue;
           }
-          opt::Instruction* dd1_object =
-              ir_context_->get_def_use_mgr()->GetDef(dd1->object());
-          opt::Instruction* dd2_object =
-              ir_context_->get_def_use_mgr()->GetDef(dd2->object());
-          if (dd1_object == nullptr || dd2_object == nullptr) {
+          if (!ObjectStillExists(*dd1) || !ObjectStillExists(*dd2)) {
             // The objects are not both available in the module, so we cannot
             // investigate the types of the associated data descriptors; we need
             // to move on.
             continue;
           }
           // Get the type of obj_1
-          auto dd1_root_type_id = dd1_object->type_id();
+          auto dd1_root_type_id =
+              fuzzerutil::GetTypeId(ir_context_, dd1->object());
           // Use this type, together with a_1, ..., a_m, to get the type of
           // obj_1[a_1, ..., a_m].
           auto dd1_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
@@ -600,7 +603,8 @@
 
           // Similarly, get the type of obj_2 and use it to get the type of
           // obj_2[b_1, ..., b_n].
-          auto dd2_root_type_id = dd2_object->type_id();
+          auto dd2_root_type_id =
+              fuzzerutil::GetTypeId(ir_context_, dd2->object());
           auto dd2_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
               ir_context_, dd2_root_type_id, dd2_prefix.index());
 
@@ -792,9 +796,11 @@
 bool DataSynonymAndIdEquationFacts::DataDescriptorsAreWellFormedAndComparable(
     const protobufs::DataDescriptor& dd1,
     const protobufs::DataDescriptor& dd2) const {
-  assert(ir_context_->get_def_use_mgr()->GetDef(dd1.object()) &&
-         ir_context_->get_def_use_mgr()->GetDef(dd2.object()) &&
-         "Both descriptors must exist in the module");
+  if (!ObjectStillExists(dd1) || !ObjectStillExists(dd2)) {
+    // We trivially return true if one or other of the objects associated with
+    // the data descriptors is gone.
+    return true;
+  }
 
   auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices(
       ir_context_, fuzzerutil::GetTypeId(ir_context_, dd1.object()),
@@ -880,7 +886,7 @@
       // There may be data descriptors in the equivalence class whose base
       // objects have been removed from the module.  We do not expose these
       // data descriptors to clients of the fact manager.
-      if (ir_context_->get_def_use_mgr()->GetDef(dd->object()) != nullptr) {
+      if (ObjectStillExists(*dd)) {
         result.push_back(dd);
       }
     }
@@ -895,8 +901,7 @@
     // We skip any data descriptors whose base objects no longer exist in the
     // module, and we restrict attention to data descriptors for plain ids,
     // which have no indices.
-    if (ir_context_->get_def_use_mgr()->GetDef(data_descriptor->object()) !=
-            nullptr &&
+    if (ObjectStillExists(*data_descriptor) &&
         data_descriptor->index().empty()) {
       result.push_back(data_descriptor->object());
     }
@@ -912,6 +917,11 @@
          synonymous_.IsEquivalent(data_descriptor1, data_descriptor2);
 }
 
+bool DataSynonymAndIdEquationFacts::ObjectStillExists(
+    const protobufs::DataDescriptor& dd) const {
+  return ir_context_->get_def_use_mgr()->GetDef(dd.object()) != nullptr;
+}
+
 }  // namespace fact_manager
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h
index 46f02d0..f8a0123 100644
--- a/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h
+++ b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h
@@ -119,8 +119,12 @@
   const protobufs::DataDescriptor* RegisterDataDescriptor(
       const protobufs::DataDescriptor& dd);
 
-  // Returns true if and only if |dd1| and |dd2| are valid data descriptors
-  // whose associated data have compatible types. Two types are compatible if:
+  // Trivially returns true if either |dd1| or |dd2|'s objects are not present
+  // in the module.
+  //
+  // Otherwise, returns true if and only if |dd1| and |dd2| are valid data
+  // descriptors whose associated data have compatible types. Two types are
+  // compatible if:
   // - they are the same
   // - they both are numerical or vectors of numerical components with the same
   //   number of components and the same bit count per component
@@ -140,6 +144,9 @@
       const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
       const std::vector<const protobufs::DataDescriptor*>& rhs_dds);
 
+  // Returns true if and only if |dd.object()| still exists in the module.
+  bool ObjectStillExists(const protobufs::DataDescriptor& dd) const;
+
   // The data descriptors that are known to be synonymous with one another are
   // captured by this equivalence relation.
   EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash,