spirv-fuzz: Check header dominance when adding dead block (#3694)

`TransformationAddDeadBlock` did not check whether the existing block
(that will become a selection header) dominates its successor block (that
will become its merge block).

This change adds the check.

Fixes #3690.
diff --git a/source/fuzz/transformation_add_dead_block.cpp b/source/fuzz/transformation_add_dead_block.cpp
index 5b06e97..9b50ea7 100644
--- a/source/fuzz/transformation_add_dead_block.cpp
+++ b/source/fuzz/transformation_add_dead_block.cpp
@@ -78,6 +78,27 @@
     return false;
   }
 
+  // |existing_block| must be reachable.
+  opt::DominatorAnalysis* dominator_analysis =
+      ir_context->GetDominatorAnalysis(existing_block->GetParent());
+  if (!dominator_analysis->IsReachable(existing_block->id())) {
+    return false;
+  }
+
+  assert(existing_block->id() != successor_block_id &&
+         "|existing_block| must be different from |successor_block_id|");
+
+  // Even though we know |successor_block_id| is not a merge block, it might
+  // still have multiple predecessors because divergent control flow is allowed
+  // to converge early (before the merge block). In this case, when we create
+  // the selection construct, its header |existing_block| will not dominate the
+  // merge block |successor_block_id|, which is invalid. Thus, |existing_block|
+  // must dominate |successor_block_id|.
+  if (!dominator_analysis->Dominates(existing_block->id(),
+                                     successor_block_id)) {
+    return false;
+  }
+
   return true;
 }
 
diff --git a/test/fuzz/transformation_add_dead_block_test.cpp b/test/fuzz/transformation_add_dead_block_test.cpp
index c9be520..a340015 100644
--- a/test/fuzz/transformation_add_dead_block_test.cpp
+++ b/test/fuzz/transformation_add_dead_block_test.cpp
@@ -20,29 +20,45 @@
 namespace {
 
 TEST(TransformationAddDeadBlockTest, BasicTest) {
-  std::string shader = R"(
+  std::string reference_shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeBool
-          %7 = OpConstantTrue %6
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpBranch %8
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstantTrue %2
+
+; main function
+          %6 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %5 %8 %9
           %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranch %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
                OpReturn
                OpFunctionEnd
   )";
 
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
@@ -51,44 +67,77 @@
                                                validator_options);
 
   // Id 4 is already in use
-  ASSERT_FALSE(TransformationAddDeadBlock(4, 5, true)
-                   .IsApplicable(context.get(), transformation_context));
+  auto transformation = TransformationAddDeadBlock(4, 11, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  // Id 7 is not a block
-  ASSERT_FALSE(TransformationAddDeadBlock(100, 7, true)
-                   .IsApplicable(context.get(), transformation_context));
+  // Id 5 is not a block
+  transformation = TransformationAddDeadBlock(14, 5, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  TransformationAddDeadBlock transformation(100, 5, true);
+  // Tests existing block not dominating its successor block.
+  transformation = TransformationAddDeadBlock(14, 8, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationAddDeadBlock(14, 9, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests existing block being an unreachable block.
+  transformation = TransformationAddDeadBlock(14, 12, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests applicable case.
+  transformation = TransformationAddDeadBlock(14, 11, true);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
   transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(100));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(14));
 
-  std::string after_transformation = R"(
+  std::string variant_shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeBool
-          %7 = OpConstantTrue %6
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpSelectionMerge %8 None
-               OpBranchConditional %7 %8 %100
-        %100 = OpLabel
-               OpBranch %8
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstantTrue %2
+
+; main function
+          %6 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %5 %8 %9
           %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %5 %13 %14
+         %14 = OpLabel
+               OpBranch %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
                OpReturn
                OpFunctionEnd
   )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
 TEST(TransformationAddDeadBlockTest, TargetBlockMustNotBeSelectionMerge) {
@@ -136,27 +185,31 @@
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeBool
-          %7 = OpConstantTrue %6
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstantTrue %2
+
+; main function
+          %6 = OpFunction %3 None %4
+          %7 = OpLabel
                OpBranch %8
           %8 = OpLabel
-               OpLoopMerge %11 %12 None
-               OpBranchConditional %7 %9 %10
+               OpLoopMerge %12 %11 None
+               OpBranchConditional %5 %9 %10
           %9 = OpLabel
-               OpBranch %12
-         %10 = OpLabel
                OpBranch %11
-         %12 = OpLabel
-               OpBranch %8
+         %10 = OpLabel
+               OpBranch %12
          %11 = OpLabel
+               OpBranch %8
+         %12 = OpLabel
                OpReturn
                OpFunctionEnd
   )";