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

Sewon Ahn via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 19 06:36:05 PDT 2026


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

>From fac0f25ae45e17d2fe6f5fe151efd42fdc75da34 Mon Sep 17 00:00:00 2001
From: lrycro <thscw973 at naver.com>
Date: Sat, 12 Sep 2026 19:14:36 +0900
Subject: [PATCH] [IR] Verify DISubrange/DISubrangeType bounds are ConstantInt

DISubrange/DISubrangeType bound operands (LowerBound, UpperBound, Stride, Bias, CountNode) can be ConstantAsMetadata wrapping any Constant, but several call sites in Verifier.cpp, LLVMContextImpl.h, DebugInfoMetadata.cpp, and AsmWriter.cpp assumed it was always a ConstantInt and used cast<ConstantInt> unconditionally, including the Verifier's own CheckDI for these fields, which already says "must be signed constant" but didn't actually check for ConstantInt.

Tighten CheckDI to require ConstantInt specifically (isConstantIntMetadata helper), and change the other seven call sites to dyn_cast so they degrade gracefully instead of asserting/UB on malformed IR.

Assisted-by: Claude
---
 llvm/lib/IR/AsmWriter.cpp          | 16 ++++---
 llvm/lib/IR/DebugInfoMetadata.cpp  | 19 +++++---
 llvm/lib/IR/LLVMContextImpl.h      | 25 +++++-----
 llvm/lib/IR/Verifier.cpp           | 42 +++++++++--------
 llvm/unittests/IR/MetadataTest.cpp | 73 ++++++++++++++++++++++++++++++
 llvm/unittests/IR/VerifierTest.cpp | 41 +++++++++++++++++
 6 files changed, 173 insertions(+), 43 deletions(-)

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, {});



More information about the llvm-commits mailing list