[llvm] [llubi] Poison object contents in `llvm.lifetime.end` (PR #206036)

Zhige Chen via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 26 09:46:54 PDT 2026


https://github.com/nofe1248 updated https://github.com/llvm/llvm-project/pull/206036

>From f76fec04ca3d30f8a334a67b11da3abce98b7349 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 26 Jun 2026 19:23:54 +0800
Subject: [PATCH 1/3] [llubi] Poison object contents in `llvm.lifetime.end`

---
 llvm/test/tools/llubi/intr_memory.ll  | 19 +++++++++++++++++++
 llvm/tools/llubi/lib/Context.h        |  7 ++++---
 llvm/tools/llubi/lib/ExecutorBase.cpp |  4 ----
 llvm/tools/llubi/lib/Interpreter.cpp  |  9 +++------
 4 files changed, 26 insertions(+), 13 deletions(-)

diff --git a/llvm/test/tools/llubi/intr_memory.ll b/llvm/test/tools/llubi/intr_memory.ll
index 5ab3a1d101fa1..3f51188602659 100644
--- a/llvm/test/tools/llubi/intr_memory.ll
+++ b/llvm/test/tools/llubi/intr_memory.ll
@@ -51,6 +51,16 @@ define void @main() {
   %prov_copy = load ptr, ptr %prov_dst, align 8
   store i8 0, ptr %prov_copy, align 1
 
+  %dead_src = alloca [4 x i8], align 1
+  %dead_dst = alloca [4 x i8], align 1
+  call void @llvm.lifetime.start.p0(ptr %dead_src)
+  call void @llvm.memset.p0.i8(ptr %dead_src, i8 51, i8 4, i1 false)
+  call void @llvm.lifetime.end.p0(ptr %dead_src)
+  call void @llvm.memcpy.p0.p0.i8(ptr %dead_dst, ptr %dead_src, i8 4, i1 false)
+  %dead_copy = load i32, ptr %dead_dst, align 1
+  call void @llvm.lifetime.start.p0(ptr %dead_src)
+  %restarted = load i32, ptr %dead_src, align 1
+
   call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
   call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
 
@@ -100,6 +110,15 @@ define void @main() {
 ; CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i8(ptr %prov_dst, ptr %prov_src, i8 8, i1 false)
 ; CHECK-NEXT:   %prov_copy = load ptr, ptr %prov_dst, align 8 => ptr 0x41 [prov_ptr]
 ; CHECK-NEXT:   store i8 0, ptr %prov_copy, align 1
+; CHECK-NEXT:   %dead_src = alloca [4 x i8], align 1 => ptr 0x43 [dead_src (dead)]
+; CHECK-NEXT:   %dead_dst = alloca [4 x i8], align 1 => ptr 0x48 [dead_dst]
+; CHECK-NEXT:   call void @llvm.lifetime.start.p0(ptr %dead_src)
+; CHECK-NEXT:   call void @llvm.memset.p0.i8(ptr %dead_src, i8 51, i8 4, i1 false)
+; CHECK-NEXT:   call void @llvm.lifetime.end.p0(ptr %dead_src)
+; CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i8(ptr %dead_dst, ptr %dead_src, i8 4, i1 false)
+; CHECK-NEXT:   %dead_copy = load i32, ptr %dead_dst, align 1 => poison
+; CHECK-NEXT:   call void @llvm.lifetime.start.p0(ptr %dead_src)
+; CHECK-NEXT:   %restarted = load i32, ptr %dead_src, align 1 => i32 -1393641077
 ; CHECK-NEXT:   call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
 ; CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
 ; CHECK-NEXT:   ret void
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 5f8b79a6b183c..7c1e8e82c7e74 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -43,9 +43,10 @@ enum class MemoryObjectState {
   //   -> Dead (after the end of lifetime of an alloca)
   //   -> Freed (after free is called on a heap object)
   Alive,
-  // This memory object is out of lifetime. It is OK to perform
-  // operations that do not access its content, e.g., getelementptr.
-  // Otherwise, an immediate UB occurs.
+  // This memory object is out of lifetime. Its contents are poison. Loads and
+  // memory transfers from it are allowed and propagate poison, stores to it
+  // cause immediate UB, and non-accessing operations such as getelementptr are
+  // allowed.
   // Valid transition:
   //   -> Alive (after the start of lifetime of an alloca)
   Dead,
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index da49a934123c9..0cbb38073f652 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -126,10 +126,6 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
           PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
           /*IsStore=*/false);
       MO) {
-    // Load from a dead stack object yields poison value.
-    if (MO->getState() == MemoryObjectState::Dead)
-      return AnyValue::getPoisonValue(Ctx, ValTy);
-
     bool ContainsUndefinedBits = false;
     AnyValue Res = Ctx.load(*MO, Offset, ValTy,
                             NoUndef ? &ContainsUndefinedBits : nullptr);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index fe3c627b358ba..fd39b809d8f25 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -763,12 +763,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
 
     MutableArrayRef<Byte> DstBytes = DstMO->getBytes().slice(DstOffset, Len);
-    if (SrcMO->getState() == MemoryObjectState::Dead) {
-      fill(DstBytes, Byte::poison());
-    } else {
-      ArrayRef<Byte> SrcBytes = SrcMO->getBytes().slice(SrcOffset, Len);
-      std::memmove(DstBytes.data(), SrcBytes.data(), Len * sizeof(Byte));
-    }
+    ArrayRef<Byte> SrcBytes = SrcMO->getBytes().slice(SrcOffset, Len);
+    std::memmove(DstBytes.data(), SrcBytes.data(), Len * sizeof(Byte));
     return AnyValue();
   }
 
@@ -1018,6 +1014,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         MO->setState(MemoryObjectState::Alive);
         fill(MO->getBytes(), Byte::undef());
       } else {
+        fill(MO->getBytes(), Byte::poison());
         MO->setState(MemoryObjectState::Dead);
       }
       return AnyValue();

>From f5754bd06e1c8bfd0c823cdefb408a9345e1782b Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Sat, 27 Jun 2026 00:05:29 +0800
Subject: [PATCH 2/3] [llubi] Add a test

---
 llvm/test/tools/llubi/store_dead_gep.ll | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 llvm/test/tools/llubi/store_dead_gep.ll

diff --git a/llvm/test/tools/llubi/store_dead_gep.ll b/llvm/test/tools/llubi/store_dead_gep.ll
new file mode 100644
index 0000000000000..8367691d72dbf
--- /dev/null
+++ b/llvm/test/tools/llubi/store_dead_gep.ll
@@ -0,0 +1,23 @@
+; 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() {
+  %alloc = alloca [4 x i8], align 1
+  call void @llvm.lifetime.start.p0(ptr %alloc)
+  store i8 0, ptr %alloc, align 1
+  call void @llvm.lifetime.end.p0(ptr %alloc)
+  %gep = getelementptr i8, ptr %alloc, i64 1
+  store i8 0, ptr %gep, align 1
+  ret void
+}
+
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca [4 x i8], align 1 => ptr 0x8 [alloc (dead)]
+; CHECK-NEXT:   call void @llvm.lifetime.start.p0(ptr %alloc)
+; CHECK-NEXT:   store i8 0, ptr %alloc, align 1
+; CHECK-NEXT:   call void @llvm.lifetime.end.p0(ptr %alloc)
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %alloc, i64 1 => ptr 0x9 [alloc + 1 (dead)]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i8 0, ptr %gep, align 1 at @main <stdin>:9
+; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object at address 0x9.
+; CHECK-NEXT: error: Execution of function 'main' failed.

>From a50653e6d400f1b49f1528c3a866d358be52377a Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Sat, 27 Jun 2026 00:46:39 +0800
Subject: [PATCH 3/3] [llubi] Fix test

---
 llvm/test/tools/llubi/store_dead_gep.ll | 1 -
 1 file changed, 1 deletion(-)

diff --git a/llvm/test/tools/llubi/store_dead_gep.ll b/llvm/test/tools/llubi/store_dead_gep.ll
index 8367691d72dbf..df61e90db1423 100644
--- a/llvm/test/tools/llubi/store_dead_gep.ll
+++ b/llvm/test/tools/llubi/store_dead_gep.ll
@@ -1,4 +1,3 @@
-; 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() {



More information about the llvm-commits mailing list