spirv-fuzz: Check termination instructions when donating modules (#3710)

The FuzzerPassDonateModules was not checking if the function to donate had
a block with OpKill or OpUnreachable as its termination instruction.

Fixes #3709.
diff --git a/source/fuzz/fuzzer_pass_donate_modules.cpp b/source/fuzz/fuzzer_pass_donate_modules.cpp
index a8b7ddc..f54d3e4 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.cpp
+++ b/source/fuzz/fuzzer_pass_donate_modules.cpp
@@ -1234,23 +1234,19 @@
     }
   }
 
-  // If the function contains OpKill or OpUnreachable instructions, and has
-  // non-void return type, then we need a value %v to use in order to turn
-  // these into instructions of the form OpReturn %v.
-  uint32_t kill_unreachable_return_value_id;
+  // If |function_to_donate| has non-void return type and contains an
+  // OpKill/OpUnreachable instruction, then a value is needed in order to turn
+  // these into instructions of the form OpReturnValue %value_id.
+  uint32_t kill_unreachable_return_value_id = 0;
   auto function_return_type_inst =
       donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id());
-  if (function_return_type_inst->opcode() == SpvOpTypeVoid) {
-    // The return type is void, so we don't need a return value.
-    kill_unreachable_return_value_id = 0;
-  } else {
-    // We do need a return value; we use zero.
-    assert(function_return_type_inst->opcode() != SpvOpTypePointer &&
-           "Function return type must not be a pointer.");
+  if (function_return_type_inst->opcode() != SpvOpTypeVoid &&
+      fuzzerutil::FunctionContainsOpKillOrUnreachable(function_to_donate)) {
     kill_unreachable_return_value_id = FindOrCreateZeroConstant(
         original_id_to_donated_id.at(function_return_type_inst->result_id()),
         false);
   }
+
   // Add the function in a livesafe manner.
   ApplyTransformation(TransformationAddFunction(
       donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index f9564bc..9e5b30e 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -476,6 +476,16 @@
   return nullptr;
 }
 
+bool FunctionContainsOpKillOrUnreachable(const opt::Function& function) {
+  for (auto& block : function) {
+    if (block.terminator()->opcode() == SpvOpKill ||
+        block.terminator()->opcode() == SpvOpUnreachable) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) {
   for (auto& entry_point : context->module()->entry_points()) {
     if (entry_point.GetSingleWordInOperand(1) == function_id) {
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index cb01ca7..d86e138 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -179,6 +179,10 @@
 // function exists.
 opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id);
 
+// Returns true if |function| has a block that the termination instruction is
+// OpKill or OpUnreachable.
+bool FunctionContainsOpKillOrUnreachable(const opt::Function& function);
+
 // Returns |true| if one of entry points has function id |function_id|.
 bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id);