[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