[llvm] [llubi] Add basic support for provenance modeling (PR #185977)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Mon May 4 08:17:48 PDT 2026
https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/185977
>From 9e98898c1e54788dddbec368661e87a6d3c242a1 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 12 Mar 2026 04:00:05 +0800
Subject: [PATCH 1/4] [llubi] Add basic support for provenance modeling
---
llvm/test/tools/llubi/alloca.ll | 2 +-
llvm/test/tools/llubi/bitcast_be.ll | 3 +-
llvm/test/tools/llubi/bitcast_le.ll | 3 +-
llvm/test/tools/llubi/freeze.ll | 6 +-
llvm/test/tools/llubi/gep.ll | 28 +++----
llvm/test/tools/llubi/inttoptr.ll | 6 +-
llvm/test/tools/llubi/loadstore_be.ll | 18 ++++-
llvm/test/tools/llubi/loadstore_le.ll | 17 +++-
llvm/test/tools/llubi/loadstore_uaf.ll | 2 +-
llvm/test/tools/llubi/store_dead.ll | 2 +-
llvm/tools/llubi/lib/Context.cpp | 107 ++++++++++++++++++++-----
llvm/tools/llubi/lib/Context.h | 33 ++++++--
llvm/tools/llubi/lib/Value.cpp | 8 +-
llvm/tools/llubi/lib/Value.h | 35 +++++---
14 files changed, 199 insertions(+), 71 deletions(-)
diff --git a/llvm/test/tools/llubi/alloca.ll b/llvm/test/tools/llubi/alloca.ll
index 51f50dff67fc0..e45d499d3205b 100644
--- a/llvm/test/tools/llubi/alloca.ll
+++ b/llvm/test/tools/llubi/alloca.ll
@@ -36,6 +36,6 @@ define void @main() {
; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x70 [alloc]
; CHECK-NEXT: ret ptr %alloc
; CHECK-NEXT: Exiting function: stack_address
-; CHECK-NEXT: %ptr = call ptr @stack_address() => ptr 0x70 [dangling]
+; CHECK-NEXT: %ptr = call ptr @stack_address() => ptr 0x70 [alloc (dangling)]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/bitcast_be.ll b/llvm/test/tools/llubi/bitcast_be.ll
index 8e86f3f325251..5bd2f73df5617 100644
--- a/llvm/test/tools/llubi/bitcast_be.ll
+++ b/llvm/test/tools/llubi/bitcast_be.ll
@@ -13,7 +13,6 @@ entry:
%bitcast_float2int = bitcast float 2.0 to i32
%bitcast_half2bf16 = bitcast half 1.0 to bfloat
%ptr = alloca i32
- ; FIXME: The provenance is lost.
%bitcast_ptr2ptr = bitcast ptr %ptr to ptr
%bitcast_vec2scalar1 = bitcast <2 x i32> <i32 0, i32 1> to i64
@@ -42,7 +41,7 @@ entry:
; CHECK-NEXT: %bitcast_float2int = bitcast float 2.000000e+00 to i32 => i32 1073741824
; CHECK-NEXT: %bitcast_half2bf16 = bitcast half 0xH3C00 to bfloat => bfloat 7.812500e-03
; CHECK-NEXT: %ptr = alloca i32, align 4 => ptr 0x8 [ptr]
-; CHECK-NEXT: %bitcast_ptr2ptr = bitcast ptr %ptr to ptr => ptr 0x8 [dangling]
+; CHECK-NEXT: %bitcast_ptr2ptr = bitcast ptr %ptr to ptr => ptr 0x8 [ptr]
; CHECK-NEXT: %bitcast_vec2scalar1 = bitcast <2 x i32> <i32 0, i32 1> to i64 => i64 1
; CHECK-NEXT: %bitcast_vec2scalar2 = bitcast <4 x i4> <i4 1, i4 2, i4 3, i4 5> to i16 => i16 4661
; CHECK-NEXT: %bitcast_scalar2vec1 = bitcast i64 1 to <2 x i32> => { i32 0, i32 1 }
diff --git a/llvm/test/tools/llubi/bitcast_le.ll b/llvm/test/tools/llubi/bitcast_le.ll
index 773e10feaac99..a0c4b84f7d804 100644
--- a/llvm/test/tools/llubi/bitcast_le.ll
+++ b/llvm/test/tools/llubi/bitcast_le.ll
@@ -13,7 +13,6 @@ entry:
%bitcast_float2int = bitcast float 2.0 to i32
%bitcast_half2bf16 = bitcast half 1.0 to bfloat
%ptr = alloca i32
- ; FIXME: The provenance is lost.
%bitcast_ptr2ptr = bitcast ptr %ptr to ptr
%bitcast_vec2scalar1 = bitcast <2 x i32> <i32 0, i32 1> to i64
@@ -42,7 +41,7 @@ entry:
; CHECK-NEXT: %bitcast_float2int = bitcast float 2.000000e+00 to i32 => i32 1073741824
; CHECK-NEXT: %bitcast_half2bf16 = bitcast half 0xH3C00 to bfloat => bfloat 7.812500e-03
; CHECK-NEXT: %ptr = alloca i32, align 4 => ptr 0x8 [ptr]
-; CHECK-NEXT: %bitcast_ptr2ptr = bitcast ptr %ptr to ptr => ptr 0x8 [dangling]
+; CHECK-NEXT: %bitcast_ptr2ptr = bitcast ptr %ptr to ptr => ptr 0x8 [ptr]
; CHECK-NEXT: %bitcast_vec2scalar1 = bitcast <2 x i32> <i32 0, i32 1> to i64 => i64 4294967296
; CHECK-NEXT: %bitcast_vec2scalar2 = bitcast <4 x i4> <i4 1, i4 2, i4 3, i4 5> to i16 => i16 21281
; CHECK-NEXT: %bitcast_scalar2vec1 = bitcast i64 1 to <2 x i32> => { i32 1, i32 0 }
diff --git a/llvm/test/tools/llubi/freeze.ll b/llvm/test/tools/llubi/freeze.ll
index cc79a8d897f4c..c63db99b08084 100644
--- a/llvm/test/tools/llubi/freeze.ll
+++ b/llvm/test/tools/llubi/freeze.ll
@@ -24,11 +24,11 @@ define void @main() {
; CHECK-NEXT: %int_freeze_poison2 = freeze i32 poison => i32 1044445579
; CHECK-NEXT: %float_freeze = freeze float 1.000000e+00 => float 1.000000e+00
; CHECK-NEXT: %float_freeze_poison = freeze float poison => float 0xF032E8B9
-; CHECK-NEXT: %ptr_freeze = freeze ptr null => ptr 0x0 [dangling]
-; CHECK-NEXT: %ptr_freeze_poison = freeze ptr poison => ptr 0x98F56903CEE3FCEE [dangling]
+; CHECK-NEXT: %ptr_freeze = freeze ptr null => ptr 0x0 [nullary]
+; CHECK-NEXT: %ptr_freeze_poison = freeze ptr poison => ptr 0x98F56903CEE3FCEE [nullary]
; CHECK-NEXT: %vec_freeze = freeze <2 x i32> <i32 10, i32 poison> => { i32 10, i32 990324140 }
; CHECK-NEXT: %arr_freeze = freeze [2 x i32] [i32 10, i32 poison] => { i32 10, i32 -44045842 }
; CHECK-NEXT: %struct_freeze = freeze { i32, float } { i32 10, float poison } => { i32 10, float 0x042AE12F }
-; CHECK-NEXT: %struct_freeze_nested = freeze { i32, { float, ptr } } { i32 poison, { float, ptr } { float 1.000000e+01, ptr poison } } => { i32 1166165736, { float 1.000000e+01, ptr 0xD79E62976F604366 [dangling] } }
+; CHECK-NEXT: %struct_freeze_nested = freeze { i32, { float, ptr } } { i32 poison, { float, ptr } { float 1.000000e+01, ptr poison } } => { i32 1166165736, { float 1.000000e+01, ptr 0xD79E62976F604366 [nullary] } }
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/gep.ll b/llvm/test/tools/llubi/gep.ll
index 4376569fa1811..3426249fa138c 100644
--- a/llvm/test/tools/llubi/gep.ll
+++ b/llvm/test/tools/llubi/gep.ll
@@ -100,16 +100,16 @@ define void @main() {
; CHECK-NEXT: %gep_poison_idx = getelementptr i8, ptr %alloc, i64 poison => poison
; CHECK-NEXT: %gep_trunc_idx = getelementptr i32, ptr %alloc, i128 18446744073709551616 => ptr 0x8 [alloc]
; CHECK-NEXT: %gep_sext_idx = getelementptr i32, ptr %alloc, i8 -1 => ptr 0x4 [alloc + -4]
-; CHECK-NEXT: %large_address = inttoptr i64 -1 to ptr => ptr 0xFFFFFFFFFFFFFFFF [dangling]
-; CHECK-NEXT: %gep_update_idx_bits = getelementptr i32, ptr %large_address, i64 1 => ptr 0xFFFFFFFF00000003 [dangling]
+; CHECK-NEXT: %large_address = inttoptr i64 -1 to ptr => ptr 0xFFFFFFFFFFFFFFFF [nullary]
+; CHECK-NEXT: %gep_update_idx_bits = getelementptr i32, ptr %large_address, i64 1 => ptr 0xFFFFFFFF00000003 [nullary]
; CHECK-NEXT: %gep_struct = getelementptr %struct, ptr %alloc_struct, i64 0, i32 1, i32 1 => ptr 0x1C [alloc_struct + 12]
-; CHECK-NEXT: %gep_scalable_vec = getelementptr <vscale x 4 x i32>, ptr null, i64 4 => ptr 0x100 [dangling]
+; CHECK-NEXT: %gep_scalable_vec = getelementptr <vscale x 4 x i32>, ptr null, i64 4 => ptr 0x100 [nullary]
; CHECK-NEXT: %gep_vec_idx = getelementptr [2 x i32], ptr %alloc, i64 1, <2 x i64> <i64 0, i64 2> => { ptr 0x10 [alloc + 8], ptr 0x18 [alloc + 16] }
; CHECK-NEXT: %ptr_vec_insert = insertelement <2 x ptr> poison, ptr %alloc, i32 0 => { ptr 0x8 [alloc], poison }
; CHECK-NEXT: %ptr_vec_splat = shufflevector <2 x ptr> %ptr_vec_insert, <2 x ptr> poison, <2 x i32> zeroinitializer => { ptr 0x8 [alloc], ptr 0x8 [alloc] }
; CHECK-NEXT: %gep_vec_ptr = getelementptr i32, <2 x ptr> %ptr_vec_splat, i64 1 => { ptr 0xC [alloc + 4], ptr 0xC [alloc + 4] }
; CHECK-NEXT: %gep_vec_ptr_vec_idx = getelementptr i32, <2 x ptr> %ptr_vec_splat, <2 x i64> <i64 0, i64 2> => { ptr 0x8 [alloc], ptr 0x10 [alloc + 8] }
-; CHECK-NEXT: %gep_inbounds_valid_null = getelementptr inbounds i8, ptr null, i64 0 => ptr 0x0 [dangling]
+; CHECK-NEXT: %gep_inbounds_valid_null = getelementptr inbounds i8, ptr null, i64 0 => ptr 0x0 [nullary]
; CHECK-NEXT: %gep_inbounds_invalid_null = getelementptr inbounds i8, ptr null, i64 1 => poison
; CHECK-NEXT: %gep_inbounds_valid1 = getelementptr inbounds i8, ptr %alloc, i64 3 => ptr 0xB [alloc + 3]
; CHECK-NEXT: %gep_inbounds_valid2 = getelementptr inbounds i8, ptr %alloc, i64 4 => ptr 0xC [alloc + 4]
@@ -120,25 +120,25 @@ define void @main() {
; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x28 [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 [dangling]
-; CHECK-NEXT: %gep_inbounds_valid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 4 => ptr 0x2C [dangling]
+; 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: %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
-; CHECK-NEXT: %gep_nusw_valid2 = getelementptr nusw i32, ptr null, i32 536870911 => ptr 0x7FFFFFFC [dangling]
+; CHECK-NEXT: %gep_nusw_valid2 = getelementptr nusw i32, ptr null, i32 536870911 => ptr 0x7FFFFFFC [nullary]
; CHECK-NEXT: %gep_nusw_invalid2 = getelementptr nusw i32, ptr null, i32 536870912 => poison
-; CHECK-NEXT: %gep_nusw_valid3 = getelementptr nusw [2 x i16], ptr null, i32 536870911, i32 1 => ptr 0x7FFFFFFE [dangling]
+; CHECK-NEXT: %gep_nusw_valid3 = getelementptr nusw [2 x i16], ptr null, i32 536870911, i32 1 => ptr 0x7FFFFFFE [nullary]
; CHECK-NEXT: %gep_nusw_invalid3 = getelementptr nusw [2 x i16], ptr null, i32 536870911, i32 2 => poison
-; CHECK-NEXT: %large_address2 = inttoptr i64 -4 to ptr => ptr 0xFFFFFFFFFFFFFFFC [dangling]
-; CHECK-NEXT: %gep_nusw_valid4 = getelementptr nusw i8, ptr %large_address2, i64 3 => ptr 0xFFFFFFFFFFFFFFFF [dangling]
+; CHECK-NEXT: %large_address2 = inttoptr i64 -4 to ptr => ptr 0xFFFFFFFFFFFFFFFC [nullary]
+; CHECK-NEXT: %gep_nusw_valid4 = getelementptr nusw i8, ptr %large_address2, i64 3 => ptr 0xFFFFFFFFFFFFFFFF [nullary]
; CHECK-NEXT: %gep_nusw_invalid4 = getelementptr nusw i8, ptr %large_address2, i64 4 => poison
-; CHECK-NEXT: %gep_nusw_valid5 = getelementptr nusw i8, ptr %large_address2, i64 -4 => ptr 0xFFFFFFFFFFFFFFF8 [dangling]
+; CHECK-NEXT: %gep_nusw_valid5 = getelementptr nusw i8, ptr %large_address2, i64 -4 => ptr 0xFFFFFFFFFFFFFFF8 [nullary]
; CHECK-NEXT: %gep_nusw_invalid5 = getelementptr nusw i8, ptr %alloc, i64 -16 => poison
-; CHECK-NEXT: %gep_nuw_valid1 = getelementptr nuw i8, ptr null, i64 2147483647 => ptr 0x7FFFFFFF [dangling]
+; CHECK-NEXT: %gep_nuw_valid1 = getelementptr nuw i8, ptr null, i64 2147483647 => ptr 0x7FFFFFFF [nullary]
; CHECK-NEXT: %gep_nuw_invalid1 = getelementptr nuw i8, ptr null, i64 -1 => poison
-; CHECK-NEXT: %gep_nuw_valid2 = getelementptr nuw i32, ptr null, i32 1073741823 => ptr 0xFFFFFFFC [dangling]
+; CHECK-NEXT: %gep_nuw_valid2 = getelementptr nuw i32, ptr null, i32 1073741823 => ptr 0xFFFFFFFC [nullary]
; CHECK-NEXT: %gep_nuw_invalid2 = getelementptr nuw i32, ptr null, i32 1073741824 => poison
-; CHECK-NEXT: %gep_nuw_valid3 = getelementptr nuw [2 x i16], ptr null, i32 1073741823, i32 1 => ptr 0xFFFFFFFE [dangling]
+; CHECK-NEXT: %gep_nuw_valid3 = getelementptr nuw [2 x i16], ptr null, i32 1073741823, i32 1 => ptr 0xFFFFFFFE [nullary]
; CHECK-NEXT: %gep_nuw_invalid3 = getelementptr nuw [2 x i16], ptr null, i32 1073741823, i32 2 => poison
; CHECK-NEXT: %gep_nuw_valid4 = getelementptr nuw i32, ptr %alloc, i64 1073741821 => ptr 0xFFFFFFFC [alloc + 4294967284]
; CHECK-NEXT: %gep_nuw_invalid4 = getelementptr nuw i32, ptr %alloc, i64 1073741822 => poison
diff --git a/llvm/test/tools/llubi/inttoptr.ll b/llvm/test/tools/llubi/inttoptr.ll
index 4e672c38fb6fd..d29d26f69e2ad 100644
--- a/llvm/test/tools/llubi/inttoptr.ll
+++ b/llvm/test/tools/llubi/inttoptr.ll
@@ -10,8 +10,8 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: %ptr1 = inttoptr i64 0 to ptr => ptr 0x0 [dangling]
-; CHECK-NEXT: %ptr2 = inttoptr i8 -1 to ptr => ptr 0xFF [dangling]
-; CHECK-NEXT: %ptr3 = inttoptr i128 -1 to ptr => ptr 0xFFFFFFFFFFFFFFFF [dangling]
+; 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: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 2519a2f692a1a..d394068ffe990 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -110,6 +110,15 @@ define void @main() {
%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 !{}
+
+ %alloc_ptr = alloca ptr
+ store ptr %alloc_ptr, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance = load ptr, ptr %alloc_ptr
+ %addr_bits = load i8, ptr %alloc_ptr
+ store i8 %addr_bits, ptr %alloc_ptr
+ ; The first byte is tainted. We cannot recover the provenance.
+ %ptr_without_provenance = load ptr, ptr %alloc_ptr
ret void
}
@@ -137,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]
+; CHECK-NEXT: %alloc_lifetime = alloca i32, align 4 => ptr 0xC [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
@@ -191,7 +200,10 @@ define void @main() {
; 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: %alloc_ptr = alloca ptr, align 8 => ptr 0x88 [alloc_ptr]
+; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x88 [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 0x88 [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 84c5246d799f6..6f6826d884adc 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -111,6 +111,15 @@ define void @main() {
%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 !{}
+
+ %alloc_ptr = alloca ptr
+ store ptr %alloc_ptr, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance = load ptr, ptr %alloc_ptr
+ %addr_bits = load i8, ptr %alloc_ptr
+ store i8 %addr_bits, ptr %alloc_ptr
+ ; The first byte is tainted. We cannot recover the provenance.
+ %ptr_without_provenance = load ptr, ptr %alloc_ptr
ret void
}
@@ -139,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]
+; CHECK-NEXT: %alloc_lifetime = alloca i32, align 4 => ptr 0xC [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
@@ -195,5 +204,11 @@ define void @main() {
; 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: %alloc_ptr = alloca ptr, align 8 => ptr 0x88 [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 0x88 [alloc_ptr]
+; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -120
+; CHECK-NEXT: store i8 %addr_bits, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x88 [nullary]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_uaf.ll b/llvm/test/tools/llubi/loadstore_uaf.ll
index ab81a75fd2cb7..a4593bff99c79 100644
--- a/llvm/test/tools/llubi/loadstore_uaf.ll
+++ b/llvm/test/tools/llubi/loadstore_uaf.ll
@@ -16,7 +16,7 @@ define void @main() {
; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
; CHECK-NEXT: ret ptr %alloc
; CHECK-NEXT: Exiting function: stack_object
-; CHECK-NEXT: %alloc = call ptr @stack_object() => ptr 0x8 [dangling]
+; CHECK-NEXT: %alloc = call ptr @stack_object() => ptr 0x8 [alloc (dangling)]
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 {{store i32 0|%res = load i32}}, ptr %alloc, align 4 at @main
; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object at address 0x8.
diff --git a/llvm/test/tools/llubi/store_dead.ll b/llvm/test/tools/llubi/store_dead.ll
index 53ad2525369cb..e1151ec8d5b74 100644
--- a/llvm/test/tools/llubi/store_dead.ll
+++ b/llvm/test/tools/llubi/store_dead.ll
@@ -10,7 +10,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc (dead)]
; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %alloc)
; CHECK-NEXT: store i32 0, ptr %alloc, align 4
; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 5b81b068460de..b4a2d077e4cc1 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -110,6 +110,24 @@ const AnyValue &Context::getConstantValue(Constant *C) {
return ConstCache.emplace(C, getConstantValueImpl(C)).first->second;
}
+APInt Context::getTag(uint32_t BitWidth, MemoryObject *Obj) {
+ // Nullary provenance.
+ if (!Obj)
+ return APInt::getZero(BitWidth);
+ // The tag is already initialized.
+ if (!Obj->getTag().isZero())
+ return Obj->getTag();
+
+ // FIXME: This doesn't work when the address space is too small.
+ while (true) {
+ APInt Tag = generateRandomAPInt(BitWidth);
+ if (Tag.isZero() || !CapturedMemoryObjects.try_emplace(Tag, Obj).second)
+ continue;
+ Obj->setTag(Tag);
+ return Tag;
+ }
+}
+
AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
uint32_t OffsetInBits, bool CheckPaddingBits,
bool *ContainsUndefinedBits) {
@@ -119,7 +137,12 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
NewOffsetInBits = alignTo(NewOffsetInBits, 8);
bool NeedsPadding = NewOffsetInBits != OffsetInBits + NumBits;
uint32_t NumBitsToExtract = NewOffsetInBits - OffsetInBits;
- SmallVector<uint64_t> RawBits(alignTo(NumBitsToExtract, 8));
+ uint32_t NumWords = divideCeil(NumBitsToExtract, 8);
+ SmallVector<uint64_t> RawBits(NumWords);
+ bool IsTagValid = Ty->isPointerTy();
+ SmallVector<uint64_t> RawTagBits;
+ if (Ty->isPointerTy())
+ RawTagBits.resize(NumWords);
for (uint32_t I = 0; I < NumBitsToExtract; I += 8) {
// Try to form a 'logical' byte that represents the bits in the range
// [BitsStart, BitsEnd].
@@ -159,6 +182,15 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
(RandomBits & ~LogicalByte.ConcreteMask)) &
Mask;
RawBits[I / 64] |= static_cast<APInt::WordType>(ActualBits) << (I % 64);
+ if (IsTagValid) {
+ if ((LogicalByte.TagMask & LogicalByte.ConcreteMask) == Mask) {
+ uint8_t ActualTagBits = LogicalByte.TagValue & Mask;
+ RawTagBits[I / 64] |= static_cast<APInt::WordType>(ActualTagBits)
+ << (I % 64);
+ } else {
+ IsTagValid = false;
+ }
+ }
}
OffsetInBits = NewOffsetInBits;
@@ -179,7 +211,12 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
if (Ty->isFloatingPointTy())
return APFloat(Ty->getFltSemantics(), Bits);
assert(Ty->isPointerTy() && "Expect a pointer type");
- // TODO: recover provenance
+ // Try to recover provenance from the tag.
+ if (IsTagValid) {
+ // TODO: decode metadata bits from the tag.
+ APInt Tag(NumBitsToExtract, RawTagBits);
+ return Pointer(CapturedMemoryObjects.lookup(Tag), Bits);
+ }
return Pointer(Bits);
}
@@ -259,7 +296,7 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
if (PaddingBits)
NewOffsetInBits = alignTo(NewOffsetInBits, 8);
bool NeedsPadding = NewOffsetInBits != OffsetInBits + NumBits;
- auto WriteBits = [&](const APInt &Bits) {
+ auto WriteBits = [&](const APInt &Bits, const APInt *TagBits) {
for (uint32_t I = 0, E = Bits.getBitWidth(); I < E; I += 8) {
uint32_t NumBitsInByte = std::min(8U, E - I);
uint32_t BitsStart = OffsetInBits + I;
@@ -276,6 +313,21 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
Bytes[BitsEnd / 8].writeBits(
static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
static_cast<uint8_t>(BitsVal >> (8 - (BitsStart % 8))));
+
+ if (TagBits) {
+ uint8_t TagBitsVal = static_cast<uint8_t>(
+ TagBits->extractBitsAsZExtValue(NumBitsInByte, I));
+ Bytes[BitsStart / 8].writeTagBits(
+ static_cast<uint8_t>(((1U << NumBitsInByte) - 1)
+ << (BitsStart % 8)),
+ static_cast<uint8_t>(TagBitsVal << (BitsStart % 8)));
+ // If it is a cross-byte access, write the remaining bits to the next
+ // byte.
+ if (((BitsStart ^ BitsEnd) & ~7) != 0)
+ Bytes[BitsEnd / 8].writeTagBits(
+ static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
+ static_cast<uint8_t>(TagBitsVal >> (8 - (BitsStart % 8))));
+ }
}
};
if (Val.isPoison()) {
@@ -289,14 +341,26 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
}
} else if (Ty->isIntegerTy()) {
auto &Bits = Val.asInteger();
- WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits) : Bits);
+ WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits) : Bits,
+ /*TagBits=*/nullptr);
} else if (Ty->isFloatingPointTy()) {
auto Bits = Val.asFloat().bitcastToAPInt();
- WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits) : Bits);
+ WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits) : Bits,
+ /*TagBits=*/nullptr);
} else if (Ty->isPointerTy()) {
- auto &Bits = Val.asPointer().address();
- WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits) : Bits);
- // TODO: save metadata of the pointer.
+ auto &AddressBits = Val.asPointer().address();
+ if (auto *MO = Val.asPointer().getMemoryObject()) {
+ APInt Tag = getTag(AddressBits.getBitWidth(), MO);
+ if (NeedsPadding)
+ Tag = Tag.zext(NewOffsetInBits - OffsetInBits);
+ WriteBits(NeedsPadding ? AddressBits.zext(NewOffsetInBits - OffsetInBits)
+ : AddressBits,
+ &Tag);
+ } else {
+ WriteBits(NeedsPadding ? AddressBits.zext(NewOffsetInBits - OffsetInBits)
+ : AddressBits,
+ /*TagBits=*/nullptr);
+ }
} else {
llvm_unreachable("Unsupported scalar type.");
}
@@ -392,21 +456,24 @@ void Context::storeRawBytes(MemoryObject &MO, uint64_t Offset, const void *Data,
MO[Offset + I] = Byte::concrete(static_cast<const uint8_t *>(Data)[I]);
}
+APInt Context::generateRandomAPInt(uint32_t BitWidth) {
+ SmallVector<APInt::WordType> RandomWords;
+ uint32_t NumWords = APInt::getNumWords(BitWidth);
+ RandomWords.reserve(NumWords);
+ static_assert(decltype(Rng)::word_size >=
+ std::numeric_limits<APInt::WordType>::digits,
+ "Unexpected Rng result type.");
+ for (uint32_t I = 0; I != NumWords; ++I)
+ RandomWords.push_back(static_cast<APInt::WordType>(Rng()));
+ return APInt(BitWidth, RandomWords);
+}
+
void Context::freeze(AnyValue &Val, Type *Ty) {
if (Val.isPoison()) {
uint32_t Bits = DL.getTypeSizeInBits(Ty);
- APInt RandomVal = APInt::getZero(Bits);
- if (UndefBehavior == UndefValueBehavior::NonDeterministic) {
- SmallVector<APInt::WordType> RandomWords;
- uint32_t NumWords = APInt::getNumWords(Bits);
- RandomWords.reserve(NumWords);
- static_assert(decltype(Rng)::word_size >=
- std::numeric_limits<APInt::WordType>::digits,
- "Unexpected Rng result type.");
- for (uint32_t I = 0; I != NumWords; ++I)
- RandomWords.push_back(static_cast<APInt::WordType>(Rng()));
- RandomVal = APInt(Bits, RandomWords);
- }
+ APInt RandomVal = UndefBehavior == UndefValueBehavior::NonDeterministic
+ ? generateRandomAPInt(Bits)
+ : APInt::getZero(Bits);
if (Ty->isIntegerTy())
Val = AnyValue(RandomVal);
else if (Ty->isFloatingPointTy())
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index aa9d61cc6077e..1ad0fedddac16 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -94,6 +94,16 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
MemAllocKind AllocKind;
bool IsConstant = false;
+ // A tag is a randomly generated unique identifier to recover the provenance
+ // of a pointer. The length of tag is equal to the store size of the pointer
+ // 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: allow encoding metadata bits (e.g., captured pointer components) into
+ // the tag. The injection should affect all the bits in the tag rather than
+ // just some low bits for better robustness.
+ APInt Tag;
+
public:
MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name, unsigned AS,
MemInitKind InitKind, MemAllocKind AllocKind);
@@ -112,6 +122,8 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
MemAllocKind getAllocKind() const { return AllocKind; }
bool isConstant() const { return IsConstant; }
void setIsConstant(bool C) { IsConstant = C; }
+ const APInt &getTag() const { return Tag; }
+ void setTag(const APInt &T) { Tag = T; }
bool inBounds(const APInt &NewAddr) const {
return NewAddr.uge(Address) && NewAddr.ule(Address + Size);
@@ -195,6 +207,7 @@ class Context {
UndefValueBehavior UndefBehavior = UndefValueBehavior::NonDeterministic;
std::mt19937_64 Rng;
+ APInt generateRandomAPInt(uint32_t BitWidth);
// Memory
uint64_t UsedMem = 0;
@@ -202,14 +215,18 @@ class Context {
// For now we don't model the behavior of address reuse, which is common
// with stack coloring.
uint64_t AllocationBase = 8;
- // Maintains a global list of 'exposed' provenances. This is used to form a
- // pointer with an exposed provenance.
- // FIXME: Currently all the allocations are considered exposed, regardless of
- // their interaction with ptrtoint. That is, ptrtoint is allowed to recover
- // the provenance of any allocation. We may track the exposed provenances more
- // precisely after we make ptrtoint have the implicit side-effect of exposing
- // the provenance.
- std::map<uint64_t, IntrusiveRefCntPtr<MemoryObject>> MemoryObjects;
+ // All live memory objects.
+ // FIXME: global objects and stack objects should not be tracked in this map.
+ DenseMap<uint64_t, IntrusiveRefCntPtr<MemoryObject>> MemoryObjects;
+ // Mapping from tags to memory objects. Tags are lazily generated when a
+ // pointer is captured.
+ DenseMap<APInt, IntrusiveRefCntPtr<MemoryObject>> CapturedMemoryObjects;
+ // TODO: Maintains a global list of 'exposed' provenances. This is used to
+ // convert an address back to a pointer with a previously exposed provenance.
+
+ /// Get the tag for a pointer to the given memory object.
+ /// TODO: encode metadata bits into the tag.
+ APInt getTag(uint32_t BitWidth, MemoryObject *Obj);
AnyValue fromBytes(ConstBytesView Bytes, Type *Ty, uint32_t OffsetInBits,
bool CheckPaddingBits, bool *ContainsUndefinedBits);
void toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 82bf0f7b6eb22..1bc835b662b51 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -20,13 +20,15 @@ void Pointer::print(raw_ostream &OS) const {
SmallString<32> AddrStr;
Address.toStringUnsigned(AddrStr, 16);
OS << "ptr 0x" << AddrStr << " [";
- if (Obj && Obj->getState() != MemoryObjectState::Freed) {
+ if (Obj) {
OS << Obj->getName();
- // TODO: print " (dead)" if the stack object is out of lifetime.
if (Address != Obj->getAddress())
OS << " + " << (Address - Obj->getAddress());
+ MemoryObjectState State = Obj->getState();
+ if (State != MemoryObjectState::Alive)
+ OS << (State == MemoryObjectState::Dead ? " (dead)" : " (dangling)");
} else {
- OS << "dangling";
+ OS << "nullary";
}
OS << "]";
}
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index dfaf5f23a15b0..59d9a5c5f46fd 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -27,15 +27,18 @@ class AnyValue;
/// - If the concrete mask bit is 0, the bit is either undef or poison. The
/// value bit indicates whether it is undef.
/// - If the concrete mask bit is 1, the bit is a concrete value. The value bit
-/// stores the concrete bit value.
+/// stores the concrete bit value. The tag mask bit indicates whether it is a
+/// pointer bit, and the tag value bit is used for provenance tracking of
+/// pointers.
struct Byte {
uint8_t ConcreteMask;
uint8_t Value;
- // TODO: captured capabilities of pointers.
+ uint8_t TagMask; // A mask to indicate which bits are pointer bits.
+ uint8_t TagValue; // Part of the tag for provenance tracking of pointers.
- static Byte poison() { return Byte{0, 0}; }
- static Byte undef() { return Byte{0, 255}; }
- static Byte concrete(uint8_t Val) { return Byte{255, Val}; }
+ static Byte poison() { return Byte{0, 0, 0, 0}; }
+ static Byte undef() { return Byte{0, 255, 0, 0}; }
+ static Byte concrete(uint8_t Val) { return Byte{255, Val, 0, 0}; }
void zeroBits(uint8_t Mask) {
ConcreteMask |= Mask;
@@ -55,6 +58,15 @@ struct Byte {
void writeBits(uint8_t Mask, uint8_t Val) {
ConcreteMask |= Mask;
Value = (Value & ~Mask) | (Val & Mask);
+ TagMask &= ~Mask;
+ }
+
+ void writeTagBits(uint8_t Mask, uint8_t Tag) {
+ assert(
+ (ConcreteMask & Mask) == Mask &&
+ "Please ensure pointer bits are concrete before calling writeTagBits.");
+ TagMask |= Mask;
+ TagValue = (TagValue & ~Mask) | (Tag & Mask);
}
/// Returns a logical byte that is part of two adjacent bytes.
@@ -63,14 +75,19 @@ struct Byte {
/// LSB | 0 1 0 1 0 1 0 1 | 0 0 0 0 1 1 1 1 | MSB
/// Result = | 1 0 1 0 0 0 0 1 |
static Byte fshr(const Byte &Low, const Byte &High, uint32_t ShAmt) {
- return Byte{static_cast<uint8_t>(
- (Low.ConcreteMask | (High.ConcreteMask << 8)) >> ShAmt),
- static_cast<uint8_t>((Low.Value | (High.Value << 8)) >> ShAmt)};
+ return Byte{
+ static_cast<uint8_t>((Low.ConcreteMask | (High.ConcreteMask << 8)) >>
+ ShAmt),
+ static_cast<uint8_t>((Low.Value | (High.Value << 8)) >> ShAmt),
+ static_cast<uint8_t>((Low.TagMask | (High.TagMask << 8)) >> ShAmt),
+ static_cast<uint8_t>((Low.TagValue | (High.TagValue << 8)) >> ShAmt)};
}
Byte lshr(uint8_t Shift) const {
return Byte{static_cast<uint8_t>(ConcreteMask >> Shift),
- static_cast<uint8_t>(Value >> Shift)};
+ static_cast<uint8_t>(Value >> Shift),
+ static_cast<uint8_t>(TagMask >> Shift),
+ static_cast<uint8_t>(TagValue >> Shift)};
}
};
>From c0eb8862d8262a0017293d75185d4915706f9cd8 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Wed, 18 Mar 2026 20:59:24 +0800
Subject: [PATCH 2/4] [llubi] Add some comments about wildcard provenance
---
llvm/tools/llubi/lib/Context.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 1ad0fedddac16..96b30f1382197 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -102,6 +102,8 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
// TODO: allow encoding metadata bits (e.g., captured pointer components) into
// the tag. The injection should affect all the bits in the tag rather than
// just some low bits for better robustness.
+ // TODO: we need a special tag encoding for wildcard provenance, which is
+ // introduced by inttoptr.
APInt Tag;
public:
>From a92943d5c40147520e60bf13b7b0fa8cd3f8669b Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 3 May 2026 16:54:07 +0800
Subject: [PATCH 3/4] [llubi] Update tests.
---
llvm/test/tools/llubi/lib_calloc_large_size.ll | 4 ++--
llvm/test/tools/llubi/lib_calloc_size_overflow.ll | 2 +-
llvm/test/tools/llubi/lib_free_nullary_pointer.ll | 2 +-
llvm/test/tools/llubi/lib_malloc_large_size.ll | 4 ++--
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/llvm/test/tools/llubi/lib_calloc_large_size.ll b/llvm/test/tools/llubi/lib_calloc_large_size.ll
index 72b0764185a47..1a7968bc8d373 100644
--- a/llvm/test/tools/llubi/lib_calloc_large_size.ll
+++ b/llvm/test/tools/llubi/lib_calloc_large_size.ll
@@ -12,7 +12,7 @@ entry:
}
; CHECK: Entering function: main
; CHECK-NEXT: %ptr_1 = call ptr @calloc(i64 2, i64 25) => ptr 0x10 [ptr_1]
-; CHECK-NEXT: %ptr_2 = call ptr @calloc(i64 4, i64 25) => ptr 0x0 [dangling]
-; CHECK-NEXT: %ptr_3 = call ptr @calloc(i64 8, i64 25) => ptr 0x0 [dangling]
+; CHECK-NEXT: %ptr_2 = call ptr @calloc(i64 4, i64 25) => ptr 0x0 [nullary]
+; CHECK-NEXT: %ptr_3 = call ptr @calloc(i64 8, i64 25) => ptr 0x0 [nullary]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_calloc_size_overflow.ll b/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
index ea06f2bde1018..21446f0c62a89 100644
--- a/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
+++ b/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
@@ -9,6 +9,6 @@ entry:
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: %ptr = call ptr @calloc(i64 -1, i64 2) => ptr 0x0 [dangling]
+; CHECK-NEXT: %ptr = call ptr @calloc(i64 -1, i64 2) => ptr 0x0 [nullary]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_free_nullary_pointer.ll b/llvm/test/tools/llubi/lib_free_nullary_pointer.ll
index 251608a547ed9..098e387c70825 100644
--- a/llvm/test/tools/llubi/lib_free_nullary_pointer.ll
+++ b/llvm/test/tools/llubi/lib_free_nullary_pointer.ll
@@ -10,7 +10,7 @@ define i32 @main() {
ret i32 0
}
; CHECK: Entering function: main
-; CHECK-NEXT: %p = getelementptr i8, ptr null, i64 42 => ptr 0x2A [dangling]
+; CHECK-NEXT: %p = getelementptr i8, ptr null, i64 42 => ptr 0x2A [nullary]
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 call void @free(ptr %p) at @main
; CHECK-NEXT: Immediate UB detected: freeing a pointer with nullary provenance.
diff --git a/llvm/test/tools/llubi/lib_malloc_large_size.ll b/llvm/test/tools/llubi/lib_malloc_large_size.ll
index 8a1800f53af92..483290e11fea7 100644
--- a/llvm/test/tools/llubi/lib_malloc_large_size.ll
+++ b/llvm/test/tools/llubi/lib_malloc_large_size.ll
@@ -12,7 +12,7 @@ entry:
}
; CHECK: Entering function: main
; CHECK-NEXT: %ptr_1 = call ptr @malloc(i64 50) => ptr 0x10 [ptr_1]
-; CHECK-NEXT: %ptr_2 = call ptr @malloc(i64 100) => ptr 0x0 [dangling]
-; CHECK-NEXT: %ptr_3 = call ptr @malloc(i64 200) => ptr 0x0 [dangling]
+; CHECK-NEXT: %ptr_2 = call ptr @malloc(i64 100) => ptr 0x0 [nullary]
+; CHECK-NEXT: %ptr_3 = call ptr @malloc(i64 200) => ptr 0x0 [nullary]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
>From e9531437cbb2b38c2f1881744ae61365de12133b Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 4 May 2026 23:17:09 +0800
Subject: [PATCH 4/4] [llubi] Update tests.
---
.../tools/llubi/assume_misalign_all_ones.ll | 2 +-
llvm/test/tools/llubi/assume_null.ll | 2 +-
llvm/test/tools/llubi/assume_null_all_ones.ll | 4 +--
...e_dereferenceable_ub_nullary_provenance.ll | 4 +--
llvm/test/tools/llubi/attributes.ll | 26 +++++++++----------
llvm/test/tools/llubi/loadstore_be.ll | 11 +++++---
llvm/test/tools/llubi/loadstore_le.ll | 10 +++----
llvm/test/tools/llubi/metadata.ll | 20 +++++++-------
8 files changed, 40 insertions(+), 39 deletions(-)
diff --git a/llvm/test/tools/llubi/assume_misalign_all_ones.ll b/llvm/test/tools/llubi/assume_misalign_all_ones.ll
index aaf54f892789f..c3e96fb144cbd 100644
--- a/llvm/test/tools/llubi/assume_misalign_all_ones.ll
+++ b/llvm/test/tools/llubi/assume_misalign_all_ones.ll
@@ -10,5 +10,5 @@ define void @main() {
; CHECK: Entering function: main
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "align"(ptr addrspace(1) null, i32 2048) ] at @main
-; CHECK-NEXT: Immediate UB detected: The pointer ptr 0xFFFFFFFFFFFFFFFF [dangling] violates align(2048) assumption.
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0xFFFFFFFFFFFFFFFF [nullary] violates align(2048) assumption.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_null.ll b/llvm/test/tools/llubi/assume_null.ll
index ef55ce54ed6b7..be187dd166a58 100644
--- a/llvm/test/tools/llubi/assume_null.ll
+++ b/llvm/test/tools/llubi/assume_null.ll
@@ -8,5 +8,5 @@ define void @main() {
; CHECK: Entering function: main
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "nonnull"(ptr null) ] at @main
-; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x0 [dangling] violates nonnull assumption.
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x0 [nullary] violates nonnull assumption.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_null_all_ones.ll b/llvm/test/tools/llubi/assume_null_all_ones.ll
index 94dcebb600056..d8663f43c2116 100644
--- a/llvm/test/tools/llubi/assume_null_all_ones.ll
+++ b/llvm/test/tools/llubi/assume_null_all_ones.ll
@@ -13,8 +13,8 @@ define void @main() {
; CHECK: Entering function: main
; CHECK-NEXT: %storage = alloca ptr, align 8 => ptr 0x8 [storage]
; CHECK-NEXT: store i64 -1, ptr %storage, align 4
-; CHECK-NEXT: %res = load ptr addrspace(1), ptr %storage, align 8 => ptr 0xFFFFFFFFFFFFFFFF [dangling]
+; CHECK-NEXT: %res = load ptr addrspace(1), ptr %storage, align 8 => ptr 0xFFFFFFFFFFFFFFFF [nullary]
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "nonnull"(ptr addrspace(1) %res) ] at @main
-; CHECK-NEXT: Immediate UB detected: The pointer ptr 0xFFFFFFFFFFFFFFFF [dangling] violates nonnull assumption.
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0xFFFFFFFFFFFFFFFF [nullary] violates nonnull assumption.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
index b4d585c2a71c3..7d3e4edf6ae8e 100644
--- a/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
@@ -13,8 +13,8 @@ define void @main() {
}
; CHECK: Entering function: main
; CHECK-NEXT: %ptr_storage = alloca i64, align 8 => ptr 0x8 [ptr_storage]
-; CHECK-NEXT: %p = load ptr, ptr %ptr_storage, align 8 => ptr 0xE82FEEACEEB98B3E [dangling]
+; CHECK-NEXT: %p = load ptr, ptr %ptr_storage, align 8 => ptr 0xE82FEEACEEB98B3E [nullary]
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 call void @callee(ptr %p) at @main
-; CHECK-NEXT: Immediate UB detected: The value ptr 0xE82FEEACEEB98B3E [dangling] violates dereferenceable{{(_or_null)?}}(4) attribute.
+; CHECK-NEXT: Immediate UB detected: The value ptr 0xE82FEEACEEB98B3E [nullary] violates dereferenceable{{(_or_null)?}}(4) attribute.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
index 1a4acfb76a0c2..9ec65e31c8390 100644
--- a/llvm/test/tools/llubi/attributes.ll
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -197,7 +197,7 @@ define void @main() {
; CHECK-NEXT: Exiting function: identity_nofpclass_agg
; CHECK-NEXT: %nofpclass_agg = call { <2 x half>, <2 x half> } @identity_nofpclass_agg({ <2 x half>, <2 x half> } { <2 x half> <half 0xH3C00, half poison>, <2 x half> <half 0xH7C00, half 0xH7E00> }) => { { half 1.000000e+00, poison }, { poison, poison } }
; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
-; CHECK-NEXT: %ptr_one = getelementptr i8, ptr null, i32 1 => ptr 0x1 [dangling]
+; CHECK-NEXT: %ptr_one = getelementptr i8, ptr null, i32 1 => ptr 0x1 [nullary]
; CHECK-NEXT: Entering function: gep_nonnull
; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x7 [alloc + -1]
@@ -211,8 +211,8 @@ define void @main() {
; CHECK-NEXT: Exiting function: gep_nonnull
; CHECK-NEXT: %nonnull_invalid_input = call ptr @gep_nonnull(ptr null) => poison
; CHECK-NEXT: Entering function: gep_nonnull
-; CHECK-NEXT: ptr %p = ptr 0x1 [dangling]
-; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x0 [dangling]
+; CHECK-NEXT: ptr %p = ptr 0x1 [nullary]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x0 [nullary]
; CHECK-NEXT: ret ptr %gep
; CHECK-NEXT: Exiting function: gep_nonnull
; CHECK-NEXT: %nonnull_invalid_output = call ptr @gep_nonnull(ptr %ptr_one) => poison
@@ -229,8 +229,8 @@ define void @main() {
; CHECK-NEXT: Exiting function: gep
; CHECK-NEXT: %nonnull_callsite_invalid_input = call ptr @gep(ptr nonnull null) => poison
; CHECK-NEXT: Entering function: gep
-; CHECK-NEXT: ptr %p = ptr 0x1 [dangling]
-; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x0 [dangling]
+; CHECK-NEXT: ptr %p = ptr 0x1 [nullary]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x0 [nullary]
; CHECK-NEXT: ret ptr %gep
; CHECK-NEXT: Exiting function: gep
; CHECK-NEXT: %nonnull_callsite_invalid_output = call nonnull ptr @gep(ptr %ptr_one) => poison
@@ -247,17 +247,17 @@ define void @main() {
; CHECK-NEXT: Exiting function: gep_align
; CHECK-NEXT: %align_invalid_input = call ptr @gep_align(ptr %ptr_one) => poison
; CHECK-NEXT: Entering function: gep_align
-; CHECK-NEXT: ptr %p = ptr 0x0 [dangling]
-; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x8 [dangling]
+; CHECK-NEXT: ptr %p = ptr 0x0 [nullary]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x8 [nullary]
; CHECK-NEXT: ret ptr %gep
; CHECK-NEXT: Exiting function: gep_align
; CHECK-NEXT: %align_invalid_output = call ptr @gep_align(ptr null) => poison
; CHECK-NEXT: %ptr_vec_1 = insertelement <4 x ptr> poison, ptr %alloc, i32 0 => { ptr 0x8 [alloc], poison, poison, poison }
-; CHECK-NEXT: %ptr_vec_2 = insertelement <4 x ptr> %ptr_vec_1, ptr %ptr_one, i32 1 => { ptr 0x8 [alloc], ptr 0x1 [dangling], poison, poison }
-; CHECK-NEXT: %ptr_vec_3 = insertelement <4 x ptr> %ptr_vec_2, ptr null, i32 2 => { ptr 0x8 [alloc], ptr 0x1 [dangling], ptr 0x0 [dangling], poison }
+; CHECK-NEXT: %ptr_vec_2 = insertelement <4 x ptr> %ptr_vec_1, ptr %ptr_one, i32 1 => { ptr 0x8 [alloc], ptr 0x1 [nullary], poison, poison }
+; CHECK-NEXT: %ptr_vec_3 = insertelement <4 x ptr> %ptr_vec_2, ptr null, i32 2 => { ptr 0x8 [alloc], ptr 0x1 [nullary], ptr 0x0 [nullary], poison }
; CHECK-NEXT: Entering function: gep_align_vec
-; CHECK-NEXT: <4 x ptr> %p = { ptr 0x8 [alloc], poison, ptr 0x0 [dangling], poison }
-; CHECK-NEXT: %gep = getelementptr i8, <4 x ptr> %p, i32 8 => { ptr 0x10 [alloc + 8], poison, ptr 0x8 [dangling], poison }
+; CHECK-NEXT: <4 x ptr> %p = { ptr 0x8 [alloc], poison, ptr 0x0 [nullary], poison }
+; CHECK-NEXT: %gep = getelementptr i8, <4 x ptr> %p, i32 8 => { ptr 0x10 [alloc + 8], poison, ptr 0x8 [nullary], poison }
; CHECK-NEXT: ret <4 x ptr> %gep
; CHECK-NEXT: Exiting function: gep_align_vec
; CHECK-NEXT: %align_vec = call <4 x ptr> @gep_align_vec(<4 x ptr> %ptr_vec_3) => { ptr 0x10 [alloc + 8], poison, poison, poison }
@@ -311,10 +311,10 @@ define void @main() {
; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
; CHECK-NEXT: %deref_or_null_valid1 = call ptr @identity_dereferenceable_or_null(ptr %alloc) => ptr 0x8 [alloc]
; CHECK-NEXT: Entering function: identity_dereferenceable_or_null
-; CHECK-NEXT: ptr %p = ptr 0x0 [dangling]
+; CHECK-NEXT: ptr %p = ptr 0x0 [nullary]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
-; CHECK-NEXT: %deref_or_null_valid2 = call ptr @identity_dereferenceable_or_null(ptr null) => ptr 0x0 [dangling]
+; CHECK-NEXT: %deref_or_null_valid2 = call ptr @identity_dereferenceable_or_null(ptr null) => ptr 0x0 [nullary]
; CHECK-NEXT: Entering function: identity_dereferenceable_or_null
; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
; CHECK-NEXT: ret ptr %p
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index d394068ffe990..8f2e0815f88fe 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -110,7 +110,7 @@ define void @main() {
%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 !{}
-
+
%alloc_ptr = alloca ptr
store ptr %alloc_ptr, ptr %alloc_ptr
; It should recover the provenance.
@@ -200,10 +200,13 @@ define void @main() {
; 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: %alloc_ptr = alloca ptr, align 8 => ptr 0x88 [alloc_ptr]
-; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x88 [alloc_ptr]
+; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [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 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 0x88 [nullary]
+; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [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 6f6826d884adc..3caf02c3e6eaa 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -111,7 +111,7 @@ define void @main() {
%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 !{}
-
+
%alloc_ptr = alloca ptr
store ptr %alloc_ptr, ptr %alloc_ptr
; It should recover the provenance.
@@ -204,11 +204,11 @@ define void @main() {
; 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: %alloc_ptr = alloca ptr, align 8 => ptr 0x88 [alloc_ptr]
+; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [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 0x88 [alloc_ptr]
-; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -120
+; 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: store i8 %addr_bits, ptr %alloc_ptr, align 1
-; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x88 [nullary]
+; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [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 7b4309e139e79..4c2759698e260 100644
--- a/llvm/test/tools/llubi/metadata.ll
+++ b/llvm/test/tools/llubi/metadata.ll
@@ -25,13 +25,11 @@ define void @main() {
%nofpclass_load_valid = load float, ptr %alloc, !noundef !{}, !nofpclass !{i32 3}
%nofpclass_load_invalid = load float, ptr %alloc, !nofpclass !{i32 99}
- ; TODO: Test dereferenceable[_or_null] after provenance support is ready
-
%alloc_ptr = alloca ptr
store ptr %alloc_ptr, ptr %alloc_ptr
- %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, !nonnull !{}, !align !{i32 8}, !noundef !{}
+ %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, !nonnull !{}, !align !{i32 8}, !noundef !{}, !dereferenceable !{i32 8}, !dereferenceable_or_null !{i32 8}
store ptr null, ptr %alloc_ptr
- %align_load_valid = load ptr, ptr %alloc_ptr, !align !{i32 8}, !noundef !{}
+ %align_load_valid = load ptr, ptr %alloc_ptr, !align !{i32 8}, !noundef !{}, !dereferenceable_or_null !{i32 8}
%nonnull_load_invalid = load ptr, ptr %alloc_ptr, !nonnull !{}
%range_call_valid = call i32 @callee(), !noundef !{}, !range !{i32 0, i32 11}
@@ -60,9 +58,9 @@ define void @main() {
; 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: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
-; CHECK-NEXT: %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !align !6, !noundef !1 => ptr 0x40 [dangling]
+; 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: store ptr null, ptr %alloc_ptr, align 8
-; CHECK-NEXT: %align_load_valid = load ptr, ptr %alloc_ptr, align 8, !align !6, !noundef !1 => ptr 0x0 [dangling]
+; 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
; CHECK-NEXT: Entering function: callee
; CHECK-NEXT: ret i32 10
@@ -86,12 +84,12 @@ define void @main() {
; 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: Entering function: callee_ptr
-; CHECK-NEXT: ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT: ptr %x = ptr 0x0 [nullary]
; CHECK-NEXT: ret ptr %x
; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT: %align_call_invalid = call ptr @callee_ptr(ptr null), !align !6, !noundef !1 => ptr 0x0 [dangling]
+; CHECK-NEXT: %align_call_invalid = call ptr @callee_ptr(ptr null), !align !6, !noundef !1 => ptr 0x0 [nullary]
; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT: ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT: ptr %x = ptr 0x0 [nullary]
; CHECK-NEXT: ret ptr %x
; CHECK-NEXT: Exiting function: callee_ptr
; CHECK-NEXT: %nonnull_call_invalid = call ptr @callee_ptr(ptr null), !nonnull !1 => poison
@@ -106,9 +104,9 @@ define void @main() {
; 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: Entering function: callee_ptr
-; CHECK-NEXT: ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT: ptr %x = ptr 0x0 [nullary]
; CHECK-NEXT: ret ptr %x
; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT: %dereferenceable_or_null_call_valid2 = call ptr @callee_ptr(ptr null), !dereferenceable_or_null !6 => ptr 0x0 [dangling]
+; CHECK-NEXT: %dereferenceable_or_null_call_valid2 = call ptr @callee_ptr(ptr null), !dereferenceable_or_null !6 => ptr 0x0 [nullary]
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
More information about the llvm-commits
mailing list