[llvm] [IR] Verify DISubrange/DISubrangeType bounds are ConstantInt (PR #223134)

via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 12 03:15:55 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-debuginfo

Author: Sewon Ahn (lrycro)

<details>
<summary>Changes</summary>

### Describe

`DISubrange`/`DISubrangeType` bound operands (`LowerBound`, `UpperBound`, `Stride`, `Bias`, `CountNode`) can be `ConstantAsMetadata`, but nothing checked that the wrapped `Constant` is actually a `ConstantInt`. Eight call sites across `Verifier.cpp`, `LLVMContextImpl.h`, `DebugInfoMetadata.cpp`, and `AsmWriter.cpp` assumed it unconditionally (`cast<ConstantInt>(MD->getValue())`), including the Verifier's own `CheckDI` for these fields, which already says "must be signed constant" but didn't actually check for `ConstantInt`.

### Expected behavior

IR with a non-`ConstantInt` bound should be rejected by the verifier with a clear diagnostic.

### Actual behavior

`cast<ConstantInt>` asserts (or is UB without assertions). Reachable via programmatic IR construction bypassing the textual parser -- confirmed with a unit test using a non-`ConstantInt` constant as a bound.

### Fix

Tightened the Verifier's `CheckDI` for these fields to require `ConstantInt` specifically (`isConstantIntMetadata` helper), and made the other seven call sites use `dyn_cast` instead of `cast` so they degrade gracefully instead of asserting.

Assisted-by: Claude

Thanks for reviewing.


---
Full diff: https://github.com/llvm/llvm-project/pull/223134.diff


6 Files Affected:

- (modified) llvm/lib/IR/AsmWriter.cpp (+9-7) 
- (modified) llvm/lib/IR/DebugInfoMetadata.cpp (+13-6) 
- (modified) llvm/lib/IR/LLVMContextImpl.h (+13-12) 
- (modified) llvm/lib/IR/Verifier.cpp (+24-18) 
- (modified) llvm/unittests/IR/MetadataTest.cpp (+73) 
- (modified) llvm/unittests/IR/VerifierTest.cpp (+41) 


``````````diff
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 8e57bff1d36c9..91fb87143bfcb 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -2048,13 +2048,15 @@ void MDFieldPrinter::printMetadataOrInt(StringRef Name, const Metadata *MD,
     return;
 
   if (auto *CI = dyn_cast<ConstantAsMetadata>(MD)) {
-    auto *CV = cast<ConstantInt>(CI->getValue());
-    if (IsUnsigned)
-      printInt(Name, CV->getZExtValue(), ShouldSkipZero);
-    else
-      printInt(Name, CV->getSExtValue(), ShouldSkipZero);
-  } else
-    printMetadata(Name, MD);
+    if (auto *CV = dyn_cast<ConstantInt>(CI->getValue())) {
+      if (IsUnsigned)
+        printInt(Name, CV->getZExtValue(), ShouldSkipZero);
+      else
+        printInt(Name, CV->getSExtValue(), ShouldSkipZero);
+      return;
+    }
+  }
+  printMetadata(Name, MD);
 }
 
 template <class IntTy>
diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp
index c25b8c6796bbc..8ffe0e14f61a5 100644
--- a/llvm/lib/IR/DebugInfoMetadata.cpp
+++ b/llvm/lib/IR/DebugInfoMetadata.cpp
@@ -657,7 +657,8 @@ DISubrange::BoundType DISubrange::getCount() const {
          "Count must be signed constant or DIVariable or DIExpression");
 
   if (auto *MD = dyn_cast<ConstantAsMetadata>(CB))
-    return BoundType(cast<ConstantInt>(MD->getValue()));
+    if (auto *CV = dyn_cast<ConstantInt>(MD->getValue()))
+      return BoundType(CV);
 
   if (auto *MD = dyn_cast<DIVariable>(CB))
     return BoundType(MD);
@@ -678,7 +679,8 @@ DISubrange::BoundType DISubrange::getLowerBound() const {
          "LowerBound must be signed constant or DIVariable or DIExpression");
 
   if (auto *MD = dyn_cast<ConstantAsMetadata>(LB))
