[llvm] [IR] Fix unsafe cast<ConstantInt> on DISubrange/DISubrangeType bound operands (6 call sites) (PR #217570)

via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 20 03:07:49 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`/`DIGenericSubrange` represent their bound
operands (`LowerBound`, `UpperBound`, `Stride`, `Bias`, `CountNode`) as
plain `Metadata *`. When such an operand is `ConstantAsMetadata`, the
wrapped `Constant` is not guaranteed to be a `ConstantInt` -- it can be
any `Constant` (e.g. `UndefValue`). Six call sites across
`llvm/lib/IR/LLVMContextImpl.h` and `llvm/lib/IR/DebugInfoMetadata.cpp`
unconditionally do `cast<ConstantInt>(MD->getValue())` once the operand
is confirmed to be `ConstantAsMetadata`, without checking the wrapped
type:

- `MDNodeKeyImpl<DISubrange>::isKeyOf()`'s `BoundsEqual` lambda
- `MDNodeKeyImpl<DISubrange>::getHashValue()` (`CountNode`)
- `MDNodeKeyImpl<DIGenericSubrange>::getHashValue()` (`CountNode`)
- `MDNodeKeyImpl<DISubrangeType>::isKeyOf()`'s `BoundsEqual` lambda
- `MDNodeKeyImpl<DISubrangeType>::getHashValue()`'s `HashBound` lambda
- `DISubrangeType::convertRawToBound()`

### Expected behavior

A bound operand that's `ConstantAsMetadata` wrapping a non-`ConstantInt`
`Constant` should be handled the same way the surrounding code already
handles any other kind of mismatch -- e.g. treated as "not equal" in the
uniquing comparison, or the raw operand hashed instead of the constant's
value -- without crashing.

### Actual behavior

`cast<X>` asserts on a type mismatch in assertions-enabled builds (the
default for unit tests and many CI configurations) and is undefined
behavior in release builds without assertions.

The `getHashValue()` and `convertRawToBound()` call sites are especially
easy to hit: they're reachable just by constructing a single
`DISubrange`/`DISubrangeType`/`DIGenericSubrange` node with such a bound
operand (`getHashValue()` runs during MDNode uniquing on insertion) or by
calling the public `getLowerBound()`/`getUpperBound()`/`getStride()`/
`getBias()` accessors (`convertRawToBound()`) -- no comparison against a
second node is needed.

### Fix

Same pattern applied at all six sites: replace the unconditional
`cast<ConstantInt>` with `dyn_cast<ConstantInt>` plus a null check,
falling back to the behavior the code already uses elsewhere for
non-matching operand kinds (treat as unequal / hash the raw `Metadata*`
operand / return an empty `BoundType()`).

```diff
-        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;
```
(applied identically at both `isKeyOf` sites; analogous
`dyn_cast`+null-check changes at the four `getHashValue`/
`convertRawToBound` sites -- see diff.)

### Testing

Added to `llvm/unittests/IR/MetadataTest.cpp`:
- `DISubrangeTest.boundsCompareNonConstantIntSafely` and
  `DISubrangeTypeTest.boundsCompareNonConstantIntSafely`: construct a
  node with a bound operand wrapping `UndefValue`. This crashes on
  unpatched code (`cast<ConstantInt>` assertion, hit during
  `getHashValue()`/uniquing at construction time, before any node
  comparison even happens) and passes cleanly after the fix.
- `DISubrangeTypeTest.boundsEqualDistinctConstantAsMetadata`: two
  `ConstantInt`s of different bit width (i32 vs i64) holding the same
  signed value are genuinely distinct instances (LLVM only uniques
  `ConstantInt`/`ConstantAsMetadata` per exact type+value) -- confirms
  the value-based equality path still works unchanged for the intended
  case.

Built `IRTests` with `LLVM_ENABLE_ASSERTIONS=ON`; ran the full suite
(901 tests) -- all pass, including the three new ones. Confirmed the new
tests reproduce the crash on the pre-fix code (verified during
development).

Thanks for reviewing.


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


3 Files Affected:

- (modified) llvm/lib/IR/DebugInfoMetadata.cpp (+5-2) 
- (modified) llvm/lib/IR/LLVMContextImpl.h (+14-12) 
- (modified) llvm/unittests/IR/MetadataTest.cpp (+70) 


``````````diff
diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp
index 134f1382dfc25..4f005a257569d 100644
--- a/llvm/lib/IR/DebugInfoMetadata.cpp
+++ b/llvm/lib/IR/DebugInfoMetadata.cpp
@@ -841,8 +841,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 41c8a92c56eda..c7e5de08e11d0 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -364,9 +364,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;
@@ -381,8 +381,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);
   }
 };
@@ -411,8 +412,9 @@ 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);
   }
 };
@@ -680,9 +682,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;
@@ -703,8 +705,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/unittests/IR/MetadataTest.cpp b/llvm/unittests/IR/MetadataTest.cpp
index 07496cec696ca..313219b299fd6 100644
--- a/llvm/unittests/IR/MetadataTest.cpp
+++ b/llvm/unittests/IR/MetadataTest.cpp
@@ -2001,6 +2001,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 *LowerUndef =
+      ConstantAsMetadata::get(UndefValue::get(Type::getInt64Ty(Context)));
+  auto *LowerInt = ConstantAsMetadata::get(
+      ConstantInt::getSigned(Type::getInt64Ty(Context), -7));
+
+  auto *N1 = DISubrange::get(Context, nullptr, LowerUndef, nullptr, nullptr);
+  auto *N2 = DISubrange::get(Context, nullptr, LowerInt, nullptr, nullptr);
+  EXPECT_NE(N1, N2);
+}
+
 typedef MetadataTest DISubrangeTypeTest;
 
 TEST_F(DISubrangeTypeTest, get) {
@@ -2034,6 +2048,62 @@ 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. UndefValue). 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(UndefValue::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) {

``````````

</details>


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


More information about the llvm-commits mailing list