[llvm] 4d093aa - [llubi] Check undefined bits when !noundef is set (#195642)
via llvm-commits
llvm-commits at lists.llvm.org
Mon May 4 06:07:10 PDT 2026
Author: Yingwei Zheng
Date: 2026-05-04T21:07:05+08:00
New Revision: 4d093aa775638fd2bdb9fa3e4723aee84ec9a9e7
URL: https://github.com/llvm/llvm-project/commit/4d093aa775638fd2bdb9fa3e4723aee84ec9a9e7
DIFF: https://github.com/llvm/llvm-project/commit/4d093aa775638fd2bdb9fa3e4723aee84ec9a9e7.diff
LOG: [llubi] Check undefined bits when !noundef is set (#195642)
Address one of the todos in
https://github.com/llvm/llvm-project/pull/195339. Currently it just
checks the loaded value after applying poison-generating metadata. This
patch also takes uninitialized bits in the memory into account.
Added:
llvm/test/tools/llubi/load_noundef_ub_poison.ll
llvm/test/tools/llubi/load_noundef_ub_poison_padding.ll
llvm/test/tools/llubi/load_noundef_ub_undef.ll
Modified:
llvm/test/tools/llubi/loadstore_be.ll
llvm/test/tools/llubi/loadstore_le.ll
llvm/tools/llubi/lib/Context.cpp
llvm/tools/llubi/lib/Context.h
llvm/tools/llubi/lib/ExecutorBase.cpp
llvm/tools/llubi/lib/ExecutorBase.h
llvm/tools/llubi/lib/Interpreter.cpp
Removed:
################################################################################
diff --git a/llvm/test/tools/llubi/load_noundef_ub_poison.ll b/llvm/test/tools/llubi/load_noundef_ub_poison.ll
new file mode 100644
index 0000000000000..e8b6dafd3bbb2
--- /dev/null
+++ b/llvm/test/tools/llubi/load_noundef_ub_poison.ll
@@ -0,0 +1,16 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ %p = alloca i32
+ store i32 poison, ptr %p
+ %res = load i32, ptr %p, !noundef !{}
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = alloca i32, align 4 => ptr 0x8 [p]
+; CHECK-NEXT: store i32 poison, ptr %p, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = load i32, ptr %p, align 4, !noundef !0 at @main
+; CHECK-NEXT: Immediate UB detected: The value loaded contains undefined bits.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/load_noundef_ub_poison_padding.ll b/llvm/test/tools/llubi/load_noundef_ub_poison_padding.ll
new file mode 100644
index 0000000000000..b57c8332a5bc9
--- /dev/null
+++ b/llvm/test/tools/llubi/load_noundef_ub_poison_padding.ll
@@ -0,0 +1,16 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ %p = alloca i8
+ store <2 x i4> <i4 1, i4 poison>, ptr %p
+ %res = load i4, ptr %p, !noundef !{}
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = alloca i8, align 1 => ptr 0x8 [p]
+; CHECK-NEXT: store <2 x i4> <i4 1, i4 poison>, ptr %p, align 1
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = load i4, ptr %p, align 1, !noundef !0 at @main
+; CHECK-NEXT: Immediate UB detected: The value loaded contains undefined bits.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/load_noundef_ub_undef.ll b/llvm/test/tools/llubi/load_noundef_ub_undef.ll
new file mode 100644
index 0000000000000..24220bdcd36bf
--- /dev/null
+++ b/llvm/test/tools/llubi/load_noundef_ub_undef.ll
@@ -0,0 +1,14 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ %p = alloca i32
+ %res = load i32, ptr %p, !noundef !{}
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = alloca i32, align 4 => ptr 0x8 [p]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = load i32, ptr %p, align 4, !noundef !0 at @main
+; CHECK-NEXT: Immediate UB detected: The value loaded contains undefined bits.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 34da612fdb581..2519a2f692a1a 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -107,6 +107,10 @@ define void @main() {
%load_int_non_zero_padding = load i33, ptr %alloc_padding_vec
%load_vec_non_zero_padding = load <3 x i11>, ptr %alloc_padding_vec
+ %alloc_struct_padding = alloca {i8, i32}
+ store {i8, i32} zeroinitializer, ptr %alloc_struct_padding
+ %load_struct_noundef = load {i8, i32}, ptr %alloc_struct_padding, !noundef !{}
+
ret void
}
; CHECK: Entering function: main
@@ -186,5 +190,8 @@ define void @main() {
; CHECK-NEXT: %load_vec = load <6 x i5>, ptr %alloc_padding_vec, align 4 => { i5 0, i5 0, i5 0, i5 0, i5 0, i5 0 }
; CHECK-NEXT: %load_int_non_zero_padding = load i33, ptr %alloc_padding_vec, align 8 => i33 255
; CHECK-NEXT: %load_vec_non_zero_padding = load <3 x i11>, ptr %alloc_padding_vec, align 8 => { i11 0, i11 0, i11 255 }
+; CHECK-NEXT: %alloc_struct_padding = alloca { i8, i32 }, align 8 => ptr 0x88 [alloc_struct_padding]
+; CHECK-NEXT: store { i8, i32 } zeroinitializer, ptr %alloc_struct_padding, align 4
+; CHECK-NEXT: %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index bdd82ddabbfec..84c5246d799f6 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -108,6 +108,10 @@ define void @main() {
%load_int_non_zero_padding = load i33, ptr %alloc_padding_vec
%load_vec_non_zero_padding = load <3 x i11>, ptr %alloc_padding_vec
+ %alloc_struct_padding = alloca {i8, i32}
+ store {i8, i32} zeroinitializer, ptr %alloc_struct_padding
+ %load_struct_noundef = load {i8, i32}, ptr %alloc_struct_padding, !noundef !{}
+
ret void
}
; CHECK: Entering function: main
@@ -188,5 +192,8 @@ define void @main() {
; CHECK-NEXT: %load_vec = load <6 x i5>, ptr %alloc_padding_vec, align 4 => { i5 0, i5 0, i5 0, i5 0, i5 0, i5 0 }
; CHECK-NEXT: %load_int_non_zero_padding = load i33, ptr %alloc_padding_vec, align 8 => poison
; CHECK-NEXT: %load_vec_non_zero_padding = load <3 x i11>, ptr %alloc_padding_vec, align 8 => { poison, poison, poison }
+; CHECK-NEXT: %alloc_struct_padding = alloca { i8, i32 }, align 8 => ptr 0x88 [alloc_struct_padding]
+; CHECK-NEXT: store { i8, i32 } zeroinitializer, ptr %alloc_struct_padding, align 4
+; CHECK-NEXT: %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index e591e9acc181e..5b81b068460de 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -111,7 +111,8 @@ const AnyValue &Context::getConstantValue(Constant *C) {
}
AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
- uint32_t OffsetInBits, bool CheckPaddingBits) {
+ uint32_t OffsetInBits, bool CheckPaddingBits,
+ bool *ContainsUndefinedBits) {
uint32_t NumBits = DL.getTypeSizeInBits(Ty).getFixedValue();
uint32_t NewOffsetInBits = OffsetInBits + NumBits;
if (CheckPaddingBits)
@@ -136,17 +137,23 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
uint32_t Mask = (1U << NumBitsInByte) - 1;
// If any of the bits in the byte is poison, the whole value is poison.
if (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask) {
+ if (ContainsUndefinedBits)
+ *ContainsUndefinedBits = true;
OffsetInBits = NewOffsetInBits;
return AnyValue::poison();
}
uint8_t RandomBits = 0;
- if (UndefBehavior == UndefValueBehavior::NonDeterministic &&
- (~LogicalByte.ConcreteMask & Mask)) {
+ if (~LogicalByte.ConcreteMask & Mask) {
// This byte contains undef bits.
- // We don't use std::uniform_int_distribution here because it produces
- //
diff erent results across
diff erent library implementations. Instead,
- // we directly use the low bits from Rng.
- RandomBits = static_cast<uint8_t>(Rng());
+ if (ContainsUndefinedBits)
+ *ContainsUndefinedBits = true;
+
+ if (UndefBehavior == UndefValueBehavior::NonDeterministic) {
+ // We don't use std::uniform_int_distribution here because it produces
+ //
diff erent results across
diff erent library implementations. Instead,
+ // we directly use the low bits from Rng.
+ RandomBits = static_cast<uint8_t>(Rng());
+ }
}
uint8_t ActualBits = ((LogicalByte.Value & LogicalByte.ConcreteMask) |
(RandomBits & ~LogicalByte.ConcreteMask)) &
@@ -159,8 +166,11 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
// Padding bits for non-byte-sized scalar types must be zero.
if (NeedsPadding) {
- if (!Bits.isIntN(NumBits))
+ if (!Bits.isIntN(NumBits)) {
+ if (ContainsUndefinedBits)
+ *ContainsUndefinedBits = true;
return AnyValue::poison();
+ }
Bits = Bits.trunc(NumBits);
}
@@ -173,12 +183,13 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
return Pointer(Bits);
}
-AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty) {
+AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
+ bool *ContainsUndefinedBits) {
assert(Bytes.size() == getEffectiveTypeStoreSize(Ty) &&
"Invalid byte array size for the type");
if (Ty->isIntegerTy() || Ty->isFloatingPointTy() || Ty->isPointerTy())
return fromBytes(ConstBytesView(Bytes, DL), Ty, /*OffsetInBits=*/0,
- /*CheckPaddingBits=*/true);
+ /*CheckPaddingBits=*/true, ContainsUndefinedBits);
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
Type *ElemTy = VecTy->getElementType();
@@ -192,8 +203,11 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty) {
const Byte &PaddingByte = View[Bytes.size() - 1];
uint32_t Mask = (~0U << (VecBits % 8)) & 255U;
// Make sure all high padding bits are zero.
- if ((PaddingByte.ConcreteMask & ~PaddingByte.Value & Mask) != Mask)
+ if ((PaddingByte.ConcreteMask & ~PaddingByte.Value & Mask) != Mask) {
+ if (ContainsUndefinedBits)
+ *ContainsUndefinedBits = true;
return AnyValue::getPoisonValue(*this, Ty);
+ }
}
std::vector<AnyValue> ValVec;
@@ -202,11 +216,11 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty) {
// the integer, and for big endian element zero is put in the most
// significant bits.
for (uint32_t I = 0; I != NumElements; ++I)
- ValVec.push_back(fromBytes(View, ElemTy,
- DL.isLittleEndian()
- ? I * ElemBits
- : VecBits - ElemBits - I * ElemBits,
- /*CheckPaddingBits=*/false));
+ ValVec.push_back(
+ fromBytes(View, ElemTy,
+ DL.isLittleEndian() ? I * ElemBits
+ : VecBits - ElemBits - I * ElemBits,
+ /*CheckPaddingBits=*/false, ContainsUndefinedBits));
return AnyValue(std::move(ValVec));
}
if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
@@ -217,7 +231,8 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty) {
std::vector<AnyValue> ValVec;
ValVec.reserve(NumElements);
for (uint32_t I = 0; I != NumElements; ++I)
- ValVec.push_back(fromBytes(Bytes.slice(I * Stride, StoreSize), ElemTy));
+ ValVec.push_back(fromBytes(Bytes.slice(I * Stride, StoreSize), ElemTy,
+ ContainsUndefinedBits));
return AnyValue(std::move(ValVec));
}
if (auto *StructTy = dyn_cast<StructType>(Ty)) {
@@ -230,7 +245,7 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty) {
ValVec.push_back(fromBytes(
Bytes.slice(getEffectiveTypeSize(Layout->getElementOffset(I)),
getEffectiveTypeStoreSize(ElemTy)),
- ElemTy));
+ ElemTy, ContainsUndefinedBits));
}
return AnyValue(std::move(ValVec));
}
@@ -358,9 +373,11 @@ void Context::toBytes(const AnyValue &Val, Type *Ty,
llvm_unreachable("Unsupported first class type.");
}
-AnyValue Context::load(MemoryObject &MO, uint64_t Offset, Type *ValTy) {
+AnyValue Context::load(MemoryObject &MO, uint64_t Offset, Type *ValTy,
+ bool *ContainsUndefinedBits) {
return fromBytes(
- MO.getBytes().slice(Offset, getEffectiveTypeStoreSize(ValTy)), ValTy);
+ MO.getBytes().slice(Offset, getEffectiveTypeStoreSize(ValTy)), ValTy,
+ ContainsUndefinedBits);
}
void Context::store(MemoryObject &MO, uint64_t Offset, const AnyValue &Val,
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 9e128076ca17e..aa9d61cc6077e 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -211,7 +211,7 @@ class Context {
// the provenance.
std::map<uint64_t, IntrusiveRefCntPtr<MemoryObject>> MemoryObjects;
AnyValue fromBytes(ConstBytesView Bytes, Type *Ty, uint32_t OffsetInBits,
- bool CheckPaddingBits);
+ bool CheckPaddingBits, bool *ContainsUndefinedBits);
void toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
MutableBytesView Bytes, bool PaddingBits);
@@ -279,11 +279,15 @@ class Context {
Pointer deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj);
/// Convert byte sequence to a value of the given type. Uninitialized bits are
/// flushed according to the options.
- AnyValue fromBytes(ArrayRef<Byte> Bytes, Type *Ty);
+ /// If \p ContainsUndefinedBits is provided, it will be set to true when there
+ /// are poison or undef bits in the value (i.e., padding bits are ignored).
+ AnyValue fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
+ bool *ContainsUndefinedBits = nullptr);
/// Convert a value to byte sequence. Padding bits are set to zero.
void toBytes(const AnyValue &Val, Type *Ty, MutableArrayRef<Byte> Bytes);
/// Direct memory load without checks.
- AnyValue load(MemoryObject &MO, uint64_t Offset, Type *ValTy);
+ AnyValue load(MemoryObject &MO, uint64_t Offset, Type *ValTy,
+ bool *ContainsUndefinedBits = nullptr);
/// Direct memory store without checks.
void store(MemoryObject &MO, uint64_t Offset, const AnyValue &Val,
Type *ValTy);
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 233497e041b00..5c1479d277bf9 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -100,7 +100,8 @@ std::optional<uint64_t> ExecutorBase::verifyMemAccess(const MemoryObject &MO,
return Offset.getZExtValue();
}
-AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy) {
+AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
+ bool NoUndef) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return AnyValue::getPoisonValue(Ctx, ValTy);
@@ -121,7 +122,12 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy) {
if (MO->getState() == MemoryObjectState::Dead)
return AnyValue::getPoisonValue(Ctx, ValTy);
- return Ctx.load(*MO, *Offset, ValTy);
+ bool ContainsUndefinedBits = false;
+ AnyValue Res = Ctx.load(*MO, *Offset, ValTy,
+ NoUndef ? &ContainsUndefinedBits : nullptr);
+ if (NoUndef && ContainsUndefinedBits)
+ reportImmediateUB() << "The value loaded contains undefined bits.";
+ return Res;
}
return AnyValue::getPoisonValue(Ctx, ValTy);
}
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index 64933929fdc35..d07b4dae1792e 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -106,7 +106,8 @@ class ExecutorBase {
uint64_t AccessSize, Align Alignment,
bool IsStore);
- AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy);
+ AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
+ bool NoUndef);
void store(const AnyValue &Ptr, Align Alignment, const AnyValue &Val,
Type *ValTy);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index ec6ee07a35471..f50f074d9d457 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1691,10 +1691,9 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitLoadInst(LoadInst &LI) {
- auto RetVal =
- load(getValue(LI.getPointerOperand()), LI.getAlign(), LI.getType());
+ auto RetVal = load(getValue(LI.getPointerOperand()), LI.getAlign(),
+ LI.getType(), LI.hasMetadata(LLVMContext::MD_noundef));
// TODO: track volatile loads
- // TODO: Check undef bits when !noundef is set.
handleMetadata(LI.getType(), RetVal, LI);
setResult(LI, std::move(RetVal));
}
More information about the llvm-commits
mailing list