-    return BoundType(cast<ConstantInt>(MD->getValue()));
+    if (auto *CV = dyn_cast<ConstantInt>(MD->getValue()))
+      return BoundType(CV);
 
   if (auto *MD = dyn_cast<DIVariable>(LB))
     return BoundType(MD);
@@ -699,7 +701,8 @@ DISubrange::BoundType DISubrange::getUpperBound() const {
          "UpperBound must be signed constant or DIVariable or DIExpression");
 
   if (auto *MD = dyn_cast<ConstantAsMetadata>(UB))
-    return BoundType(cast<ConstantInt>(MD->getValue()));
+    if (auto *CV = dyn_cast<ConstantInt>(MD->getValue()))
+      return BoundType(CV);
 
   if (auto *MD = dyn_cast<DIVariable>(UB))
     return BoundType(MD);
@@ -720,7 +723,8 @@ DISubrange::BoundType DISubrange::getStride() const {
          "Stride must be signed constant or DIVariable or DIExpression");
 
   if (auto *MD = dyn_cast<ConstantAsMetadata>(ST))
-    return BoundType(cast<ConstantInt>(MD->getValue()));
+    if (auto *CV = dyn_cast<ConstantInt>(MD->getValue()))
+      return BoundType(CV);
 
   if (auto *MD = dyn_cast<DIVariable>(ST))
     return BoundType(MD);
