spirv-fuzz: Disallow copying of null and undefined pointers (#3172)

If the fuzzer object-copies a pointer we would like to be able to
perform loads from the copy (and stores to it, if its value is known
not to matter).  Undefined and null pointers present a problem here,
so this change disallows copying them.
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index b2ace38..f9f9969 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -226,6 +226,20 @@
     // We can only make a synonym of an instruction that has a type.
     return false;
   }
+  auto type_inst = ir_context->get_def_use_mgr()->GetDef(inst->type_id());
+  if (type_inst->opcode() == SpvOpTypePointer) {
+    switch (inst->opcode()) {
+      case SpvOpConstantNull:
+      case SpvOpUndef:
+        // We disallow making synonyms of null or undefined pointers.  This is
+        // to provide the property that if the original shader exhibited no bad
+        // pointer accesses, the transformed shader will not either.
+        return false;
+      default:
+        break;
+    }
+  }
+
   // We do not make synonyms of objects that have decorations: if the synonym is
   // not decorated analogously, using the original object vs. its synonymous
   // form may not be equivalent.
diff --git a/source/fuzz/transformation_copy_object.h b/source/fuzz/transformation_copy_object.h
index 3a75ac9..ac5e978 100644
--- a/source/fuzz/transformation_copy_object.h
+++ b/source/fuzz/transformation_copy_object.h
@@ -47,6 +47,8 @@
   // - It must be legal to insert an OpCopyObject instruction directly
   //   before 'inst'.
   // - |message_.object| must be available directly before 'inst'.
+  // - |message_.object| must not be a null pointer or undefined pointer (so as
+  //   to make it legal to load from copied pointers).
   bool IsApplicable(opt::IRContext* context,
                     const FactManager& fact_manager) const override;
 
diff --git a/test/fuzz/transformation_copy_object_test.cpp b/test/fuzz/transformation_copy_object_test.cpp
index b489f71..a33f58d 100644
--- a/test/fuzz/transformation_copy_object_test.cpp
+++ b/test/fuzz/transformation_copy_object_test.cpp
@@ -588,6 +588,44 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationCopyObjectTest, DoNotCopyNullOrUndefPointers) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpConstantNull %7
+          %9 = OpUndef %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Illegal to copy null.
+  ASSERT_FALSE(TransformationCopyObject(
+                   8, MakeInstructionDescriptor(5, SpvOpReturn, 0), 100)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Illegal to copy an OpUndef of pointer type.
+  ASSERT_FALSE(TransformationCopyObject(
+                   9, MakeInstructionDescriptor(5, SpvOpReturn, 0), 100)
+                   .IsApplicable(context.get(), fact_manager));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools