[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