@@ -841,8 +845,11 @@ DISubrangeType::convertRawToBound(Metadata *IN) const {
   assert(isa<ConstantAsMetadata>(IN) || isa<DIVariable>(IN) ||
          isa<DIExpression>(IN) || isa<DIDerivedType>(IN));
 
-  if (auto *MD = dyn_cast<ConstantAsMetadata>(IN))
-    return BoundType(cast<ConstantInt>(MD->getValue()));
+  if (auto *MD = dyn_cast<ConstantAsMetadata>(IN)) {
+    if (auto *CV = dyn_cast<ConstantInt>(MD->getValue()))
+      return BoundType(CV);
+    return BoundType();
+  }
 
   if (auto *MD = dyn_cast<DIVariable>(IN))
     return BoundType(MD);
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 49a8a803732a5..f44a95af0c426 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -360,9 +360,9 @@ template <> struct MDNodeKeyImpl<DISubrange> {
       ConstantAsMetadata *MD1 = dyn_cast_or_null<ConstantAsMetadata>(Node1);
       ConstantAsMetadata *MD2 = dyn_cast_or_null<ConstantAsMetadata>(Node2);
       if (MD1 && MD2) {
-        ConstantInt *CV1 = cast<ConstantInt>(MD1->getValue());
-        ConstantInt *CV2 = cast<ConstantInt>(MD2->getValue());
-        if (CV1->getSExtValue() == CV2->getSExtValue())
+        ConstantInt *CV1 = dyn_cast<ConstantInt>(MD1->getValue());
+        ConstantInt *CV2 = dyn_cast<ConstantInt>(MD2->getValue());
+        if (CV1 && CV2 && CV1->getSExtValue() == CV2->getSExtValue())
           return true;
       }
       return false;
@@ -377,8 +377,9 @@ template <> struct MDNodeKeyImpl<DISubrange> {
   unsigned getHashValue() const {
     if (CountNode)
       if (auto *MD = dyn_cast<ConstantAsMetadata>(CountNode))
-        return hash_combine(cast<ConstantInt>(MD->getValue())->getSExtValue(),
-                            LowerBound, UpperBound, Stride);
+        if (auto *CV = dyn_cast<ConstantInt>(MD->getValue()))
+          return hash_combine(CV->getSExtValue(), LowerBound, UpperBound,
+                              Stride);
     return hash_combine(CountNode, LowerBound, UpperBound, Stride);
   }
 };
@@ -407,8 +408,8 @@ template <> struct MDNodeKeyImpl<DIGenericSubrange> {
   unsigned getHashValue() const {
     auto *MD = dyn_cast_or_null<ConstantAsMetadata>(CountNode);
     if (CountNode && MD)
-      return hash_combine(cast<ConstantInt>(MD->getValue())->getSExtValue(),
-                          LowerBound, UpperBound, Stride);
+      if (auto *CV = dyn_cast<ConstantInt>(MD->getValue()))
+        return hash_combine(CV->getSExtValue(), LowerBound, UpperBound, Stride);
     return hash_combine(CountNode, LowerBound, UpperBound, Stride);
   }
 };
@@ -676,9 +677,9 @@ template <> struct MDNodeKeyImpl<DISubrangeType> {
       ConstantAsMetadata *MD1 = dyn_cast_or_null<ConstantAsMetadata>(Node1);
       ConstantAsMetadata *MD2 = dyn_cast_or_null<ConstantAsMetadata>(Node2);
       if (MD1 && MD2) {
-        ConstantInt *CV1 = cast<ConstantInt>(MD1->getValue());
-        ConstantInt *CV2 = cast<ConstantInt>(MD2->getValue());
-        if (CV1->getSExtValue() == CV2->getSExtValue())
+        ConstantInt *CV1 = dyn_cast<ConstantInt>(MD1->getValue());
+        ConstantInt *CV2 = dyn_cast<ConstantInt>(MD2->getValue());
+        if (CV1 && CV2 && CV1->getSExtValue() == CV2->getSExtValue())
           return true;
       }
       return false;
@@ -699,8 +700,8 @@ template <> struct MDNodeKeyImpl<DISubrangeType> {
     unsigned val = 0;
     auto HashBound = [&](Metadata *Node) -> void {
       ConstantAsMetadata *MD = dyn_cast_or_null<ConstantAsMetadata>(Node);
-      if (MD) {
-        ConstantInt *CV = cast<ConstantInt>(MD->getValue());
+      ConstantInt *CV = MD ? dyn_cast<ConstantInt>(MD->getValue()) : nullptr;
+      if (CV) {
         val = hash_combine(val, CV->getSExtValue());
       } else {
         val = hash_combine(val, Node);
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index f1b377798a4d9..a8f4a2c07e53c 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1176,6 +1176,14 @@ void Verifier::visitMetadataAsValue(const MetadataAsValue &MDV, Function *F) {
 
 static bool isType(const Metadata *MD) { return !MD || isa<DIType>(MD); }
 static bool isScope(const Metadata *MD) { return !MD || isa<DIScope>(MD); }
+// Subrange bound fields accept a signed constant as ConstantAsMetadata, but
+// ConstantAsMetadata can wrap any Constant -- this additionally requires the
+// wrapped constant to actually be the ConstantInt the field's error message
+// (and its consumers) assume.
+static bool isConstantIntMetadata(const Metadata *MD) {
+  auto *CAM = dyn_cast_or_null<ConstantAsMetadata>(MD);
+  return CAM && isa<ConstantInt>(CAM->getValue());
+}
 static bool isDINode(const Metadata *MD) { return !MD || isa<DINode>(MD); }
 static bool isMDTuple(const Metadata *MD) { return !MD || isa<MDTuple>(MD); }
 
@@ -1211,30 +1219,28 @@ void Verifier::visitDISubrangeType(const DISubrangeType &N) {
   auto *BaseType = N.getRawBaseType();
   CheckDI(!BaseType || isType(BaseType), "BaseType must be a type");
   auto *LBound = N.getRawLowerBound();
-  CheckDI(!LBound || isa<ConstantAsMetadata>(LBound) ||
-              isa<DIVariable>(LBound) || isa<DIExpression>(LBound) ||
-              isa<DIDerivedType>(LBound),
+  CheckDI(!LBound || isConstantIntMetadata(LBound) || isa<DIVariable>(LBound) ||
+              isa<DIExpression>(LBound) || isa<DIDerivedType>(LBound),
           "LowerBound must be signed constant or DIVariable or DIExpression or "
           "DIDerivedType",
           &N);
   auto *UBound = N.getRawUpperBound();
-  CheckDI(!UBound || isa<ConstantAsMetadata>(UBound) ||
-              isa<DIVariable>(UBound) || isa<DIExpression>(UBound) ||
-              isa<DIDerivedType>(UBound),
+  CheckDI(!UBound || isConstantIntMetadata(UBound) || isa<DIVariable>(UBound) ||
+              isa<DIExpression>(UBound) || isa<DIDerivedType>(UBound),
           "UpperBound must be signed constant or DIVariable or DIExpression or "
           "DIDerivedType",
           &N);
   auto *Stride = N.getRawStride();
-  CheckDI(!Stride || isa<ConstantAsMetadata>(Stride) ||
-              isa<DIVariable>(Stride) || isa<DIExpression>(Stride),
+  CheckDI(!Stride || isConstantIntMetadata(Stride) || isa<DIVariable>(Stride) ||
+              isa<DIExpression>(Stride),
           "Stride must be signed constant or DIVariable or DIExpression", &N);
   auto *Bias = N.getRawBias();
-  CheckDI(!Bias || isa<ConstantAsMetadata>(Bias) || isa<DIVariable>(Bias) ||
+  CheckDI(!Bias || isConstantIntMetadata(Bias) || isa<DIVariable>(Bias) ||
               isa<DIExpression>(Bias),
           "Bias must be signed constant or DIVariable or DIExpression", &N);
   // Subrange types currently only support constant size.
   auto *Size = N.getRawSizeInBits();
-  CheckDI(!Size || isa<ConstantAsMetadata>(Size),
+  CheckDI(!Size || isConstantIntMetadata(Size),
           "SizeInBits must be a constant");
 }
 
@@ -1243,26 +1249,26 @@ void Verifier::visitDISubrange(const DISubrange &N) {
   CheckDI(!N.getRawCountNode() || !N.getRawUpperBound(),
           "Subrange can have any one of count or upperBound", &N);
   auto *CBound = N.getRawCountNode();
-  CheckDI(!CBound || isa<ConstantAsMetadata>(CBound) ||
-              isa<DIVariable>(CBound) || isa<DIExpression>(CBound),
+  CheckDI(!CBound || isConstantIntMetadata(CBound) || isa<DIVariable>(CBound) ||
+              isa<DIExpression>(CBound),
           "Count must be signed constant or DIVariable or DIExpression", &N);
   auto Count = N.getCount();
   CheckDI(!Count || !isa<ConstantInt *>(Count) ||
               cast<ConstantInt *>(Count)->getSExtValue() >= -1,
           "invalid subrange count", &N);
   auto *LBound = N.getRawLowerBound();
-  CheckDI(!LBound || isa<ConstantAsMetadata>(LBound) ||
-              isa<DIVariable>(LBound) || isa<DIExpression>(LBound),
+  CheckDI(!LBound || isConstantIntMetadata(LBound) || isa<DIVariable>(LBound) ||
+              isa<DIExpression>(LBound),
           "LowerBound must be signed constant or DIVariable or DIExpression",
           &N);
   auto *UBound = N.getRawUpperBound();
-  CheckDI(!UBound || isa<ConstantAsMetadata>(UBound) ||
-              isa<DIVariable>(UBound) || isa<DIExpression>(UBound),
+  CheckDI(!UBound || isConstantIntMetadata(UBound) || isa<DIVariable>(UBound) ||
+              isa<DIExpression>(UBound),
           "UpperBound must be signed constant or DIVariable or DIExpression",
           &N);
   auto *Stride = N.getRawStride();
-  CheckDI(!Stride || isa<ConstantAsMetadata>(Stride) ||
-              isa<DIVariable>(Stride) || isa<DIExpression>(Stride),
+  CheckDI(!Stride || isConstantIntMetadata(Stride) || isa<DIVariable>(Stride) ||
+              isa<DIExpression>(Stride),
           "Stride must be signed constant or DIVariable or DIExpression", &N);
 }
 
diff --git a/llvm/unittests/IR/MetadataTest.cpp b/llvm/unittests/IR/MetadataTest.cpp
index 5f6feb5bd87bb..f45f742db41e8 100644
--- a/llvm/unittests/IR/MetadataTest.cpp
+++ b/llvm/unittests/IR/MetadataTest.cpp
@@ -1997,6 +1997,20 @@ TEST_F(DISubrangeTest, fortranAllocatableExpr) {
   EXPECT_NE(N, DISubrange::get(Context, nullptr, LVother, UE, SE));
 }
 
+// Regression test: MDNodeKeyImpl<DISubrange>::isKeyOf() (like its
+// DISubrangeType counterpart) must not crash when a bound operand is
+// ConstantAsMetadata wrapping a non-ConstantInt Constant.
+TEST_F(DISubrangeTest, boundsCompareNonConstantIntSafely) {
+  auto *LowerPoison =
+      ConstantAsMetadata::get(PoisonValue::get(Type::getInt64Ty(Context)));
+  auto *LowerInt = ConstantAsMetadata::get(
+      ConstantInt::getSigned(Type::getInt64Ty(Context), -7));
+
+  auto *N1 = DISubrange::get(Context, nullptr, LowerPoison, nullptr, nullptr);
+  auto *N2 = DISubrange::get(Context, nullptr, LowerInt, nullptr, nullptr);
+  EXPECT_NE(N1, N2);
+}
+
 typedef MetadataTest DISubrangeTypeTest;
 
 TEST_F(DISubrangeTypeTest, get) {
@@ -2030,6 +2044,65 @@ TEST_F(DISubrangeTypeTest, get) {
   EXPECT_EQ(N, MDNode::replaceWithUniqued(std::move(Temp)));
 }
 
+// MDNodeKeyImpl<DISubrangeType>::isKeyOf() compares bound operands that are
+// ConstantAsMetadata by comparing the wrapped ConstantInt's *value* rather
+// than requiring pointer identity. Use two different integer widths (i32
+// and i64) holding the same signed value: ConstantInt::get uniques per
+// (type, value), so these are genuinely distinct ConstantInt/ConstantAsMetadata
+// instances -- unlike same-width same-value constants, which LLVM would
+// already unique to an identical pointer, making the value-comparison branch
+// unreachable in that case.
+TEST_F(DISubrangeTypeTest, boundsEqualDistinctConstantAsMetadata) {
+  auto *Base =
+      DIBasicType::get(Context, dwarf::DW_TAG_base_type, "test_integer", 32, 0,
+                       dwarf::DW_ATE_signed, 100, DINode::FlagZero);
+  DILocalScope *Scope = getSubprogram();
+  DIFile *File = getFile();
+
+  auto *Lower1 =
+      ConstantAsMetadata::get(ConstantInt::get(Context, APInt(32, -7, true)));
+  auto *Lower2 =
+      ConstantAsMetadata::get(ConstantInt::get(Context, APInt(64, -7, true)));
+  ASSERT_NE(Lower1, Lower2);
+  auto *Upper =
+      ConstantAsMetadata::get(ConstantInt::get(Context, APInt(32, 23, true)));
+
+  auto *N1 = DISubrangeType::get(Context, StringRef(), File, 101, Scope, 32, 0,
+                                 DINode::FlagZero, Base, Lower1, Upper, nullptr,
+                                 nullptr);
+  auto *N2 = DISubrangeType::get(Context, StringRef(), File, 101, Scope, 32, 0,
+                                 DINode::FlagZero, Base, Lower2, Upper, nullptr,
+                                 nullptr);
+  EXPECT_EQ(N1, N2);
+}
+
+// Regression test: a bound operand may be ConstantAsMetadata wrapping a
+// Constant that is not a ConstantInt (e.g. PoisonValue). isKeyOf() must not
+// crash comparing such nodes -- it should safely treat them as unequal
+// instead of unconditionally casting to ConstantInt.
+TEST_F(DISubrangeTypeTest, boundsCompareNonConstantIntSafely) {
+  auto *Base =
+      DIBasicType::get(Context, dwarf::DW_TAG_base_type, "test_integer", 32, 0,
+                       dwarf::DW_ATE_signed, 100, DINode::FlagZero);
+  DILocalScope *Scope = getSubprogram();
+  DIFile *File = getFile();
+
+  auto *Lower1 =
+      ConstantAsMetadata::get(PoisonValue::get(Type::getInt32Ty(Context)));
+  auto *Lower2 =
+      ConstantAsMetadata::get(ConstantInt::get(Context, APInt(32, -7, true)));
+  auto *Upper =
+      ConstantAsMetadata::get(ConstantInt::get(Context, APInt(32, 23, true)));
+
+  auto *N1 = DISubrangeType::get(Context, StringRef(), File, 101, Scope, 32, 0,
+                                 DINode::FlagZero, Base, Lower1, Upper, nullptr,
+                                 nullptr);
+  auto *N2 = DISubrangeType::get(Context, StringRef(), File, 102, Scope, 32, 0,
+                                 DINode::FlagZero, Base, Lower2, Upper, nullptr,
+                                 nullptr);
+  EXPECT_NE(N1, N2);
+}
+
 typedef MetadataTest DIGenericSubrangeTest;
 
 TEST_F(DIGenericSubrangeTest, fortranAssumedRankInt) {
diff --git a/llvm/unittests/IR/VerifierTest.cpp b/llvm/unittests/IR/VerifierTest.cpp
index 893cab5b413cd..1de31440ea8c1 100644
--- a/llvm/unittests/IR/VerifierTest.cpp
+++ b/llvm/unittests/IR/VerifierTest.cpp
@@ -292,6 +292,47 @@ TEST(VerifierTest, DetectInvalidDebugInfo) {
   }
 }
 
+// DISubrange/DISubrangeType bound operands (LowerBound/UpperBound/Stride/
+// Bias/CountNode) accept a signed constant wrapped in ConstantAsMetadata,
+// but ConstantAsMetadata can wrap any Constant, not just a ConstantInt. The
+// textual IR parser only ever produces a ConstantInt for these fields, so
+// this is only reachable via programmatic IR construction.
+TEST(VerifierTest, DISubrangeNonConstantIntBound) {
+  LLVMContext C;
+  Module M("M", C);
+  auto *NonIntBound =
+      ConstantAsMetadata::get(PoisonValue::get(Type::getInt32Ty(C)));
+  auto *N = DISubrange::get(C, nullptr, NonIntBound, nullptr, nullptr);
+  M.getOrInsertNamedMetadata("test")->addOperand(N);
+
+  std::string Error;
+  raw_string_ostream ErrorOS(Error);
+  EXPECT_TRUE(verifyModule(M, &ErrorOS));
+  EXPECT_TRUE(StringRef(Error).contains(
+      "LowerBound must be signed constant or DIVariable or DIExpression"))
+      << Error;
+}
+
+TEST(VerifierTest, DISubrangeTypeNonConstantIntBound) {
+  LLVMContext C;
+  Module M("M", C);
+  auto *Base = DIBasicType::get(C, dwarf::DW_TAG_base_type, "int", 32, 0,
+                                dwarf::DW_ATE_signed, DINode::FlagZero);
+  auto *NonIntBound =
+      ConstantAsMetadata::get(PoisonValue::get(Type::getInt32Ty(C)));
+  auto *N = DISubrangeType::get(C, StringRef(), nullptr, 0, nullptr, 32, 0,
+                                DINode::FlagZero, Base, NonIntBound, nullptr,
+                                nullptr, nullptr);
+  M.getOrInsertNamedMetadata("test")->addOperand(N);
+
+  std::string Error;
+  raw_string_ostream ErrorOS(Error);
+  EXPECT_TRUE(verifyModule(M, &ErrorOS));
+  EXPECT_TRUE(StringRef(Error).contains(
+      "LowerBound must be signed constant or DIVariable or DIExpression"))
+      << Error;
+}
+
 TEST(VerifierTest, MDNodeWrongContext) {
   LLVMContext C1, C2;
   auto *Node = MDNode::get(C1, {});

``````````

</details>


https://github.com/llvm/llvm-project/pull/223134


More information about the llvm-commits mailing list