[llvm] 9d43f86 - [llubi] Add support for exposed provenance (#200596)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Jun 13 21:57:39 PDT 2026
Author: Yingwei Zheng
Date: 2026-06-14T04:57:34Z
New Revision: 9d43f86e6f302604b109ffa9df7de793aeb801d8
URL: https://github.com/llvm/llvm-project/commit/9d43f86e6f302604b109ffa9df7de793aeb801d8
DIFF: https://github.com/llvm/llvm-project/commit/9d43f86e6f302604b109ffa9df7de793aeb801d8.diff
LOG: [llubi] Add support for exposed provenance (#200596)
This patch implements the semantics of exposed provenance, as described
in [nikic's RFC draft](https://hackmd.io/@nikic/SJBt4mFCll) and
[Miri](https://doc.rust-lang.org/beta/nightly-rustc/miri/enum.Provenance.html).
The provenance of an inttoptr is marked as "wildcard", which picks one
from previously exposed provenances each time a memory access is
performed. For angelic non-determinism, a snapshot of the exposed
provenance set is recorded when inttoptr executes. When a memory access
is performed, all invalid provenances are masked out. If we fail to pick
one, it is UB.
Since all memory objects in llubi are non-overlapping (i.e., there is at
most one memory object satisfying `Obj->inBounds(Addr)` for each
address), we can determine a unique memory object for a wildcard
provenance when the first memory access is performed.
This matches Miri's behavior. Another variant is to resolve the memory
object when inttoptr executes, which gives a limited provenance set
(implemented in the second commit). See also the comments in nikic's RFC
(`For example, one could limit inttoptr to only recover the provenance
of an object that the address is part of.`).
Another thing to be noted is that `gep inbounds` cannot determine the
associated memory object of a wildcard provenance, because it doesn't
cause UB. Instead, I reuse the Obj field to represent something like
`this pointer has a wildcard provenance, but it is only allowed to use
exposed provenances associated with a specific memory object, when
performing load/store via the pointer`. This is an approximation: we
cannot update the wildcard provenance if the gep is used by an icmp and
a branch.
As we don't support `captures` for now, all checkers just return true.
I'll add more tests in the `captures` patch.
Added:
llvm/test/tools/llubi/inttoptr_freed.ll
llvm/test/tools/llubi/inttoptr_generation.ll
llvm/test/tools/llubi/inttoptr_generation2.ll
llvm/test/tools/llubi/inttoptr_gep.ll
llvm/test/tools/llubi/inttoptr_multiobj.ll
llvm/test/tools/llubi/inttoptr_multiobj2.ll
llvm/test/tools/llubi/inttoptr_oob.ll
llvm/test/tools/llubi/inttoptr_oob2.ll
Modified:
llvm/test/tools/llubi/alloca.ll
llvm/test/tools/llubi/attributes.ll
llvm/test/tools/llubi/gep.ll
llvm/test/tools/llubi/inttoptr.ll
llvm/test/tools/llubi/lib_abort.ll
llvm/test/tools/llubi/lib_exit.ll
llvm/test/tools/llubi/lib_io.ll
llvm/test/tools/llubi/lib_printf_format.ll
llvm/test/tools/llubi/lib_terminate.ll
llvm/test/tools/llubi/loadstore_be.ll
llvm/test/tools/llubi/loadstore_le.ll
llvm/test/tools/llubi/metadata.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
llvm/tools/llubi/lib/Library.cpp
llvm/tools/llubi/lib/Value.cpp
llvm/tools/llubi/lib/Value.h
Removed:
################################################################################
diff --git a/llvm/test/tools/llubi/alloca.ll b/llvm/test/tools/llubi/alloca.ll
index e45d499d3205b..3445ced01331b 100644
--- a/llvm/test/tools/llubi/alloca.ll
+++ b/llvm/test/tools/llubi/alloca.ll
@@ -30,12 +30,12 @@ define void @main() {
; CHECK-NEXT: ret i32 4
; CHECK-NEXT: Exiting function: count
; CHECK-NEXT: %count = call i32 @count() => i32 4
-; CHECK-NEXT: %alloc_dyn = alloca i32, i32 %count, align 4 => ptr 0x44 [alloc_dyn]
-; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x58 [alloc_struct]
+; CHECK-NEXT: %alloc_dyn = alloca i32, i32 %count, align 4 => ptr 0x48 [alloc_dyn]
+; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x60 [alloc_struct]
; CHECK-NEXT: Entering function: stack_address
-; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x70 [alloc]
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x7C [alloc]
; CHECK-NEXT: ret ptr %alloc
; CHECK-NEXT: Exiting function: stack_address
-; CHECK-NEXT: %ptr = call ptr @stack_address() => ptr 0x70 [alloc (dangling)]
+; CHECK-NEXT: %ptr = call ptr @stack_address() => ptr 0x7C [alloc (dangling)]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
index cc8fcf62a46f9..87057ab816ba7 100644
--- a/llvm/test/tools/llubi/attributes.ll
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -320,7 +320,7 @@ define void @main() {
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
; CHECK-NEXT: %deref_or_null_mixed = call dereferenceable(1) dereferenceable_or_null(1) ptr @identity_dereferenceable_or_null(ptr dereferenceable(1) dereferenceable_or_null(1) %alloc) => ptr 0x8 [alloc]
-; CHECK-NEXT: %fmt_n_out = alloca [6 x i8], align 1 => ptr 0xC [fmt_n_out]
+; CHECK-NEXT: %fmt_n_out = alloca [6 x i8], align 1 => ptr 0xD [fmt_n_out]
; CHECK-NEXT: store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
; CHECK-NEXT: N=6
; CHECK-NEXT: %res = call noundef range(i32 0, 15) i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6) => i32 4
diff --git a/llvm/test/tools/llubi/gep.ll b/llvm/test/tools/llubi/gep.ll
index 3426249fa138c..8cfa63b105b4b 100644
--- a/llvm/test/tools/llubi/gep.ll
+++ b/llvm/test/tools/llubi/gep.ll
@@ -117,11 +117,11 @@ define void @main() {
; CHECK-NEXT: %gep_inbounds_invalid2 = getelementptr inbounds i8, ptr %alloc, i64 5 => poison
; CHECK-NEXT: %gep_inbounds_invalid3 = getelementptr inbounds %struct, ptr %alloc_struct, i64 1, i32 1, i32 -2 => poison
; CHECK-NEXT: Entering function: dead_stack_object
-; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x28 [alloc]
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x2C [alloc]
; CHECK-NEXT: ret ptr %alloc
; CHECK-NEXT: Exiting function: dead_stack_object
-; CHECK-NEXT: %dead_stack_ptr = call ptr @dead_stack_object() => ptr 0x28 [alloc (dangling)]
-; CHECK-NEXT: %gep_inbounds_valid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 4 => ptr 0x2C [alloc + 4 (dangling)]
+; CHECK-NEXT: %dead_stack_ptr = call ptr @dead_stack_object() => ptr 0x2C [alloc (dangling)]
+; CHECK-NEXT: %gep_inbounds_valid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 4 => ptr 0x30 [alloc + 4 (dangling)]
; CHECK-NEXT: %gep_inbounds_invalid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 5 => poison
; CHECK-NEXT: %gep_nusw_valid1 = getelementptr nusw i8, ptr %alloc, i64 -1 => ptr 0x7 [alloc + -1]
; CHECK-NEXT: %gep_nusw_invalid1 = getelementptr nusw i8, ptr %large_address, i64 -2147483649 => poison
diff --git a/llvm/test/tools/llubi/inttoptr.ll b/llvm/test/tools/llubi/inttoptr.ll
index d29d26f69e2ad..53310b84af1e9 100644
--- a/llvm/test/tools/llubi/inttoptr.ll
+++ b/llvm/test/tools/llubi/inttoptr.ll
@@ -7,11 +7,19 @@ define void @main() {
%ptr1 = inttoptr i64 0 to ptr
%ptr2 = inttoptr i8 255 to ptr
%ptr3 = inttoptr i128 -1 to ptr
+ %alloc = alloca i32
+ %off = ptrtoint ptr %alloc to i64
+ %gep = getelementptr i8, ptr %ptr1, i64 %off
+ %cast = inttoptr i64 %off to ptr
ret void
}
; CHECK: Entering function: main
; CHECK-NEXT: %ptr1 = inttoptr i64 0 to ptr => ptr 0x0 [nullary]
; CHECK-NEXT: %ptr2 = inttoptr i8 -1 to ptr => ptr 0xFF [nullary]
; CHECK-NEXT: %ptr3 = inttoptr i128 -1 to ptr => ptr 0xFFFFFFFFFFFFFFFF [nullary]
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %off = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT: %gep = getelementptr i8, ptr %ptr1, i64 %off => ptr 0x8 [nullary]
+; CHECK-NEXT: %cast = inttoptr i64 %off to ptr => ptr 0x8 [wildcard]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/inttoptr_freed.ll b/llvm/test/tools/llubi/inttoptr_freed.ll
new file mode 100644
index 0000000000000..f56d4e1f4a950
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_freed.ll
@@ -0,0 +1,31 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ %alloc = call ptr @malloc(i64 4)
+ %int = ptrtoint ptr %alloc to i64
+ %ptr = inttoptr i64 %int to ptr
+
+ store i32 0, ptr %ptr
+ call void @free(ptr %ptr)
+
+ ; We cannot access a freed object via wildcard provenance.
+ store i32 0, ptr %ptr
+
+ ret void
+}
+
+declare ptr @malloc(i64)
+declare void @free(ptr)
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = call ptr @malloc(i64 4) => ptr 0x10 [alloc]
+; CHECK-NEXT: %int = ptrtoint ptr %alloc to i64 => i64 16
+; CHECK-NEXT: %ptr = inttoptr i64 %int to ptr => ptr 0x10 [wildcard]
+; CHECK-NEXT: store i32 0, ptr %ptr, align 4
+; CHECK-NEXT: call void @free(ptr %ptr)
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %ptr, align 4 at @main <stdin>:15
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/inttoptr_generation.ll b/llvm/test/tools/llubi/inttoptr_generation.ll
new file mode 100644
index 0000000000000..575a669f785fd
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_generation.ll
@@ -0,0 +1,34 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ ; Assume the pointer address is 8.
+ %alloc = alloca i32
+ %ptr1 = inttoptr i64 8 to ptr
+ %int = ptrtoint ptr %alloc to i64
+ %ptr2 = inttoptr i64 %int to ptr
+ %ptr3 = inttoptr i64 8 to ptr
+
+ ; We can access the wildcard pointer through exposed provenances.
+ store i32 0, ptr %ptr2
+ ; We can access the wildcard pointer through exposed provenances, even if there are no dependencies.
+ store i32 0, ptr %ptr3
+ ; We cannot access the pointer created before any provenance is exposed.
+ store i32 0, ptr %ptr1
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %ptr1 = inttoptr i64 8 to ptr => ptr 0x8 [nullary]
+; CHECK-NEXT: %int = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT: %ptr2 = inttoptr i64 %int to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %ptr3 = inttoptr i64 8 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: store i32 0, ptr %ptr2, align 4
+; CHECK-NEXT: store i32 0, ptr %ptr3, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %ptr1, align 4 at @main <stdin>:19
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/inttoptr_generation2.ll b/llvm/test/tools/llubi/inttoptr_generation2.ll
new file mode 100644
index 0000000000000..e04595ad44cb4
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_generation2.ll
@@ -0,0 +1,39 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ ; Assume the pointer address is 8.
+ %alloc = alloca i32
+ %alloc2 = alloca i32
+ ; We expose the provenance of another object so that the slow path is hitted.
+ %tmp = ptrtoint ptr %alloc2 to i64
+ %ptr1 = inttoptr i64 8 to ptr
+ %int = ptrtoint ptr %alloc to i64
+ %ptr2 = inttoptr i64 %int to ptr
+ %ptr3 = inttoptr i64 8 to ptr
+
+ ; We can access the wildcard pointer through exposed provenances.
+ store i32 0, ptr %ptr2
+ ; We can access the wildcard pointer through exposed provenances, even if there are no dependencies.
+ store i32 0, ptr %ptr3
+ ; We cannot access the pointer created before any provenance is exposed.
+ store i32 0, ptr %ptr1
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %alloc2 = alloca i32, align 4 => ptr 0x10 [alloc2]
+; CHECK-NEXT: %tmp = ptrtoint ptr %alloc2 to i64 => i64 16
+; CHECK-NEXT: %ptr1 = inttoptr i64 8 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %int = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT: %ptr2 = inttoptr i64 %int to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %ptr3 = inttoptr i64 8 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: store i32 0, ptr %ptr2, align 4
+; CHECK-NEXT: store i32 0, ptr %ptr3, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %ptr1, align 4 at @main <stdin>:22
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/inttoptr_gep.ll b/llvm/test/tools/llubi/inttoptr_gep.ll
new file mode 100644
index 0000000000000..5de7093a696af
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_gep.ll
@@ -0,0 +1,41 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ %alloc1 = alloca i32
+ %alloc2 = alloca i32
+
+ %addr1 = ptrtoint ptr %alloc1 to i64
+ %addr2 = ptrtoint ptr %alloc2 to i64
+ %ptr
diff 1 = sub i64 %addr2, %addr1
+ %ptr
diff 2 = sub i64 %addr1, %addr2
+
+ %p = inttoptr i64 %addr1 to ptr
+ ; For out-of-bounds GEP with wildcard provenance, it returns poison.
+ %gep_oob = getelementptr inbounds i8, ptr %p, i64 %ptr
diff 1
+ %gep1 = getelementptr i8, ptr %p, i64 %ptr
diff 1
+ %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2
+ %gep3 = getelementptr i8, ptr %gep2, i64 %ptr
diff 2
+
+ ; We cannot access %alloc1 because %gep2 has been resolved to %alloc2.
+ store i16 0, ptr %gep3
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc1 = alloca i32, align 4 => ptr 0x8 [alloc1]
+; CHECK-NEXT: %alloc2 = alloca i32, align 4 => ptr 0x10 [alloc2]
+; CHECK-NEXT: %addr1 = ptrtoint ptr %alloc1 to i64 => i64 8
+; CHECK-NEXT: %addr2 = ptrtoint ptr %alloc2 to i64 => i64 16
+; CHECK-NEXT: %ptr
diff 1 = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT: %ptr
diff 2 = sub i64 %addr1, %addr2 => i64 -8
+; CHECK-NEXT: %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %gep_oob = getelementptr inbounds i8, ptr %p, i64 %ptr
diff 1 => poison
+; CHECK-NEXT: %gep1 = getelementptr i8, ptr %p, i64 %ptr
diff 1 => ptr 0x10 [wildcard]
+; CHECK-NEXT: %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2 => ptr 0x12 [alloc2 + 2]
+; CHECK-NEXT: %gep3 = getelementptr i8, ptr %gep2, i64 %ptr
diff 2 => ptr 0xA [alloc2 + -6]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i16 0, ptr %gep3, align 2 at @main <stdin>:23
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/inttoptr_multiobj.ll b/llvm/test/tools/llubi/inttoptr_multiobj.ll
new file mode 100644
index 0000000000000..aaf305b3851c8
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_multiobj.ll
@@ -0,0 +1,40 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ %alloc1 = alloca i32
+ %alloc2 = alloca i32
+
+ %addr1 = ptrtoint ptr %alloc1 to i64
+ %addr2 = ptrtoint ptr %alloc2 to i64
+ %ptr
diff = sub i64 %addr2, %addr1
+
+ %p = inttoptr i64 %addr1 to ptr
+
+ %gep1 = getelementptr i8, ptr %p, i64 %ptr
diff
+ %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2
+
+ ; We can access %alloc1, although %gep2 has been resolved to %alloc2.
+ store i32 0, ptr %p
+
+ ; Now we cannot access %alloc2 through %gep2, since the wildcard provenance has been resolved to %alloc1.
+ store i16 0, ptr %gep2
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc1 = alloca i32, align 4 => ptr 0x8 [alloc1]
+; CHECK-NEXT: %alloc2 = alloca i32, align 4 => ptr 0x10 [alloc2]
+; CHECK-NEXT: %addr1 = ptrtoint ptr %alloc1 to i64 => i64 8
+; CHECK-NEXT: %addr2 = ptrtoint ptr %alloc2 to i64 => i64 16
+; CHECK-NEXT: %ptr
diff = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT: %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %gep1 = getelementptr i8, ptr %p, i64 %ptr
diff => ptr 0x10 [wildcard]
+; CHECK-NEXT: %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2 => ptr 0x12 [alloc2 + 2]
+; CHECK-NEXT: store i32 0, ptr %p, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i16 0, ptr %gep2, align 2 at @main <stdin>:23
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/inttoptr_multiobj2.ll b/llvm/test/tools/llubi/inttoptr_multiobj2.ll
new file mode 100644
index 0000000000000..f44c07722fac0
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_multiobj2.ll
@@ -0,0 +1,41 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ %alloc1 = alloca i32
+ %alloc2 = alloca i32
+
+ %addr1 = ptrtoint ptr %alloc1 to i64
+ %addr2 = ptrtoint ptr %alloc2 to i64
+ %ptr
diff = sub i64 %addr2, %addr1
+
+ %p = inttoptr i64 %addr1 to ptr
+ %gep = getelementptr i8, ptr %p, i64 %ptr
diff
+
+ ; After %alloc1 is accessed, we cannot access other memory objects later.
+ store i32 0, ptr %p
+
+ ; %gep2 is poison as the wildcard provenance has been resolved to %alloc1.
+ %gep2 = getelementptr inbounds i8, ptr %p, i64 %ptr
diff
+
+ ; Now we cannot access %alloc2 via the pointer derived from %p.
+ store i32 0, ptr %gep
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc1 = alloca i32, align 4 => ptr 0x8 [alloc1]
+; CHECK-NEXT: %alloc2 = alloca i32, align 4 => ptr 0x10 [alloc2]
+; CHECK-NEXT: %addr1 = ptrtoint ptr %alloc1 to i64 => i64 8
+; CHECK-NEXT: %addr2 = ptrtoint ptr %alloc2 to i64 => i64 16
+; CHECK-NEXT: %ptr
diff = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT: %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i64 %ptr
diff => ptr 0x10 [wildcard]
+; CHECK-NEXT: store i32 0, ptr %p, align 4
+; CHECK-NEXT: %gep2 = getelementptr inbounds i8, ptr %p, i64 %ptr
diff => poison
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %gep, align 4 at @main <stdin>:24
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/inttoptr_oob.ll b/llvm/test/tools/llubi/inttoptr_oob.ll
new file mode 100644
index 0000000000000..baf64792d6cf4
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_oob.ll
@@ -0,0 +1,30 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ %alloc = alloca i32
+ %int = ptrtoint ptr %alloc to i64
+ %ptr = inttoptr i64 %int to ptr
+
+ %ptr_oob = getelementptr i8, ptr %ptr, i64 5
+ %ptr_inbounds_resolved = getelementptr inbounds i8, ptr %ptr, i64 4
+ %ptr_inbounds_oob = getelementptr inbounds i8, ptr %ptr, i64 5
+
+ ; We cannot resolve to any exposed provenance.
+ store i64 0, ptr %ptr_oob
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %int = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT: %ptr = inttoptr i64 %int to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %ptr_oob = getelementptr i8, ptr %ptr, i64 5 => ptr 0xD [wildcard]
+; CHECK-NEXT: %ptr_inbounds_resolved = getelementptr inbounds i8, ptr %ptr, i64 4 => ptr 0xC [alloc + 4]
+; CHECK-NEXT: %ptr_inbounds_oob = getelementptr inbounds i8, ptr %ptr, i64 5 => poison
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i64 0, ptr %ptr_oob, align 4 at @main <stdin>:16
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/inttoptr_oob2.ll b/llvm/test/tools/llubi/inttoptr_oob2.ll
new file mode 100644
index 0000000000000..a2e85b1fa697b
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_oob2.ll
@@ -0,0 +1,33 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ %alloc = alloca i32
+ ; Another memory object with high address.
+ %alloc2 = alloca i32, align 256
+ %int = ptrtoint ptr %alloc to i64
+ %ptr = inttoptr i64 %int to ptr
+
+ %ptr_oob = getelementptr i8, ptr %ptr, i64 5
+ %ptr_inbounds_resolved = getelementptr inbounds i8, ptr %ptr, i64 4
+ %ptr_inbounds_oob = getelementptr inbounds i8, ptr %ptr, i64 5
+
+ ; We cannot resolve to any exposed provenance.
+ store i64 0, ptr %ptr_oob
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %alloc2 = alloca i32, align 256 => ptr 0x100 [alloc2]
+; CHECK-NEXT: %int = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT: %ptr = inttoptr i64 %int to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %ptr_oob = getelementptr i8, ptr %ptr, i64 5 => ptr 0xD [wildcard]
+; CHECK-NEXT: %ptr_inbounds_resolved = getelementptr inbounds i8, ptr %ptr, i64 4 => ptr 0xC [alloc + 4]
+; CHECK-NEXT: %ptr_inbounds_oob = getelementptr inbounds i8, ptr %ptr, i64 5 => poison
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i64 0, ptr %ptr_oob, align 4 at @main <stdin>:18
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_abort.ll b/llvm/test/tools/llubi/lib_abort.ll
index 78ed03c6c70ca..f1d3b829d8076 100644
--- a/llvm/test/tools/llubi/lib_abort.ll
+++ b/llvm/test/tools/llubi/lib_abort.ll
@@ -23,7 +23,7 @@ entry:
; CHECK: Entering function: main
; CHECK-NEXT: %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
; CHECK-NEXT: store [7 x i8] c"Before\00", ptr %before, align 1
-; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0x10 [after]
; CHECK-NEXT: store [6 x i8] c"After\00", ptr %after, align 1
; CHECK-NEXT: Before
; CHECK-NEXT: %0 = call i32 @puts(ptr %before) => i32 1
diff --git a/llvm/test/tools/llubi/lib_exit.ll b/llvm/test/tools/llubi/lib_exit.ll
index 7239352f18182..f5b0e923fcf44 100644
--- a/llvm/test/tools/llubi/lib_exit.ll
+++ b/llvm/test/tools/llubi/lib_exit.ll
@@ -23,7 +23,7 @@ entry:
; CHECK: Entering function: main
; CHECK-NEXT: %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
; CHECK-NEXT: store [7 x i8] c"Before\00", ptr %before, align 1
-; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0x10 [after]
; CHECK-NEXT: store [6 x i8] c"After\00", ptr %after, align 1
; CHECK-NEXT: Before
; CHECK-NEXT: %0 = call i32 @puts(ptr %before) => i32 1
diff --git a/llvm/test/tools/llubi/lib_io.ll b/llvm/test/tools/llubi/lib_io.ll
index 157d11942a64c..d6f71c61d73d5 100644
--- a/llvm/test/tools/llubi/lib_io.ll
+++ b/llvm/test/tools/llubi/lib_io.ll
@@ -26,9 +26,9 @@ entry:
; CHECK-NEXT: store [13 x i8] c"Hello, puts!\00", ptr %puts.str, align 1
; CHECK-NEXT: Hello, puts!
; CHECK-NEXT: %0 = call i32 @puts(ptr %puts.str) => i32 1
-; CHECK-NEXT: %fmt.str = alloca [18 x i8], align 1 => ptr 0x15 [fmt.str]
+; CHECK-NEXT: %fmt.str = alloca [18 x i8], align 1 => ptr 0x16 [fmt.str]
; CHECK-NEXT: store [18 x i8] c"Int: %d, Str: %s\0A\00", ptr %fmt.str, align 1
-; CHECK-NEXT: %arg.str = alloca [5 x i8], align 1 => ptr 0x27 [arg.str]
+; CHECK-NEXT: %arg.str = alloca [5 x i8], align 1 => ptr 0x29 [arg.str]
; CHECK-NEXT: store [5 x i8] c"test\00", ptr %arg.str, align 1
; CHECK-NEXT: Int: 42, Str: test
; CHECK-NEXT: %1 = call i32 (ptr, ...) @printf(ptr %fmt.str, i32 42, ptr %arg.str) => i32 19
diff --git a/llvm/test/tools/llubi/lib_printf_format.ll b/llvm/test/tools/llubi/lib_printf_format.ll
index e4d5dea1b6a27..b14b0f618f4a0 100644
--- a/llvm/test/tools/llubi/lib_printf_format.ll
+++ b/llvm/test/tools/llubi/lib_printf_format.ll
@@ -46,27 +46,27 @@ entry:
; CHECK: Entering function: main
; CHECK-NEXT: %fmt_int = alloca [36 x i8], align 1 => ptr 0x8 [fmt_int]
; CHECK-NEXT: store [36 x i8] c"Ints: %d, %i, %u, %o, %x, %X, %05d\0A\00", ptr %fmt_int, align 1
-; CHECK-NEXT: %fmt_len = alloca [35 x i8], align 1 => ptr 0x2C [fmt_len]
+; CHECK-NEXT: %fmt_len = alloca [35 x i8], align 1 => ptr 0x2D [fmt_len]
; CHECK-NEXT: store [35 x i8] c"Lengths: %ld, %lld, %hd, %hhu, %c\0A\00", ptr %fmt_len, align 1
-; CHECK-NEXT: %fmt_str_ptr = alloca [18 x i8], align 1 => ptr 0x4F [fmt_str_ptr]
+; CHECK-NEXT: %fmt_str_ptr = alloca [18 x i8], align 1 => ptr 0x51 [fmt_str_ptr]
; CHECK-NEXT: store [18 x i8] c"Str: %s, Ptr: %p\0A\00", ptr %fmt_str_ptr, align 1
-; CHECK-NEXT: %fmt_pct = alloca [15 x i8], align 1 => ptr 0x61 [fmt_pct]
+; CHECK-NEXT: %fmt_pct = alloca [15 x i8], align 1 => ptr 0x64 [fmt_pct]
; CHECK-NEXT: store [15 x i8] c"Percent: %d%%\0A\00", ptr %fmt_pct, align 1
-; CHECK-NEXT: %dummy_str = alloca [6 x i8], align 1 => ptr 0x70 [dummy_str]
+; CHECK-NEXT: %dummy_str = alloca [6 x i8], align 1 => ptr 0x74 [dummy_str]
; CHECK-NEXT: store [6 x i8] c"llubi\00", ptr %dummy_str, align 1
-; CHECK-NEXT: %fmt_float = alloca [20 x i8], align 1 => ptr 0x76 [fmt_float]
+; CHECK-NEXT: %fmt_float = alloca [20 x i8], align 1 => ptr 0x7B [fmt_float]
; CHECK-NEXT: store [20 x i8] c"Floats: %f, %e, %g\0A\00", ptr %fmt_float, align 1
-; CHECK-NEXT: %fmt_n = alloca [15 x i8], align 1 => ptr 0x8A [fmt_n]
+; CHECK-NEXT: %fmt_n = alloca [15 x i8], align 1 => ptr 0x90 [fmt_n]
; CHECK-NEXT: store [15 x i8] c"Count: %nDone\0A\00", ptr %fmt_n, align 1
-; CHECK-NEXT: %fmt_n_out = alloca [6 x i8], align 1 => ptr 0x99 [fmt_n_out]
+; CHECK-NEXT: %fmt_n_out = alloca [6 x i8], align 1 => ptr 0xA0 [fmt_n_out]
; CHECK-NEXT: store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
-; CHECK-NEXT: %n_count = alloca i32, align 4 => ptr 0xA0 [n_count]
+; CHECK-NEXT: %n_count = alloca i32, align 4 => ptr 0xA8 [n_count]
; CHECK-NEXT: store i32 0, ptr %n_count, align 4
; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 00042
; CHECK-NEXT: %0 = call i32 (ptr, ...) @printf(ptr %fmt_int, i32 42, i32 -42, i32 255, i32 255, i32 255, i32 255, i32 42) => i32 39
; CHECK-NEXT: Lengths: 123456789, 987654321, 100, 50, A
; CHECK-NEXT: %1 = call i32 (ptr, ...) @printf(ptr %fmt_len, i64 123456789, i64 987654321, i32 100, i32 50, i32 65) => i32 42
-; CHECK-NEXT: Str: llubi, Ptr: 0x70
+; CHECK-NEXT: Str: llubi, Ptr: 0x74
; CHECK-NEXT: %2 = call i32 (ptr, ...) @printf(ptr %fmt_str_ptr, ptr %dummy_str, ptr %dummy_str) => i32 22
; CHECK-NEXT: Percent: 100%
; CHECK-NEXT: %3 = call i32 (ptr, ...) @printf(ptr %fmt_pct, i32 100) => i32 14
diff --git a/llvm/test/tools/llubi/lib_terminate.ll b/llvm/test/tools/llubi/lib_terminate.ll
index 8026e9aa24497..8849d356d34ae 100644
--- a/llvm/test/tools/llubi/lib_terminate.ll
+++ b/llvm/test/tools/llubi/lib_terminate.ll
@@ -23,7 +23,7 @@ entry:
; CHECK: Entering function: main
; CHECK-NEXT: %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
; CHECK-NEXT: store [7 x i8] c"Before\00", ptr %before, align 1
-; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0x10 [after]
; CHECK-NEXT: store [6 x i8] c"After\00", ptr %after, align 1
; CHECK-NEXT: Before
; CHECK-NEXT: %0 = call i32 @puts(ptr %before) => i32 1
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 8f2e0815f88fe..8f1f851ff13c2 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -146,7 +146,7 @@ define void @main() {
; CHECK-NEXT: %val11 = load i25, ptr %alloc, align 4 => poison
; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr poison)
; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr poison)
-; CHECK-NEXT: %alloc_lifetime = alloca i32, align 4 => ptr 0xC [alloc_lifetime (dead)]
+; CHECK-NEXT: %alloc_lifetime = alloca i32, align 4 => ptr 0x10 [alloc_lifetime (dead)]
; CHECK-NEXT: %val12 = load i32, ptr %alloc_lifetime, align 4 => poison
; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
; CHECK-NEXT: %val13 = load i32, ptr %alloc_lifetime, align 4 => i32 -289830082
@@ -161,52 +161,52 @@ define void @main() {
; CHECK-NEXT: %val16 = load i32, ptr %alloc_lifetime, align 4 => poison
; CHECK-NEXT: store i32 -524288, ptr %alloc, align 4
; CHECK-NEXT: %val17 = load float, ptr %alloc, align 4 => float 0xFFF80000
-; CHECK-NEXT: %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x10 [alloc_vscale]
+; CHECK-NEXT: %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x18 [alloc_vscale]
; CHECK-NEXT: %insert = insertelement <vscale x 1 x i32> poison, i32 1, i32 0 => { i32 1, poison, poison, poison }
; CHECK-NEXT: %ones = shufflevector <vscale x 1 x i32> %insert, <vscale x 1 x i32> poison, <vscale x 1 x i32> zeroinitializer => { i32 1, i32 1, i32 1, i32 1 }
; CHECK-NEXT: %twos = add <vscale x 1 x i32> %ones, %ones => { i32 2, i32 2, i32 2, i32 2 }
; CHECK-NEXT: store <vscale x 1 x i32> %ones, ptr %alloc_vscale, align 4
-; CHECK-NEXT: %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1 => ptr 0x20 [alloc_vscale + 16]
+; CHECK-NEXT: %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1 => ptr 0x28 [alloc_vscale + 16]
; CHECK-NEXT: store <vscale x 1 x i32> %twos, ptr %gep3, align 4
; CHECK-NEXT: %val18 = load <vscale x 2 x i32>, ptr %alloc_vscale, align 8 => { i32 1, i32 1, i32 1, i32 1, i32 2, i32 2, i32 2, i32 2 }
-; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x30 [alloc_struct]
+; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x40 [alloc_struct]
; CHECK-NEXT: store %struct { [2 x i16] [i16 1, i16 2], i64 3 }, ptr %alloc_struct, align 8
; CHECK-NEXT: %val19 = load %struct, ptr %alloc_struct, align 8 => { { i16 1, i16 2 }, i64 3 }
; CHECK-NEXT: %val20 = load i64, ptr %alloc_struct, align 8 => i64 281486191577587
; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 281486962127119
-; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [alloc_struct_packed]
+; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x58 [alloc_struct_packed]
; CHECK-NEXT: store %struct.packed <{ [2 x i16] [i16 1, i16 2], i64 3 }>, ptr %alloc_struct_packed, align 1
; CHECK-NEXT: %val22 = load %struct.packed, ptr %alloc_struct_packed, align 1 => { { i16 1, i16 2 }, i64 3 }
; CHECK-NEXT: %val23 = load i64, ptr %alloc_struct_packed, align 8 => i64 281483566645248
; CHECK-NEXT: %val24 = load i64, ptr %alloc_struct_packed, align 8 => i64 281483566645248
-; CHECK-NEXT: %alloc_struct_vscale = alloca %struct.vscale, align 8 => ptr 0x50 [alloc_struct_vscale]
+; CHECK-NEXT: %alloc_struct_vscale = alloca %struct.vscale, align 8 => ptr 0x68 [alloc_struct_vscale]
; CHECK-NEXT: store %struct.vscale zeroinitializer, ptr %alloc_struct_vscale, align 4
-; CHECK-NEXT: %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1 => ptr 0x60 [alloc_struct_vscale + 16]
+; CHECK-NEXT: %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1 => ptr 0x78 [alloc_struct_vscale + 16]
; CHECK-NEXT: store <vscale x 1 x i32> %ones, ptr %gep4, align 4
; CHECK-NEXT: %val25 = load %struct.vscale, ptr %alloc_struct_vscale, align 4 => { { i32 0, i32 0, i32 0, i32 0 }, { i32 1, i32 1, i32 1, i32 1 } }
-; CHECK-NEXT: %alloc_array = alloca [2 x i32], align 4 => ptr 0x70 [alloc_array]
+; CHECK-NEXT: %alloc_array = alloca [2 x i32], align 4 => ptr 0x8C [alloc_array]
; CHECK-NEXT: store [2 x i32] [i32 1, i32 2], ptr %alloc_array, align 4
; CHECK-NEXT: %val26 = load [2 x i32], ptr %alloc_array, align 4 => { i32 1, i32 2 }
-; CHECK-NEXT: %alloc_i1_vec = alloca <4 x i1>, align 1 => ptr 0x78 [alloc_i1_vec]
+; CHECK-NEXT: %alloc_i1_vec = alloca <4 x i1>, align 1 => ptr 0x95 [alloc_i1_vec]
; CHECK-NEXT: store <4 x i1> <i1 true, i1 false, i1 poison, i1 false>, ptr %alloc_i1_vec, align 1
; CHECK-NEXT: %val27 = load <4 x i1>, ptr %alloc_i1_vec, align 1 => { T, F, poison, F }
; CHECK-NEXT: %val28 = load i8, ptr %alloc_i1_vec, align 1 => poison
-; CHECK-NEXT: %alloc_padding = alloca i31, align 4 => ptr 0x7C [alloc_padding]
+; CHECK-NEXT: %alloc_padding = alloca i31, align 4 => ptr 0x98 [alloc_padding]
; CHECK-NEXT: store i32 0, ptr %alloc_padding, align 4
-; CHECK-NEXT: %alloc_padding_vec = alloca i64, align 8 => ptr 0x80 [alloc_padding_vec]
+; CHECK-NEXT: %alloc_padding_vec = alloca i64, align 8 => ptr 0xA0 [alloc_padding_vec]
; CHECK-NEXT: store { <6 x i5>, i32 } { <6 x i5> zeroinitializer, i32 -1 }, ptr %alloc_padding_vec, align 4
; CHECK-NEXT: %load_agg = load { <6 x i5>, i32 }, ptr %alloc_padding_vec, align 4 => { { i5 0, i5 0, i5 0, i5 0, i5 0, i5 0 }, i32 -1 }
; 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: %alloc_struct_padding = alloca { i8, i32 }, align 8 => ptr 0xB0 [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: %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0xC0 [alloc_ptr]
; CHECK-NEXT: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
-; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [alloc_ptr]
; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 0
; CHECK-NEXT: store i8 %addr_bits, ptr %alloc_ptr, align 1
-; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [nullary]
; 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 3caf02c3e6eaa..9ad92ac6e75e0 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -148,7 +148,7 @@ define void @main() {
; CHECK-NEXT: %val11 = load i25, ptr %alloc, align 4 => poison
; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr poison)
; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr poison)
-; CHECK-NEXT: %alloc_lifetime = alloca i32, align 4 => ptr 0xC [alloc_lifetime (dead)]
+; CHECK-NEXT: %alloc_lifetime = alloca i32, align 4 => ptr 0x10 [alloc_lifetime (dead)]
; CHECK-NEXT: %val12 = load i32, ptr %alloc_lifetime, align 4 => poison
; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
; CHECK-NEXT: %val13 = load i32, ptr %alloc_lifetime, align 4 => i32 -289830082
@@ -163,52 +163,52 @@ define void @main() {
; CHECK-NEXT: %val16 = load i32, ptr %alloc_lifetime, align 4 => poison
; CHECK-NEXT: store i32 -524288, ptr %alloc, align 4
; CHECK-NEXT: %val17 = load float, ptr %alloc, align 4 => float 0xFFF80000
-; CHECK-NEXT: %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x10 [alloc_vscale]
+; CHECK-NEXT: %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x18 [alloc_vscale]
; CHECK-NEXT: %insert = insertelement <vscale x 1 x i32> poison, i32 1, i32 0 => { i32 1, poison, poison, poison }
; CHECK-NEXT: %ones = shufflevector <vscale x 1 x i32> %insert, <vscale x 1 x i32> poison, <vscale x 1 x i32> zeroinitializer => { i32 1, i32 1, i32 1, i32 1 }
; CHECK-NEXT: %twos = add <vscale x 1 x i32> %ones, %ones => { i32 2, i32 2, i32 2, i32 2 }
; CHECK-NEXT: store <vscale x 1 x i32> %ones, ptr %alloc_vscale, align 4
-; CHECK-NEXT: %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1 => ptr 0x20 [alloc_vscale + 16]
+; CHECK-NEXT: %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1 => ptr 0x28 [alloc_vscale + 16]
; CHECK-NEXT: store <vscale x 1 x i32> %twos, ptr %gep3, align 4
; CHECK-NEXT: %val18 = load <vscale x 2 x i32>, ptr %alloc_vscale, align 8 => { i32 1, i32 1, i32 1, i32 1, i32 2, i32 2, i32 2, i32 2 }
-; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x30 [alloc_struct]
+; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x40 [alloc_struct]
; CHECK-NEXT: store %struct { [2 x i16] [i16 1, i16 2], i64 3 }, ptr %alloc_struct, align 8
; CHECK-NEXT: %val19 = load %struct, ptr %alloc_struct, align 8 => { { i16 1, i16 2 }, i64 3 }
; CHECK-NEXT: %val20 = load i64, ptr %alloc_struct, align 8 => i64 -7172745523491635199
; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 -3863260483603529727
-; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [alloc_struct_packed]
+; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x58 [alloc_struct_packed]
; CHECK-NEXT: store %struct.packed <{ [2 x i16] [i16 1, i16 2], i64 3 }>, ptr %alloc_struct_packed, align 1
; CHECK-NEXT: %val22 = load %struct.packed, ptr %alloc_struct_packed, align 1 => { { i16 1, i16 2 }, i64 3 }
; CHECK-NEXT: %val23 = load i64, ptr %alloc_struct_packed, align 8 => i64 12885032961
; CHECK-NEXT: %val24 = load i64, ptr %alloc_struct_packed, align 8 => i64 12885032961
-; CHECK-NEXT: %alloc_struct_vscale = alloca %struct.vscale, align 8 => ptr 0x50 [alloc_struct_vscale]
+; CHECK-NEXT: %alloc_struct_vscale = alloca %struct.vscale, align 8 => ptr 0x68 [alloc_struct_vscale]
; CHECK-NEXT: store %struct.vscale zeroinitializer, ptr %alloc_struct_vscale, align 4
-; CHECK-NEXT: %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1 => ptr 0x60 [alloc_struct_vscale + 16]
+; CHECK-NEXT: %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1 => ptr 0x78 [alloc_struct_vscale + 16]
; CHECK-NEXT: store <vscale x 1 x i32> %ones, ptr %gep4, align 4
; CHECK-NEXT: %val25 = load %struct.vscale, ptr %alloc_struct_vscale, align 4 => { { i32 0, i32 0, i32 0, i32 0 }, { i32 1, i32 1, i32 1, i32 1 } }
-; CHECK-NEXT: %alloc_array = alloca [2 x i32], align 4 => ptr 0x70 [alloc_array]
+; CHECK-NEXT: %alloc_array = alloca [2 x i32], align 4 => ptr 0x8C [alloc_array]
; CHECK-NEXT: store [2 x i32] [i32 1, i32 2], ptr %alloc_array, align 4
; CHECK-NEXT: %val26 = load [2 x i32], ptr %alloc_array, align 4 => { i32 1, i32 2 }
-; CHECK-NEXT: %alloc_i1_vec = alloca <4 x i1>, align 1 => ptr 0x78 [alloc_i1_vec]
+; CHECK-NEXT: %alloc_i1_vec = alloca <4 x i1>, align 1 => ptr 0x95 [alloc_i1_vec]
; CHECK-NEXT: store <4 x i1> <i1 true, i1 false, i1 poison, i1 false>, ptr %alloc_i1_vec, align 1
; CHECK-NEXT: %val27 = load <4 x i1>, ptr %alloc_i1_vec, align 1 => { T, F, poison, F }
; CHECK-NEXT: %val28 = load i8, ptr %alloc_i1_vec, align 1 => poison
-; CHECK-NEXT: %alloc_padding = alloca i31, align 4 => ptr 0x7C [alloc_padding]
+; CHECK-NEXT: %alloc_padding = alloca i31, align 4 => ptr 0x98 [alloc_padding]
; CHECK-NEXT: store i32 0, ptr %alloc_padding, align 4
-; CHECK-NEXT: %alloc_padding_vec = alloca i64, align 8 => ptr 0x80 [alloc_padding_vec]
+; CHECK-NEXT: %alloc_padding_vec = alloca i64, align 8 => ptr 0xA0 [alloc_padding_vec]
; CHECK-NEXT: store { <6 x i5>, i32 } { <6 x i5> zeroinitializer, i32 -1 }, ptr %alloc_padding_vec, align 4
; CHECK-NEXT: %load_agg = load { <6 x i5>, i32 }, ptr %alloc_padding_vec, align 4 => { { i5 0, i5 0, i5 0, i5 0, i5 0, i5 0 }, i32 -1 }
; 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: %alloc_struct_padding = alloca { i8, i32 }, align 8 => ptr 0xB0 [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: %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0xC0 [alloc_ptr]
; CHECK-NEXT: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
-; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
-; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -112
+; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [alloc_ptr]
+; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -64
; CHECK-NEXT: store i8 %addr_bits, ptr %alloc_ptr, align 1
-; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [nullary]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/metadata.ll b/llvm/test/tools/llubi/metadata.ll
index 4c2759698e260..bf971c8ad7ef8 100644
--- a/llvm/test/tools/llubi/metadata.ll
+++ b/llvm/test/tools/llubi/metadata.ll
@@ -56,9 +56,9 @@ define void @main() {
; CHECK-NEXT: store float 0.000000e+00, ptr %alloc, align 4
; CHECK-NEXT: %nofpclass_load_valid = load float, ptr %alloc, align 4, !noundef !1, !nofpclass !4 => float 0.000000e+00
; CHECK-NEXT: %nofpclass_load_invalid = load float, ptr %alloc, align 4, !nofpclass !5 => poison
-; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x48 [alloc_ptr]
; CHECK-NEXT: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
-; CHECK-NEXT: %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !dereferenceable !6, !dereferenceable_or_null !6, !align !6, !noundef !1 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !dereferenceable !6, !dereferenceable_or_null !6, !align !6, !noundef !1 => ptr 0x48 [alloc_ptr]
; CHECK-NEXT: store ptr null, ptr %alloc_ptr, align 8
; CHECK-NEXT: %align_load_valid = load ptr, ptr %alloc_ptr, align 8, !dereferenceable_or_null !6, !align !6, !noundef !1 => ptr 0x0 [nullary]
; CHECK-NEXT: %nonnull_load_invalid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1 => poison
@@ -79,10 +79,10 @@ define void @main() {
; CHECK-NEXT: Exiting function: callee_fp
; CHECK-NEXT: %nofpclass_call_invalid = call float @callee_fp(), !nofpclass !5 => poison
; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT: ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: ptr %x = ptr 0x48 [alloc_ptr]
; CHECK-NEXT: ret ptr %x
; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT: %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !1, !align !6, !noundef !1 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !1, !align !6, !noundef !1 => ptr 0x48 [alloc_ptr]
; CHECK-NEXT: Entering function: callee_ptr
; CHECK-NEXT: ptr %x = ptr 0x0 [nullary]
; CHECK-NEXT: ret ptr %x
@@ -94,15 +94,15 @@ define void @main() {
; CHECK-NEXT: Exiting function: callee_ptr
; CHECK-NEXT: %nonnull_call_invalid = call ptr @callee_ptr(ptr null), !nonnull !1 => poison
; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT: ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: ptr %x = ptr 0x48 [alloc_ptr]
; CHECK-NEXT: ret ptr %x
; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT: %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !6 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !6 => ptr 0x48 [alloc_ptr]
; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT: ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: ptr %x = ptr 0x48 [alloc_ptr]
; CHECK-NEXT: ret ptr %x
; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT: %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !6 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !6 => ptr 0x48 [alloc_ptr]
; CHECK-NEXT: Entering function: callee_ptr
; CHECK-NEXT: ptr %x = ptr 0x0 [nullary]
; CHECK-NEXT: ret ptr %x
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index dc503a788754a..7b9d0d849e5f3 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -71,7 +71,7 @@ bool Context::initGlobalValues() {
if (!GV.hasInitializer())
continue;
- MemoryObject *Obj = GlobalAddrMap.at(&GV).getMemoryObject();
+ MemoryObject *Obj = GlobalAddrMap.at(&GV).provenance().getMemoryObject();
assert(Obj && "global pointer should have memory object provenance");
Constant *Init = GV.getInitializer();
@@ -581,7 +581,9 @@ Context::allocate(uint64_t Size, uint64_t Align, StringRef Name, unsigned AS,
auto MemObj = makeIntrusiveRefCnt<MemoryObject>(
AlignedAddr, Size, Name, AS, InitKind, AllocKind, IsIRGlobalValue);
MemoryObjects[AlignedAddr] = MemObj;
- AllocationBase = AlignedAddr + AllocateSize;
+ // Extra padding to make sure getWildcardProvenance resolves to at most one
+ // memory object.
+ AllocationBase = AlignedAddr + AllocateSize + 1;
UsedMem += AllocateSize;
return MemObj;
}
@@ -600,6 +602,7 @@ bool Context::free(const MemoryObject &Obj) {
for (const APInt &Tag : MutableObj.AssociatedTags)
TaggedProvenances.erase(Tag);
MutableObj.AssociatedTags.clear();
+ ExposedProvenances.erase(Address);
MemoryObjects.erase(It);
return true;
@@ -612,6 +615,103 @@ Pointer Context::deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj) {
Obj->getAddress()));
}
+void Context::exposeProvenance(Provenance &Prov) {
+ if (Prov.Wildcard)
+ return;
+ MemoryObject *Obj = Prov.getMemoryObject();
+ if (!Obj)
+ return;
+ uint64_t Address = Obj->getAddress();
+ ExposedProvenanceSet &Set = ExposedProvenances[Address];
+ if (Set.Set.insert(&Prov).second)
+ Set.List.push_back({&Prov, ++ExposedProvenanceSetGeneration});
+}
+
+MemoryObject *
+Context::checkProvenance(const Pointer &Ptr,
+ function_ref<bool(const Provenance &)> Check,
+ bool HasSideEffect) {
+ auto &Prov = Ptr.provenance();
+ if (!Check(Prov))
+ return nullptr;
+ // Early return for concrete provenances.
+ if (!Prov.Wildcard)
+ return Prov.Obj.get();
+
+ MemoryObject *MO = nullptr;
+ APInt &Mask = Prov.Wildcard->ActiveMask;
+ SmallVector<ExposedProvenance> *List = nullptr;
+ uint32_t ProvenanceCount = 0;
+ if (Mask.isZero()) {
+ // The memory object hasn't been determined.
+ uint64_t Addr = Ptr.address().getLimitedValue();
+ auto Iter = ExposedProvenances.upper_bound(Addr);
+ if (Iter == ExposedProvenances.begin())
+ return nullptr;
+ auto &[BaseAddress, Set] = *std::prev(Iter);
+ auto &Obj = MemoryObjects.at(BaseAddress);
+ if (!Obj->inBounds(Ptr.address()))
+ return nullptr;
+ MO = Obj.get();
+ // We only inspect the first N exposed provenances according to the global
+ // generation number of the wildcard pointer.
+ ProvenanceCount = std::distance(
+ Set.List.begin(),
+ upper_bound(Set.List,
+ ExposedProvenance{nullptr, Prov.Wildcard->Generation}));
+ if (HasSideEffect) {
+ Mask = APInt::getAllOnes(ProvenanceCount);
+ Prov.Wildcard->BaseAddress = BaseAddress;
+ }
+ List = &Set.List;
+ } else {
+ // We already determined the memory object in a previous memory access.
+ uint64_t BaseAddress = Prov.Wildcard->BaseAddress;
+ auto Iter = ExposedProvenances.find(BaseAddress);
+ // The memory object has been freed.
+ if (Iter == ExposedProvenances.end())
+ return nullptr;
+ MO = MemoryObjects.at(BaseAddress).get();
+ if (!MO->inBounds(Ptr.address()))
+ return nullptr;
+ List = &Iter->second.List;
+ ProvenanceCount = Mask.getBitWidth();
+ }
+ if (Prov.Obj) {
+ // We already determined the memory object via speculatable operations like
+ // gep inbounds.
+ if (Prov.Obj.get() != MO)
+ return nullptr;
+ }
+
+ bool Valid = false;
+ for (uint32_t I = 0; I != ProvenanceCount; ++I) {
+ assert((!HasSideEffect || !Mask.isZero()) &&
+ "Mask must be initialized if HasSideEffect is true.");
+ if (!Mask.isZero() && !Mask[I])
+ continue;
+ if (Check(*(*List)[I].Prov)) {
+ Valid = true;
+ // Early return as we don't need to update the Mask.
+ if (!HasSideEffect)
+ break;
+ } else if (HasSideEffect)
+ Mask.clearBit(I);
+ }
+
+ return Valid ? MO : nullptr;
+}
+
+IntrusiveRefCntPtr<Provenance> Context::getWildcardProvenance() {
+ // No exposed provenances.
+ if (ExposedProvenanceSetGeneration == 0)
+ return Provenance::nullary();
+ auto Prov = makeIntrusiveRefCnt<Provenance>(nullptr);
+ Prov->Wildcard =
+ makeIntrusiveRefCnt<WildcardProvenance>(ExposedProvenanceSetGeneration);
+ return Prov;
+}
+
Function *Context::getTargetFunction(const Pointer &Ptr) {
if (Ptr.address().getActiveBits() > 64)
return nullptr;
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 5daa9e816f145..cc947bd6af51a 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -237,8 +237,40 @@ class Context {
// Mapping from tags to provenances. Tags are lazily generated when a
// pointer is captured by memory.
DenseMap<APInt, IntrusiveRefCntPtr<Provenance>> TaggedProvenances;
- // TODO: Maintains a global list of 'exposed' provenances. This is used to
- // convert an address back to a pointer with a previously exposed provenance.
+ // Maintains a global list of 'exposed' provenances. This is used to convert
+ // an address back to a pointer with a previously exposed provenance. In
+ // theory the provenance is picked from all previously exposed provenances
+ // using angelic non-determinism. Since llubi is just an interpreter, we make
+ // two approximations:
+ // 1. Each address maps to at most one memory object during the execution of
+ // the program, as AllocationBase increases monotonically.
+ // 2. We maintain the set of exposed provenances. When ptrtoint executes,
+ // the provenance is inserted to the set. When inttoptr executes, it yields
+ // a pointer with a wildcard provenance. That is, each later use will check
+ // whether there is an exposed provenance in the snapshot allowing the
+ // operation. The invalid provenance will be masked out after the operation.
+ // If we cannot pick one, it is UB.
+
+ /// Exposed provenances are grouped by associated memory objects for efficient
+ /// invalidation.
+ struct ExposedProvenance {
+ IntrusiveRefCntPtr<Provenance> Prov;
+ uint64_t Generation;
+
+ bool operator<(const ExposedProvenance &RHS) const {
+ return Generation < RHS.Generation;
+ }
+ };
+ struct ExposedProvenanceSet {
+ // (Provenance, Generation)
+ SmallVector<ExposedProvenance> List;
+ // FIXME: Implement a partial order comparator for provenance instead of
+ // deduplicating by pointers.
+ SmallPtrSet<Provenance *, 4> Set;
+ };
+ std::map<uint64_t, ExposedProvenanceSet> ExposedProvenances;
+ // Global version number for the set of exposed provenances.
+ uint64_t ExposedProvenanceSetGeneration = 0;
/// Get the tag for the given pointer provenance.
APInt getTag(uint32_t BitWidth, Provenance &Prov);
@@ -329,6 +361,22 @@ class Context {
/// Derive a pointer from a memory object with offset 0.
/// Please use Pointer's interface for further manipulations.
Pointer deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj);
+ /// Mark this provenance as exposed. It is no-op if it is not associated with
+ /// a memory object or a wildcard provenance.
+ void exposeProvenance(Provenance &Prov);
+ /// A helper to check both concrete and wildcard provenance. Please don't
+ /// report UB inside the \p Check callback due to the existence of wildcard
+ /// provenance.
+ /// Returns the resolved memory object if success. \p Ptr is guaranteed to be
+ /// within the bounds of the returned memory object. But the state is not
+ /// checked, for better diagnostic messages. If \p HasSideEffect is true, some
+ /// invalid provenances will be masked out. Note that in this case the caller
+ /// must report UB when the result is nullptr.
+ MemoryObject *checkProvenance(const Pointer &Ptr,
+ function_ref<bool(const Provenance &)> Check,
+ bool HasSideEffect = true);
+ /// Returns the snapshot of currently exposed provenances.
+ IntrusiveRefCntPtr<Provenance> getWildcardProvenance();
/// Convert byte sequence to a value of the given type. Uninitialized bits are
/// flushed according to the options.
/// If \p ContainsUndefinedBits is provided, it will be set to true when there
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 1433526f18d3a..482930af81b5b 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -53,51 +53,60 @@ void ExecutorBase::reportErrorString(StringRef Msg) {
Handler.onError(Msg);
}
-std::optional<uint64_t> ExecutorBase::verifyMemAccess(const MemoryObject &MO,
- const APInt &Address,
- uint64_t AccessSize,
- Align Alignment,
- bool IsStore) {
+std::pair<MemoryObject *, uint64_t>
+ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
+ Align Alignment, bool IsStore) {
+ auto *MO = Ctx.checkProvenance(Ptr, [](const Provenance &) {
+ // TODO: check provenance
+ // TODO: check inrange(S, E)
+ return true;
+ });
+ if (!MO) {
+ reportImmediateUB()
+ << "Invalid memory access via a pointer with nullary provenance.";
+ return {};
+ }
+ const APInt &Address = Ptr.address();
// Loading from a stack object outside its lifetime is not undefined
// behavior and returns a poison value instead. Storing to it is still
// undefined behavior.
- if (IsStore ? MO.getState() != MemoryObjectState::Alive
- : MO.getState() == MemoryObjectState::Freed) {
+ if (IsStore ? MO->getState() != MemoryObjectState::Alive
+ : MO->getState() == MemoryObjectState::Freed) {
reportImmediateUB() << "Try to access a dead memory object at address 0x"
<< Twine::utohexstr(Address.getZExtValue()) << ".";
- return std::nullopt;
+ return {};
}
if (Address.countr_zero() < Log2(Alignment)) {
reportImmediateUB() << "Misaligned memory access. Address: 0x"
<< Twine::utohexstr(Address.getZExtValue())
<< ", Required alignment: " << Alignment.value() << ".";
- return std::nullopt;
+ return {};
}
- if (AccessSize > MO.getSize() || Address.ult(MO.getAddress())) {
+ if (AccessSize > MO->getSize() || Address.ult(MO->getAddress())) {
reportImmediateUB() << "Memory access is out of bounds. Accessed size: "
<< AccessSize << ", Address: 0x"
<< Twine::utohexstr(Address.getZExtValue())
<< ", Object base: 0x"
- << Twine::utohexstr(MO.getAddress())
- << ", Object size: " << MO.getSize() << ".";
- return std::nullopt;
+ << Twine::utohexstr(MO->getAddress())
+ << ", Object size: " << MO->getSize() << ".";
+ return {};
}
- APInt Offset = Address - MO.getAddress();
+ APInt Offset = Address - MO->getAddress();
- if (Offset.ugt(MO.getSize() - AccessSize)) {
+ if (Offset.ugt(MO->getSize() - AccessSize)) {
reportImmediateUB() << "Memory access is out of bounds. Accessed size: "
<< AccessSize << ", Address: 0x"
<< Twine::utohexstr(Address.getZExtValue())
<< ", Object base: 0x"
- << Twine::utohexstr(MO.getAddress())
- << ", Object size: " << MO.getSize() << ".";
- return std::nullopt;
+ << Twine::utohexstr(MO->getAddress())
+ << ", Object size: " << MO->getSize() << ".";
+ return {};
}
- return Offset.getZExtValue();
+ return {MO, Offset.getZExtValue()};
}
AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
@@ -107,23 +116,16 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
return AnyValue::getPoisonValue(Ctx, ValTy);
}
auto &PtrVal = Ptr.asPointer();
- auto *MO = PtrVal.getMemoryObject();
- if (!MO) {
- reportImmediateUB()
- << "Invalid memory access via a pointer with nullary provenance.";
- return AnyValue::getPoisonValue(Ctx, ValTy);
- }
- // TODO: pointer capability check
- if (auto Offset =
- verifyMemAccess(*MO, PtrVal.address(),
- Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
- /*IsStore=*/false)) {
+ if (auto [MO, Offset] = verifyMemAccess(
+ 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,
+ AnyValue Res = Ctx.load(*MO, Offset, ValTy,
NoUndef ? &ContainsUndefinedBits : nullptr);
if (NoUndef && ContainsUndefinedBits)
reportImmediateUB() << "The value loaded contains undefined bits.";
@@ -139,23 +141,17 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
return;
}
auto &PtrVal = Ptr.asPointer();
- auto *MO = PtrVal.getMemoryObject();
- if (!MO) {
- reportImmediateUB()
- << "Invalid memory access via a pointer with nullary provenance.";
- return;
- }
- if (MO->isConstant()) {
- reportImmediateUB() << "Try to write to a constant memory object: "
- << PtrVal << ".";
- return;
+ if (auto [MO, Offset] = verifyMemAccess(
+ PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
+ /*IsStore=*/true);
+ MO) {
+ if (MO->isConstant()) {
+ reportImmediateUB() << "Try to write to a constant memory object: "
+ << PtrVal << ".";
+ return;
+ }
+ Ctx.store(*MO, Offset, Val, ValTy);
}
- // TODO: pointer capability check
- if (auto Offset =
- verifyMemAccess(*MO, PtrVal.address(),
- Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
- /*IsStore=*/true))
- Ctx.store(*MO, *Offset, Val, ValTy);
}
void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index d07b4dae1792e..d165192b55d63 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -99,12 +99,12 @@ class ExecutorBase {
DiagnosticReporter reportImmediateUB();
DiagnosticReporter reportError();
- /// Check if the upcoming memory access is valid. Returns the offset relative
- /// to the underlying object if it is valid.
- std::optional<uint64_t> verifyMemAccess(const MemoryObject &MO,
- const APInt &Address,
- uint64_t AccessSize, Align Alignment,
- bool IsStore);
+ /// Check if the upcoming memory access is valid. Returns the resolved memory
+ /// object and offset if it is valid.
+ std::pair<MemoryObject *, uint64_t> verifyMemAccess(const Pointer &Ptr,
+ uint64_t AccessSize,
+ Align Alignment,
+ bool IsStore);
AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
bool NoUndef);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 90d1966422a4c..341bf8fd7fca9 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -120,23 +120,25 @@ static bool violatesNoUndefAttr(AnyValue &V) {
/// Assumes V is either a poison or a pointer.
static bool violatesDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
bool OrNull, unsigned AS,
- const DataLayout &DL) {
+ Context &Ctx) {
if (V.isPoison())
return true;
auto &Ptr = V.asPointer();
- if (Ptr.isNullPtr(AS, DL)) {
+ if (Ptr.isNullPtr(AS, Ctx.getDataLayout())) {
if (OrNull)
return false;
return true;
}
- auto *MO = Ptr.getMemoryObject();
+
+ auto *MO = Ctx.checkProvenance(Ptr, [&](const Provenance &) {
+ // TODO: check read_provenance
+ // TODO: check nofree for attributes/metadata.
+ return true;
+ });
if (!MO)
return true;
- // TODO: check read_provenance
- // TODO: check nofree for attributes/metadata.
-
const APInt &PtrAddr = Ptr.address();
return Bytes > MO->getSize() || PtrAddr.ult(MO->getAddress()) ||
PtrAddr.ugt(MO->getAddress() + MO->getSize() - Bytes);
@@ -732,9 +734,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
APInt NewAddr = Ptr.address();
NewAddr.insertBits(NewIndex.asInteger(), 0);
- auto *MO = Ptr.getMemoryObject();
- if (Flags.isInBounds() && (!MO || !MO->inBounds(NewAddr)))
- return AnyValue::poison();
+ MemoryObject *MO = nullptr;
+ if (Flags.isInBounds()) {
+ MO = Ctx.checkProvenance(
+ Ptr, [](const Provenance &) { return true; },
+ /*HasSideEffect=*/false);
+ if (!MO || !MO->inBounds(NewAddr))
+ return AnyValue::poison();
+ }
if (!AccumulatedOffset.isPoison()) {
AccumulatedOffset =
@@ -746,7 +753,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
// Should not expose provenance here even if the new address doesn't point
// to the original object.
- return Ptr.getWithNewAddr(NewAddr);
+ auto Res = Ptr.getWithNewAddr(NewAddr);
+ if (MO) {
+ auto &Prov = Res.provenance();
+ if (Prov.isWildcard() && !Prov.getMemoryObject())
+ Res = Res.getWithNewProvenance(Prov.getWithKnownMemoryObject(*MO));
+ }
+ return Res;
}
AnyValue computePtrAdd(const AnyValue &Ptr, const APInt &Offset,
@@ -985,7 +998,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
break;
if (violatesDereferenceableBytesAttr(
WasOn, DereferenceableBytes.getLimitedValue(),
- Kind == Attribute::DereferenceableOrNull, AS, DL))
+ Kind == Attribute::DereferenceableOrNull, AS, Ctx))
reportImmediateUB() << "The pointer " << WasOn << " violates "
<< (Kind == Attribute::DereferenceableOrNull
? "dereferenceable_or_null("
@@ -1010,7 +1023,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
auto Ptr = Args[0];
if (Ptr.isPoison())
return AnyValue();
- auto *MO = Ptr.asPointer().getMemoryObject();
+ auto *MO = Ctx.checkProvenance(Ptr.asPointer(),
+ [](const Provenance &) { return true; });
assert(MO && "Memory object accessed by lifetime intrinsic should be "
"always valid.");
if (IID == Intrinsic::lifetime_start) {
@@ -1693,7 +1707,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
std::max(AttrsAtCallSite.getDereferenceableBytes(),
AttrsAtCallee.getDereferenceableBytes())) {
if (violatesDereferenceableBytesAttr(V, DereferenceableBytes,
- /*OrNull=*/false, AS, DL))
+ /*OrNull=*/false, AS, Ctx))
reportImmediateUB()
<< "The value " << V << " violates dereferenceable("
<< DereferenceableBytes << ") attribute.";
@@ -1701,7 +1715,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
std::max(AttrsAtCallSite.getDereferenceableOrNullBytes(),
AttrsAtCallee.getDereferenceableOrNullBytes())) {
if (violatesDereferenceableBytesAttr(V, DereferenceableOrNullBytes,
- /*OrNull=*/true, AS, DL))
+ /*OrNull=*/true, AS, Ctx))
reportImmediateUB() << "The value " << V
<< " violates "
"dereferenceable_or_null("
@@ -1759,7 +1773,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
I.getMetadata(LLVMContext::MD_dereferenceable)) {
uint64_t Bytes = ExtractFirstIntOperand(DereferenceableBytes);
if (violatesDereferenceableBytesAttr(V, Bytes,
- /*OrNull=*/false, AS, DL))
+ /*OrNull=*/false, AS, Ctx))
reportImmediateUB()
<< "The value " << V << " violates !dereferenceable !{i64 "
<< Bytes << "} metadata.";
@@ -1767,7 +1781,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
I.getMetadata(LLVMContext::MD_dereferenceable_or_null)) {
uint64_t Bytes = ExtractFirstIntOperand(DereferenceableOrNullBytes);
if (violatesDereferenceableBytesAttr(V, Bytes,
- /*OrNull=*/true, AS, DL))
+ /*OrNull=*/true, AS, Ctx))
reportImmediateUB()
<< "The value " << V << " violates !dereferenceable_or_null!{i64 "
<< Bytes << "} metadata.";
@@ -2419,14 +2433,26 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
setResult(GEP, std::move(Res));
}
+ void visitPtrToInt(PtrToIntInst &I) {
+ return visitUnOp(I, [&](const AnyValue &V) -> AnyValue {
+ if (V.isPoison())
+ return AnyValue::poison();
+ Ctx.exposeProvenance(V.asPointer().provenance());
+ return V.asPointer().address();
+ });
+ }
+
+ // TODO: Add support for ptrtoaddr, which exposes nothing.
+
void visitIntToPtr(IntToPtrInst &I) {
return visitUnOp(I, [&](const AnyValue &V) -> AnyValue {
if (V.isPoison())
return AnyValue::poison();
- // TODO: expose provenance
+ auto Prov = Ctx.getWildcardProvenance();
// TODO: check metadata
- return Pointer(V.asInteger().zextOrTrunc(
- DL.getPointerSizeInBits(I.getType()->getPointerAddressSpace())));
+ return Pointer(std::move(Prov),
+ V.asInteger().zextOrTrunc(DL.getPointerSizeInBits(
+ I.getType()->getPointerAddressSpace())));
});
}
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 3eab215f933d1..c3452b8af92c0 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -29,24 +29,17 @@ Library::Library(Context &Ctx, EventHandler &Handler, const DataLayout &DL,
: Ctx(Ctx), Handler(Handler), DL(DL), Executor(Executor) {}
std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
- auto *MO = Ptr.getMemoryObject();
- if (!MO) {
- Executor.reportImmediateUB()
- << "Invalid memory access via a pointer with nullary provenance.";
- return std::nullopt;
- }
-
std::string Result;
const APInt &Address = Ptr.address();
uint64_t Offset = 0;
while (true) {
- auto ValidOffset =
- Executor.verifyMemAccess(*MO, Address + Offset, 1, Align(1), false);
- if (!ValidOffset)
+ auto [MO, ValidOffset] = Executor.verifyMemAccess(
+ Ptr.getWithNewAddr(Address + Offset), 1, Align(1), /*IsStore=*/false);
+ if (!MO)
return std::nullopt;
- Byte B = (*MO)[*ValidOffset];
+ Byte B = (*MO)[ValidOffset];
if (B.ConcreteMask != 0xFF) {
Executor.reportImmediateUB()
<< "Read uninitialized or poison memory while "
@@ -128,7 +121,10 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
if (Ptr.isNullPtr(/*AS=*/0, DL))
return AnyValue();
- MemoryObject *Obj = Ptr.getMemoryObject();
+ MemoryObject *Obj = Ctx.checkProvenance(Ptr, [](const Provenance &) {
+ // TODO: check nofree
+ return true;
+ });
if (!Obj) {
Executor.reportImmediateUB()
<< "freeing a pointer with nullary provenance.";
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 04a1aa803f66c..3f1c2567cc51d 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -22,11 +22,20 @@ IntrusiveRefCntPtr<Provenance> Provenance::nullary() {
return Instance;
}
+IntrusiveRefCntPtr<Provenance>
+Provenance::getWithKnownMemoryObject(MemoryObject &KnownObj) {
+ assert(!Obj && Wildcard && "The memory object has been determined.");
+ auto Res = makeIntrusiveRefCnt<Provenance>(*this);
+ Res->Obj = &KnownObj;
+ Res->Tag = APInt();
+ return Res;
+}
+
void Pointer::print(raw_ostream &OS) const {
SmallString<32> AddrStr;
Address.toStringUnsigned(AddrStr, 16);
OS << "ptr 0x" << AddrStr << " [";
- if (MemoryObject *Obj = getMemoryObject()) {
+ if (MemoryObject *Obj = Prov->getMemoryObject()) {
if (Obj->isIRGlobalValue())
OS << "@";
OS << Obj->getName();
@@ -36,8 +45,9 @@ void Pointer::print(raw_ostream &OS) const {
if (State != MemoryObjectState::Alive)
OS << (State == MemoryObjectState::Dead ? " (dead)" : " (dangling)");
} else {
- OS << "nullary";
+ OS << (Prov->isWildcard() ? "wildcard" : "nullary");
}
+ // TODO: print provenance
OS << "]";
}
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index e88e2da04dcc5..b5c5078103e18 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -114,15 +114,56 @@ enum class StorageKind {
/// Tri-state boolean value.
enum class BooleanKind { False, True, Poison };
+/// A set of previously exposed provenances. It is originally yielded by
+/// inttoptr, and shared by pointers derived from the result.
+///
+/// Each capability check may invalidate some provenances. If we cannot
+/// pick one, it is UB. That is, from the angelic non-determinism view,
+/// we cannot pick a provenance to make the program reach this point.
+///
+/// For efficiency, this class has
diff erent forms in two stages:
+/// 1. Before any memory access is performed, ActiveMask is set to zero and
+/// Generation represents the global generation number of the snapshot.
+/// 2. After a memory access is performed, we can determine exactly one memory
+/// object to be accessed (address ranges are distinct). In this case,
+/// BaseAddress is set and ActiveMask is non-zero. ActiveMask represents the
+/// validity of the first N exposed provenances associated with the memory
+/// object. The bitwidth N is the number of provenances in the list with
+/// List[I].Generation <= WildcardProvenance::Generation (The generation field
+/// in the list is monotonically increasing). That is, we can only access
+/// through exposed provenances before inttoptr executes. Note that if
+/// ActiveMask becomes zero again, UB must be triggered.
+class WildcardProvenance : public RefCountedBase<WildcardProvenance> {
+ APInt ActiveMask;
+ union {
+ uint64_t Generation;
+ uint64_t BaseAddress;
+ };
+
+ friend class Context;
+
+public:
+ explicit WildcardProvenance(uint64_t Generation)
+ : ActiveMask(), Generation(Generation) {}
+};
+
/// Components of a pointer excluding address. They are shared between pointer
/// values, as most of operations don't change the provenance.
/// Each node will be assigned a unique, pointer-sized tag, which is used to
/// represent the pointer in the memory.
+/// The provenance can be either concrete or wildcard, as determined by the
+/// cases below:
+/// Obj Wildcard State
+/// Null Null Invalid
+/// Null NonNull Wildcard
+/// NonNull Null Concrete
+/// NonNull NonNull Wildcard (associated with a specific MO)
class Provenance : public RefCountedBase<Provenance> {
// TODO: store reference to the provenance of the pointer it is derived from
// The underlying memory object. It can be null for invalid or dangling
- // pointers.
+ // pointers. Besides, for pointers with wildcard provenance, it can be null
+ // until the memory object is resolved by gep inbounds.
IntrusiveRefCntPtr<MemoryObject> Obj;
// A tag is a randomly generated unique identifier to recover the provenance
@@ -130,10 +171,11 @@ class Provenance : public RefCountedBase<Provenance> {
// type, in bits. It may produce false negatives in some corner cases. But in
// real practice the false negative rate should be negligible.
// A zero tag is invalid.
- // TODO: we need a special tag for wildcard provenance, which is introduced by
- // inttoptr.
APInt Tag;
+ // Null if it is concrete.
+ IntrusiveRefCntPtr<WildcardProvenance> Wildcard;
+
// TODO: modeling nofree
// TODO: modeling captures
// TODO: modeling inrange(Start, End) attribute
@@ -146,7 +188,9 @@ class Provenance : public RefCountedBase<Provenance> {
public:
Provenance(IntrusiveRefCntPtr<MemoryObject> Obj) : Obj(std::move(Obj)) {}
static IntrusiveRefCntPtr<Provenance> nullary();
+ IntrusiveRefCntPtr<Provenance> getWithKnownMemoryObject(MemoryObject &Obj);
MemoryObject *getMemoryObject() const { return Obj.get(); }
+ bool isWildcard() const { return Wildcard != nullptr; }
};
class Pointer {
@@ -166,12 +210,14 @@ class Pointer {
Pointer getWithNewAddr(const APInt &NewAddr) const {
return Pointer(Prov, NewAddr);
}
+ Pointer getWithNewProvenance(IntrusiveRefCntPtr<Provenance> NewProv) const {
+ return Pointer(NewProv, Address);
+ }
static AnyValue null(unsigned AS, const DataLayout &DL);
bool isNullPtr(unsigned AS, const DataLayout &DL) const;
void print(raw_ostream &OS) const;
const APInt &address() const { return Address; }
Provenance &provenance() const { return *Prov; }
- MemoryObject *getMemoryObject() const { return Prov->getMemoryObject(); }
};
// Value representation for actual values of LLVM values.
More information about the llvm-commits
mailing list