Handle id overflow in sroa better. (#2582)

There is a case where sroa is not handling id overflow gracefully.  It
is handled and an error message is output when the ids overflow.

Fixes https://crbug.com/961030.
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index 768364b..8dce8ec 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -167,6 +167,10 @@
     const Constant* new_const, Module::inst_iterator* pos, uint32_t type_id) {
   // TODO(1841): Handle id overflow.
   uint32_t new_id = context()->TakeNextId();
+  if (new_id == 0) {
+    return nullptr;
+  }
+
   auto new_inst = CreateInstruction(new_id, new_const, type_id);
   if (!new_inst) {
     return nullptr;
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 185b494..c857c52 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -456,7 +456,16 @@
 
   // Return the next available SSA id and increment it.  Returns 0 if the
   // maximum SSA id has been reached.
-  inline uint32_t TakeNextId() { return module()->TakeNextIdBound(); }
+  inline uint32_t TakeNextId() {
+    uint32_t next_id = module()->TakeNextIdBound();
+    if (next_id == 0) {
+      if (consumer()) {
+        std::string message = "ID overflow. Try running compact-ids.";
+        consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
+      }
+    }
+    return next_id;
+  }
 
   FeatureManager* get_feature_mgr() {
     if (!feature_mgr_.get()) {
diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp
index bfa8598..6997bf6 100644
--- a/source/opt/scalar_replacement_pass.cpp
+++ b/source/opt/scalar_replacement_pass.cpp
@@ -72,7 +72,9 @@
 bool ScalarReplacementPass::ReplaceVariable(
     Instruction* inst, std::queue<Instruction*>* worklist) {
   std::vector<Instruction*> replacements;
-  CreateReplacementVariables(inst, &replacements);
+  if (!CreateReplacementVariables(inst, &replacements)) {
+    return false;
+  }
 
   std::vector<Instruction*> dead;
   dead.push_back(inst);
@@ -257,7 +259,7 @@
   return true;
 }
 
-void ScalarReplacementPass::CreateReplacementVariables(
+bool ScalarReplacementPass::CreateReplacementVariables(
     Instruction* inst, std::vector<Instruction*>* replacements) {
   Instruction* type = GetStorageType(inst);
 
@@ -302,6 +304,8 @@
   }
 
   TransferAnnotations(inst, replacements);
+  return std::find(replacements->begin(), replacements->end(), nullptr) ==
+         replacements->end();
 }
 
 void ScalarReplacementPass::TransferAnnotations(
@@ -801,7 +805,9 @@
   const analysis::Constant* null_const = const_mgr->GetConstant(type, {});
   Instruction* null_inst =
       const_mgr->GetDefiningInstruction(null_const, type_id);
-  context()->UpdateDefUse(null_inst);
+  if (null_inst != nullptr) {
+    context()->UpdateDefUse(null_inst);
+  }
   return null_inst;
 }
 
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index 0ff9947..ca8c0b4 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -117,8 +117,8 @@
   // for element of the composite type. Uses of |inst| are updated as
   // appropriate. If the replacement variables are themselves scalarizable, they
   // get added to |worklist| for further processing. If any replacement
-  // variable ends up with no uses it is erased. Returns false if any
-  // subsequent access chain is out of bounds.
+  // variable ends up with no uses it is erased. Returns false if the variable
+  // could not be replaced.
   bool ReplaceVariable(Instruction* inst, std::queue<Instruction*>* worklist);
 
   // Returns the underlying storage type for |inst|.
@@ -145,13 +145,14 @@
                       std::vector<Instruction*>* replacements);
 
   // Populates |replacements| with a new OpVariable for each element of |inst|.
+  // Returns true if the replacement variables were successfully created.
   //
   // |inst| must be an OpVariable of a composite type. New variables are
   // initialized the same as the corresponding index in |inst|. |replacements|
   // will contain a variable for each element of the composite with matching
   // indexes (i.e. the 0'th element of |inst| is the 0'th entry of
   // |replacements|).
-  void CreateReplacementVariables(Instruction* inst,
+  bool CreateReplacementVariables(Instruction* inst,
                                   std::vector<Instruction*>* replacements);
 
   // Returns the value of an OpConstant of integer type.
@@ -217,6 +218,7 @@
 
   // Returns an instruction defining a null constant with type |type_id|.  If
   // one already exists, it is returned.  Otherwise a new one is created.
+  // Returns |nullptr| if the new constant could not be created.
   Instruction* CreateNullConstant(uint32_t type_id);
 
   // Maps storage type to a pointer type enclosing that type.
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
index a53f09d..6c182d5 100644
--- a/test/opt/scalar_replacement_test.cpp
+++ b/test/opt/scalar_replacement_test.cpp
@@ -1620,6 +1620,43 @@
   EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
 }
 
+// Test that id overflow is handled gracefully.
+TEST_F(ScalarReplacementTest, IdBoundOverflow) {
+  const std::string text = R"(
+OpCapability ImageQuery
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %4 "main"
+OpExecutionMode %4 OriginUpperLeft
+OpDecorate %4194302 DescriptorSet 1073495039
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeStruct %6 %6
+%557056 = OpTypeStruct %7
+%9 = OpTypePointer Function %7
+%18 = OpTypeFunction %7 %9
+%4 = OpFunction %2 Pure|Const %3
+%1836763 = OpLabel
+%4194302 = OpVariable %9 Function
+%10 = OpVariable %9 Function
+OpKill
+%4194301 = OpLabel
+%524296 = OpLoad %7 %4194302
+OpKill
+OpFunctionEnd
+  )";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  std::vector<Message> messages = {
+      {SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."},
+      {SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."}};
+  SetMessageConsumer(GetTestMessageConsumer(messages));
+  auto result =
+      SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false);
+  EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools