[llvm] [llubi] Improve diagnostics and add stacktrace (PR #195449)
Zhige Chen via llvm-commits
llvm-commits at lists.llvm.org
Sat May 2 06:49:41 PDT 2026
https://github.com/nofe1248 updated https://github.com/llvm/llvm-project/pull/195449
>From c48efd6ecec381b235a6215411f9d9657d30284d Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Sat, 2 May 2026 21:15:41 +0800
Subject: [PATCH 1/3] [llubi] Improve diagnostics and add stacktrace
---
llvm/test/tools/llubi/alloca_large_count.ll | 4 +-
llvm/test/tools/llubi/alloca_large_size.ll | 2 +
llvm/test/tools/llubi/alloca_poison_count.ll | 2 +
llvm/test/tools/llubi/alloca_size_overflow.ll | 4 +-
llvm/test/tools/llubi/assume_false.ll | 2 +
llvm/test/tools/llubi/assume_poison.ll | 2 +
llvm/test/tools/llubi/br_poison.ll | 2 +
.../tools/llubi/call_mismatched_signature.ll | 4 +-
llvm/test/tools/llubi/call_poison.ll | 2 +
llvm/test/tools/llubi/divrem_ub1.ll | 2 +
llvm/test/tools/llubi/divrem_ub2.ll | 2 +
llvm/test/tools/llubi/divrem_ub3.ll | 2 +
llvm/test/tools/llubi/divrem_ub4.ll | 2 +
llvm/test/tools/llubi/indirectbr_invalid.ll | 2 +
llvm/test/tools/llubi/indirectbr_poison.ll | 2 +
llvm/test/tools/llubi/infinite_loop.ll | 2 +
llvm/test/tools/llubi/invoke_poison.ll | 3 +
.../tools/llubi/lib_cxx_memory_large_size.ll | 2 +
llvm/test/tools/llubi/lib_double_free.ll | 4 +-
.../tools/llubi/lib_free_nullary_pointer.ll | 2 +
.../test/tools/llubi/lib_free_out_of_bound.ll | 4 +-
llvm/test/tools/llubi/lib_free_stack.ll | 4 +-
llvm/test/tools/llubi/lib_poison_argument.ll | 4 +-
.../llubi/lib_printf_not_enough_argument.ll | 4 +-
.../llubi/lib_printf_unknown_specifier.ll | 4 +-
.../tools/llubi/lib_read_nullary_string.ll | 2 +
llvm/test/tools/llubi/lib_uninit_string.ll | 4 +-
llvm/test/tools/llubi/loadstore_misaligned.ll | 2 +
llvm/test/tools/llubi/loadstore_null.ll | 2 +
llvm/test/tools/llubi/loadstore_oob1.ll | 4 +-
llvm/test/tools/llubi/loadstore_poison.ll | 2 +
llvm/test/tools/llubi/loadstore_uaf.ll | 4 +-
llvm/test/tools/llubi/stack_overflow.ll | 11 +++
llvm/test/tools/llubi/store_dead.ll | 4 +-
llvm/test/tools/llubi/switch_poison.ll | 3 +
llvm/test/tools/llubi/unreachable.ll | 2 +
llvm/tools/llubi/lib/ExecutorBase.cpp | 62 +++++++++++++---
llvm/tools/llubi/lib/ExecutorBase.h | 49 +++++++++++-
llvm/tools/llubi/lib/Interpreter.cpp | 74 ++++++++++---------
llvm/tools/llubi/lib/Library.cpp | 59 +++++++++------
40 files changed, 271 insertions(+), 82 deletions(-)
diff --git a/llvm/test/tools/llubi/alloca_large_count.ll b/llvm/test/tools/llubi/alloca_large_count.ll
index 62772317ae561..3601f4aa689ca 100644
--- a/llvm/test/tools/llubi/alloca_large_count.ll
+++ b/llvm/test/tools/llubi/alloca_large_count.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: Alloca with large array size that overflows uint64_t.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %alloc_dyn = alloca i32, i128 -1, align 4 at @main
+; CHECK-NEXT: Immediate UB detected: Alloca with large array size that overflows uint64_t. Size: -1
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/alloca_large_size.ll b/llvm/test/tools/llubi/alloca_large_size.ll
index a9773d3bcb184..478dc88c36206 100644
--- a/llvm/test/tools/llubi/alloca_large_size.ll
+++ b/llvm/test/tools/llubi/alloca_large_size.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %alloc_dyn = alloca i32, i32 100, align 4 at @main
; CHECK-NEXT: Error: Insufficient stack space.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/alloca_poison_count.ll b/llvm/test/tools/llubi/alloca_poison_count.ll
index c8c97c4517bfc..4b092bb41033f 100644
--- a/llvm/test/tools/llubi/alloca_poison_count.ll
+++ b/llvm/test/tools/llubi/alloca_poison_count.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %alloc_dyn = alloca i32, i32 poison, align 4 at @main
; CHECK-NEXT: Immediate UB detected: Alloca with poison array size.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/alloca_size_overflow.ll b/llvm/test/tools/llubi/alloca_size_overflow.ll
index 258f94d57d90f..f70e72d1c47c9 100644
--- a/llvm/test/tools/llubi/alloca_size_overflow.ll
+++ b/llvm/test/tools/llubi/alloca_size_overflow.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: Alloca with allocation size that overflows uint64_t.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %alloc_dyn = alloca i32, i64 -1, align 4 at @main
+; CHECK-NEXT: Immediate UB detected: Alloca with allocation size that overflows uint64_t. Size: -1
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_false.ll b/llvm/test/tools/llubi/assume_false.ll
index d621db1db97be..a8bb04c259ec6 100644
--- a/llvm/test/tools/llubi/assume_false.ll
+++ b/llvm/test/tools/llubi/assume_false.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 false) at @main
; CHECK-NEXT: Immediate UB detected: Assume on false or poison condition.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_poison.ll b/llvm/test/tools/llubi/assume_poison.ll
index c178c19f2d221..a33bf9224a497 100644
--- a/llvm/test/tools/llubi/assume_poison.ll
+++ b/llvm/test/tools/llubi/assume_poison.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 poison) at @main
; CHECK-NEXT: Immediate UB detected: Assume on false or poison condition.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/br_poison.ll b/llvm/test/tools/llubi/br_poison.ll
index dcad8d0584767..c6be9f8c09637 100644
--- a/llvm/test/tools/llubi/br_poison.ll
+++ b/llvm/test/tools/llubi/br_poison.ll
@@ -9,5 +9,7 @@ exit:
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 br i1 poison, label %exit, label %exit at @main
; CHECK-NEXT: Immediate UB detected: Branch on poison condition.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/call_mismatched_signature.ll b/llvm/test/tools/llubi/call_mismatched_signature.ll
index 9c65dd231826c..50227d310fa01 100644
--- a/llvm/test/tools/llubi/call_mismatched_signature.ll
+++ b/llvm/test/tools/llubi/call_mismatched_signature.ll
@@ -10,5 +10,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: Indirect call through a function pointer with mismatched signature.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @foo() at @main
+; CHECK-NEXT: Immediate UB detected: Indirect call through a function pointer with mismatched signature. Expected: void (), Actual: i32 ()
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/call_poison.ll b/llvm/test/tools/llubi/call_poison.ll
index a9dec9e0603f1..01c76e189bdd0 100644
--- a/llvm/test/tools/llubi/call_poison.ll
+++ b/llvm/test/tools/llubi/call_poison.ll
@@ -7,5 +7,7 @@ entry:
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void poison() at @main
; CHECK-NEXT: Immediate UB detected: Indirect call through poison function pointer.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/divrem_ub1.ll b/llvm/test/tools/llubi/divrem_ub1.ll
index 00b6dfeca43f5..7923ad4e20967 100644
--- a/llvm/test/tools/llubi/divrem_ub1.ll
+++ b/llvm/test/tools/llubi/divrem_ub1.ll
@@ -8,5 +8,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = {{sdiv|udiv|srem|urem}} <2 x i32> splat (i32 10), zeroinitializer at @main
; CHECK-NEXT: Immediate UB detected: Division by zero.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/divrem_ub2.ll b/llvm/test/tools/llubi/divrem_ub2.ll
index 03a941da1e7a0..b4bdbac3f2c2e 100644
--- a/llvm/test/tools/llubi/divrem_ub2.ll
+++ b/llvm/test/tools/llubi/divrem_ub2.ll
@@ -8,5 +8,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = {{sdiv|udiv|srem|urem}} i32 10, poison at @main
; CHECK-NEXT: Immediate UB detected: Division by zero (refine RHS to 0).
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/divrem_ub3.ll b/llvm/test/tools/llubi/divrem_ub3.ll
index 7ab668d6b9c8a..00e91bffd2f63 100644
--- a/llvm/test/tools/llubi/divrem_ub3.ll
+++ b/llvm/test/tools/llubi/divrem_ub3.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = {{sdiv|udiv|srem|urem}} i8 -128, -1 at @main
; CHECK-NEXT: Immediate UB detected: Signed division overflow.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/divrem_ub4.ll b/llvm/test/tools/llubi/divrem_ub4.ll
index 90a7d7c025f23..cdb673d55cd55 100644
--- a/llvm/test/tools/llubi/divrem_ub4.ll
+++ b/llvm/test/tools/llubi/divrem_ub4.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = {{sdiv|udiv|srem|urem}} i8 poison, -1 at @main
; CHECK-NEXT: Immediate UB detected: Signed division overflow (refine LHS to INT_MIN).
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/indirectbr_invalid.ll b/llvm/test/tools/llubi/indirectbr_invalid.ll
index 9ffb26b0aebbb..2dfb99db36f09 100644
--- a/llvm/test/tools/llubi/indirectbr_invalid.ll
+++ b/llvm/test/tools/llubi/indirectbr_invalid.ll
@@ -12,5 +12,7 @@ exit:
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 indirectbr ptr blockaddress(@main, %bb2), [label %exit] at @main
; CHECK-NEXT: Immediate UB detected: Indirect branch on unlisted target BB.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/indirectbr_poison.ll b/llvm/test/tools/llubi/indirectbr_poison.ll
index 076aa79c50a52..d2c5fa266a662 100644
--- a/llvm/test/tools/llubi/indirectbr_poison.ll
+++ b/llvm/test/tools/llubi/indirectbr_poison.ll
@@ -9,5 +9,7 @@ exit:
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 indirectbr ptr poison, [label %exit] at @main
; CHECK-NEXT: Immediate UB detected: Indirect branch on poison.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/infinite_loop.ll b/llvm/test/tools/llubi/infinite_loop.ll
index bc0fda540b758..01f38589c2d07 100644
--- a/llvm/test/tools/llubi/infinite_loop.ll
+++ b/llvm/test/tools/llubi/infinite_loop.ll
@@ -19,5 +19,7 @@ loop:
; CHECK-NEXT: br label %loop jump to %loop
; CHECK-NEXT: br label %loop jump to %loop
; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 br label %loop at @main
; CHECK-NEXT: Error: Exceeded maximum number of execution steps.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/invoke_poison.ll b/llvm/test/tools/llubi/invoke_poison.ll
index 709f3e96b8a28..21927d87f332a 100644
--- a/llvm/test/tools/llubi/invoke_poison.ll
+++ b/llvm/test/tools/llubi/invoke_poison.ll
@@ -13,5 +13,8 @@ exit:
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 invoke void poison()
+; CHECK-NEXT: to label %exit unwind label %cleanup at @main
; CHECK-NEXT: Immediate UB detected: Indirect call through poison function pointer.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_cxx_memory_large_size.ll b/llvm/test/tools/llubi/lib_cxx_memory_large_size.ll
index 207bb469c9199..a8e7c58051e5a 100644
--- a/llvm/test/tools/llubi/lib_cxx_memory_large_size.ll
+++ b/llvm/test/tools/llubi/lib_cxx_memory_large_size.ll
@@ -11,5 +11,7 @@ define i32 @main() {
}
; CHECK: Entering function: main
; CHECK-NEXT: %ptr_1 = call ptr @_Znwm(i64 50) => ptr 0x10 [ptr_1]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %ptr_2 = call ptr @_Znwm(i64 100) at @main
; CHECK-NEXT: Error: Insufficient heap space.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_double_free.ll b/llvm/test/tools/llubi/lib_double_free.ll
index db0a684e74193..19408d796b467 100644
--- a/llvm/test/tools/llubi/lib_double_free.ll
+++ b/llvm/test/tools/llubi/lib_double_free.ll
@@ -17,5 +17,7 @@ entry:
; CHECK: Entering function: main
; CHECK-NEXT: %ptr = call ptr @malloc(i64 4) => ptr 0x10 [ptr]
; CHECK-NEXT: call void @free(ptr %ptr)
-; CHECK-NEXT: Immediate UB detected: double-freeing a memory object.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @free(ptr %ptr) at @main
+; CHECK-NEXT: Immediate UB detected: double-freeing a memory object allocated at 0x10.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_free_nullary_pointer.ll b/llvm/test/tools/llubi/lib_free_nullary_pointer.ll
index b487a7b335f60..251608a547ed9 100644
--- a/llvm/test/tools/llubi/lib_free_nullary_pointer.ll
+++ b/llvm/test/tools/llubi/lib_free_nullary_pointer.ll
@@ -11,5 +11,7 @@ define i32 @main() {
}
; CHECK: Entering function: main
; CHECK-NEXT: %p = getelementptr i8, ptr null, i64 42 => ptr 0x2A [dangling]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @free(ptr %p) at @main
; CHECK-NEXT: Immediate UB detected: freeing a pointer with nullary provenance.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_free_out_of_bound.ll b/llvm/test/tools/llubi/lib_free_out_of_bound.ll
index 827df76ce3562..5ab7fa2452103 100644
--- a/llvm/test/tools/llubi/lib_free_out_of_bound.ll
+++ b/llvm/test/tools/llubi/lib_free_out_of_bound.ll
@@ -14,5 +14,7 @@ define i32 @main() {
; CHECK: Entering function: main
; CHECK-NEXT: %p = call ptr @malloc(i64 4) => ptr 0x10 [p]
; CHECK-NEXT: %p_oob = getelementptr i8, ptr %p, i64 8 => ptr 0x18 [p + 8]
-; CHECK-NEXT: Immediate UB detected: freeing a pointer that does not point to the start of an allocation.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @free(ptr %p_oob) at @main
+; CHECK-NEXT: Immediate UB detected: freeing a pointer that does not point to the start of an allocation. Pointer address: 0x18, allocation base: 0x10.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_free_stack.ll b/llvm/test/tools/llubi/lib_free_stack.ll
index ca4fc72101a1c..d6f80003913e3 100644
--- a/llvm/test/tools/llubi/lib_free_stack.ll
+++ b/llvm/test/tools/llubi/lib_free_stack.ll
@@ -11,5 +11,7 @@ define i32 @main() {
}
; CHECK: Entering function: main
; CHECK-NEXT: %p = alloca i32, align 4 => ptr 0x8 [p]
-; CHECK-NEXT: Immediate UB detected: freeing a non-heap allocation.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @free(ptr %p) at @main
+; CHECK-NEXT: Immediate UB detected: freeing a non-heap allocation at 0x8.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_poison_argument.ll b/llvm/test/tools/llubi/lib_poison_argument.ll
index 4ebea78faf0d2..7889609e59290 100644
--- a/llvm/test/tools/llubi/lib_poison_argument.ll
+++ b/llvm/test/tools/llubi/lib_poison_argument.ll
@@ -9,5 +9,7 @@ define i32 @main() {
ret i32 0
}
; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: Poison argument passed to a library call.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %1 = call i32 @puts(ptr poison) at @main
+; CHECK-NEXT: Immediate UB detected: Poison argument passed to a library call at argument index 0.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_printf_not_enough_argument.ll b/llvm/test/tools/llubi/lib_printf_not_enough_argument.ll
index 87158213b5e70..03b03cc32a512 100644
--- a/llvm/test/tools/llubi/lib_printf_not_enough_argument.ll
+++ b/llvm/test/tools/llubi/lib_printf_not_enough_argument.ll
@@ -14,5 +14,7 @@ define i32 @main() {
; CHECK: Entering function: main
; CHECK-NEXT: %fmt = alloca [18 x i8], align 1 => ptr 0x8 [fmt]
; CHECK-NEXT: store [18 x i8] c"Ints: %d, %i, %u\0A\00", ptr %fmt, align 1
-; CHECK-NEXT: Immediate UB detected: Not enough arguments provided for the format string.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %1 = call i32 (ptr, ...) @printf(ptr %fmt, i32 42, i32 -42) at @main
+; CHECK-NEXT: Immediate UB detected: Not enough arguments provided for the format string. Required argument for 'u'.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_printf_unknown_specifier.ll b/llvm/test/tools/llubi/lib_printf_unknown_specifier.ll
index d6c4cb7f90b53..29fb7c39c81e1 100644
--- a/llvm/test/tools/llubi/lib_printf_unknown_specifier.ll
+++ b/llvm/test/tools/llubi/lib_printf_unknown_specifier.ll
@@ -14,5 +14,7 @@ define i32 @main() {
; CHECK: Entering function: main
; CHECK-NEXT: %fmt = alloca [4 x i8], align 1 => ptr 0x8 [fmt]
; CHECK-NEXT: store [4 x i8] c"%m\0A\00", ptr %fmt, align 1
-; CHECK-NEXT: Immediate UB detected: Unknown or unsupported format specifier in printf.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %1 = call i32 (ptr, ...) @printf(ptr %fmt, i32 0) at @main
+; CHECK-NEXT: Immediate UB detected: Unknown or unsupported format specifier 'm' in printf.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_read_nullary_string.ll b/llvm/test/tools/llubi/lib_read_nullary_string.ll
index 2c1e354a3850e..bdf848f87da05 100644
--- a/llvm/test/tools/llubi/lib_read_nullary_string.ll
+++ b/llvm/test/tools/llubi/lib_read_nullary_string.ll
@@ -9,5 +9,7 @@ define i32 @main() {
ret i32 0
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %1 = call i32 @puts(ptr null) at @main
; 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_uninit_string.ll b/llvm/test/tools/llubi/lib_uninit_string.ll
index 7274cfdb63363..917e2b14078cb 100644
--- a/llvm/test/tools/llubi/lib_uninit_string.ll
+++ b/llvm/test/tools/llubi/lib_uninit_string.ll
@@ -14,5 +14,7 @@ entry:
}
; CHECK: Entering function: main
; CHECK-NEXT: %ptr = call ptr @malloc(i64 10) => ptr 0x10 [ptr]
-; CHECK-NEXT: Immediate UB detected: Read uninitialized or poison memory while parsing C-string.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %0 = call i32 @puts(ptr %ptr) at @main
+; CHECK-NEXT: Immediate UB detected: Read uninitialized or poison memory while parsing C-string at offset 0.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_misaligned.ll b/llvm/test/tools/llubi/loadstore_misaligned.ll
index cc80639d773bd..bec10370e9832 100644
--- a/llvm/test/tools/llubi/loadstore_misaligned.ll
+++ b/llvm/test/tools/llubi/loadstore_misaligned.ll
@@ -10,5 +10,7 @@ define void @main() {
; CHECK: Entering function: main
; CHECK-NEXT: %alloc = alloca [2 x i32], align 8 => ptr 0x8 [alloc]
; CHECK-NEXT: %gep = getelementptr inbounds [2 x i32], ptr %alloc, i64 0, i64 1 => ptr 0xC [alloc + 4]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 {{store i32 0|%res = load i32}}, ptr %gep, align 8 at @main
; CHECK-NEXT: Immediate UB detected: Misaligned memory access.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_null.ll b/llvm/test/tools/llubi/loadstore_null.ll
index 7a243784e6bd6..d6ee8cc64e881 100644
--- a/llvm/test/tools/llubi/loadstore_null.ll
+++ b/llvm/test/tools/llubi/loadstore_null.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 {{store i32 0|%res = load i32}}, ptr null, align 4 at @main
; 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/loadstore_oob1.ll b/llvm/test/tools/llubi/loadstore_oob1.ll
index 0618faa96bfde..93c2a53f17c9b 100644
--- a/llvm/test/tools/llubi/loadstore_oob1.ll
+++ b/llvm/test/tools/llubi/loadstore_oob1.ll
@@ -10,5 +10,7 @@ define void @main() {
; CHECK: Entering function: main
; CHECK-NEXT: %alloc = alloca [2 x i32], align 4 => ptr 0x8 [alloc]
; CHECK-NEXT: %gep = getelementptr inbounds [2 x i32], ptr %alloc, i64 0, i64 2 => ptr 0x10 [alloc + 8]
-; CHECK-NEXT: Immediate UB detected: Memory access is out of bounds.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 {{store i32 0|%res = load i32}}, ptr %gep, align 4 at @main
+; CHECK-NEXT: Immediate UB detected: Memory access is out of bounds. Accessed size: 4, Address: 0x10, Object base: 0x8, Object size: 8.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_poison.ll b/llvm/test/tools/llubi/loadstore_poison.ll
index 44c72aa803709..694c52da99d3c 100644
--- a/llvm/test/tools/llubi/loadstore_poison.ll
+++ b/llvm/test/tools/llubi/loadstore_poison.ll
@@ -6,5 +6,7 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 {{store i32 0|%res = load i32}}, ptr poison, align 4 at @main
; CHECK-NEXT: Immediate UB detected: Invalid memory access with a poison pointer.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_uaf.ll b/llvm/test/tools/llubi/loadstore_uaf.ll
index 21b86552275b6..ab81a75fd2cb7 100644
--- a/llvm/test/tools/llubi/loadstore_uaf.ll
+++ b/llvm/test/tools/llubi/loadstore_uaf.ll
@@ -17,5 +17,7 @@ define void @main() {
; CHECK-NEXT: ret ptr %alloc
; CHECK-NEXT: Exiting function: stack_object
; CHECK-NEXT: %alloc = call ptr @stack_object() => ptr 0x8 [dangling]
-; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object.
+; 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.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/stack_overflow.ll b/llvm/test/tools/llubi/stack_overflow.ll
index a241d176ad696..f38350dd4e7d8 100644
--- a/llvm/test/tools/llubi/stack_overflow.ll
+++ b/llvm/test/tools/llubi/stack_overflow.ll
@@ -103,5 +103,16 @@ entry:
; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 41
; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 40
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #1 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #2 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #3 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #4 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #5 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #6 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #7 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #8 %call1 = call i32 @fib(i32 %sub1) at @fib
+; CHECK-NEXT: #9 %res2 = call i32 @fib(i32 50) at @main
; CHECK-NEXT: Error: Maximum stack depth exceeded.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/store_dead.ll b/llvm/test/tools/llubi/store_dead.ll
index 8bd15beefb8d3..53ad2525369cb 100644
--- a/llvm/test/tools/llubi/store_dead.ll
+++ b/llvm/test/tools/llubi/store_dead.ll
@@ -14,5 +14,7 @@ define void @main() {
; 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)
-; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %alloc, align 4 at @main
+; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object at address 0x8.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/switch_poison.ll b/llvm/test/tools/llubi/switch_poison.ll
index 32316a467063a..946643ec02567 100644
--- a/llvm/test/tools/llubi/switch_poison.ll
+++ b/llvm/test/tools/llubi/switch_poison.ll
@@ -9,5 +9,8 @@ exit:
ret void
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 switch i32 poison, label %exit [
+; CHECK-NEXT: ] at @main
; CHECK-NEXT: Immediate UB detected: Switch on poison condition.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/unreachable.ll b/llvm/test/tools/llubi/unreachable.ll
index c24a02eb5eb86..bf98ec7361c4f 100644
--- a/llvm/test/tools/llubi/unreachable.ll
+++ b/llvm/test/tools/llubi/unreachable.ll
@@ -5,5 +5,7 @@ define void @main() {
unreachable
}
; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 unreachable at @main
; CHECK-NEXT: Immediate UB detected: Unreachable code.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index f1247cd6bf69e..2146bf19d208f 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -27,19 +27,29 @@ Frame::Frame(Function &F, CallBase *CallSite, Frame *LastFrame,
ValueMap[&Arg] = Args[Arg.getArgNo()];
}
-void ExecutorBase::reportImmediateUB(StringRef Msg) {
+DiagnosticReporter ExecutorBase::reportImmediateUB() {
+ return DiagnosticReporter(*this, DiagnosticKind::ImmediateUB);
+}
+
+DiagnosticReporter ExecutorBase::reportError() {
+ return DiagnosticReporter(*this, DiagnosticKind::Error);
+}
+
+void ExecutorBase::reportImmediateUBString(StringRef Msg) {
// Check if we have already reported an immediate UB.
if (hasProgramExited())
return;
+ dumpStackTrace();
requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
// TODO: Provide stack trace information.
Handler.onImmediateUB(Msg);
}
-void ExecutorBase::reportError(StringRef Msg) {
+void ExecutorBase::reportErrorString(StringRef Msg) {
// Check if we have already reported an error message.
if (hasProgramExited())
return;
+ dumpStackTrace();
requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
Handler.onError(Msg);
}
@@ -54,24 +64,37 @@ std::optional<uint64_t> ExecutorBase::verifyMemAccess(const MemoryObject &MO,
// undefined behavior.
if (IsStore ? MO.getState() != MemoryObjectState::Alive
: MO.getState() == MemoryObjectState::Freed) {
- reportImmediateUB("Try to access a dead memory object.");
+ reportImmediateUB() << "Try to access a dead memory object at address 0x"
+ << Twine::utohexstr(Address.getZExtValue()) << ".";
return std::nullopt;
}
if (Address.countr_zero() < Log2(Alignment)) {
- reportImmediateUB("Misaligned memory access.");
+ reportImmediateUB() << "Misaligned memory access. Address: 0x"
+ << Twine::utohexstr(Address.getZExtValue())
+ << ", Required alignment: " << Alignment.value() << ".";
return std::nullopt;
}
if (AccessSize > MO.getSize() || Address.ult(MO.getAddress())) {
- reportImmediateUB("Memory access is out of bounds.");
+ 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;
}
APInt Offset = Address - MO.getAddress();
if (Offset.ugt(MO.getSize() - AccessSize)) {
- reportImmediateUB("Memory access is out of bounds.");
+ 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;
}
@@ -80,14 +103,14 @@ std::optional<uint64_t> ExecutorBase::verifyMemAccess(const MemoryObject &MO,
AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy) {
if (Ptr.isPoison()) {
- reportImmediateUB("Invalid memory access with a poison pointer.");
+ reportImmediateUB() << "Invalid memory access with a poison pointer.";
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.");
+ reportImmediateUB()
+ << "Invalid memory access via a pointer with nullary provenance.";
return AnyValue::getPoisonValue(Ctx, ValTy);
}
// TODO: pointer capability check
@@ -107,14 +130,14 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy) {
void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
const AnyValue &Val, Type *ValTy) {
if (Ptr.isPoison()) {
- reportImmediateUB("Invalid memory access with a poison pointer.");
+ reportImmediateUB() << "Invalid memory access with a poison pointer.";
return;
}
auto &PtrVal = Ptr.asPointer();
auto *MO = PtrVal.getMemoryObject();
if (!MO) {
- reportImmediateUB(
- "Invalid memory access via a pointer with nullary provenance.");
+ reportImmediateUB()
+ << "Invalid memory access via a pointer with nullary provenance.";
return;
}
// TODO: pointer capability check
@@ -144,4 +167,19 @@ std::optional<ProgramExitInfo> ExecutorBase::getExitInfo() const {
unsigned ExecutorBase::getIntSize() const {
return CurrentFrame->TLI.getIntSize();
}
+
+void ExecutorBase::dumpStackTrace() const {
+ errs() << "Stacktrace:\n";
+ const Frame *Frm = CurrentFrame;
+ unsigned Index = 0;
+ while (Frm != nullptr) {
+ if (Frm->BB) {
+ Instruction &Inst = *Frm->PC;
+ errs() << "#" << Index++ << " " << Inst << " at ";
+ Inst.getFunction()->printAsOperand(errs(), /*PrintType=*/false);
+ errs() << "\n";
+ }
+ Frm = Frm->LastFrame;
+ }
+}
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index 0fa73d9294e07..00e399bd30f9d 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -15,7 +15,10 @@
#include "Context.h"
#include "Value.h"
+#include "llvm/Support/raw_ostream.h"
#include <optional>
+#include <string>
+#include <utility>
namespace llvm::ubi {
@@ -68,7 +71,16 @@ struct Frame {
const TargetLibraryInfoImpl &TLIImpl);
};
+enum class DiagnosticKind {
+ ImmediateUB,
+ Error,
+};
+
+class DiagnosticReporter;
+
class ExecutorBase {
+ friend class DiagnosticReporter;
+
protected:
Context &Ctx;
EventHandler &Handler;
@@ -79,9 +91,13 @@ class ExecutorBase {
: Ctx(C), Handler(H), ExitInfo(std::nullopt) {}
~ExecutorBase() = default;
+private:
+ void reportImmediateUBString(StringRef Msg);
+ void reportErrorString(StringRef Msg);
+
public:
- void reportImmediateUB(StringRef Msg);
- void reportError(StringRef Msg);
+ DiagnosticReporter reportImmediateUB();
+ DiagnosticReporter reportError();
/// Check if the upcoming memory access is valid. Returns the offset relative
/// to the underlying object if it is valid.
@@ -102,6 +118,35 @@ class ExecutorBase {
std::optional<ProgramExitInfo> getExitInfo() const;
unsigned getIntSize() const;
+
+ void dumpStackTrace() const;
+};
+
+class DiagnosticReporter {
+ ExecutorBase &Executor;
+ std::string Buf;
+ raw_string_ostream OS;
+ DiagnosticKind Kind;
+
+public:
+ DiagnosticReporter(ExecutorBase &E, DiagnosticKind K)
+ : Executor(E), OS(Buf), Kind(K) {}
+ ~DiagnosticReporter() {
+ OS.flush();
+ switch (Kind) {
+ case DiagnosticKind::ImmediateUB:
+ Executor.reportImmediateUBString(Buf);
+ break;
+ case DiagnosticKind::Error:
+ Executor.reportErrorString(Buf);
+ break;
+ }
+ }
+
+ template <typename T> DiagnosticReporter &operator<<(const T &Val) {
+ OS << Val;
+ return *this;
+ }
};
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index cc8b70b9875f6..a77a8488c5f33 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -378,7 +378,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
// a poison is found.
bool getBooleanNonPoison(BooleanKind Boolean) {
if (Boolean == BooleanKind::Poison)
- reportImmediateUB("Unexpected poison boolean value");
+ reportImmediateUB() << "Unexpected poison boolean value";
return Boolean == BooleanKind::True;
}
@@ -410,7 +410,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
jumpTo(BI, BI.getSuccessor(1));
return;
case BooleanKind::Poison:
- reportImmediateUB("Branch on poison condition.");
+ reportImmediateUB() << "Branch on poison condition.";
return;
}
}
@@ -418,7 +418,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void visitSwitchInst(SwitchInst &SI) {
auto &Cond = getValue(SI.getCondition());
if (Cond.isPoison()) {
- reportImmediateUB("Switch on poison condition.");
+ reportImmediateUB() << "Switch on poison condition.";
return;
}
for (auto &Case : SI.cases()) {
@@ -431,7 +431,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitUnreachableInst(UnreachableInst &) {
- reportImmediateUB("Unreachable code.");
+ reportImmediateUB() << "Unreachable code.";
}
void visitCallBrInst(CallBrInst &CI) {
@@ -447,7 +447,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void visitIndirectBrInst(IndirectBrInst &IBI) {
auto &Target = getValue(IBI.getAddress());
if (Target.isPoison()) {
- reportImmediateUB("Indirect branch on poison.");
+ reportImmediateUB() << "Indirect branch on poison.";
return;
}
if (BasicBlock *DestBB = Ctx.getTargetBlock(Target.asPointer())) {
@@ -455,11 +455,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
[DestBB](BasicBlock *Succ) { return Succ == DestBB; }))
jumpTo(IBI, DestBB);
else
- reportImmediateUB("Indirect branch on unlisted target BB.");
+ reportImmediateUB() << "Indirect branch on unlisted target BB.";
return;
}
- reportImmediateUB("Indirect branch on invalid target BB.");
+ reportImmediateUB() << "Indirect branch on invalid target BB.";
}
void returnFromCallee() {
@@ -488,7 +488,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
break;
case BooleanKind::False:
case BooleanKind::Poison:
- reportImmediateUB("Assume on false or poison condition.");
+ reportImmediateUB() << "Assume on false or poison condition.";
break;
}
// TODO: handle llvm.assume with operand bundles
@@ -941,17 +941,20 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
auto &CalleeVal = getValue(CalledOperand);
if (CalleeVal.isPoison()) {
- reportImmediateUB("Indirect call through poison function pointer.");
+ reportImmediateUB() << "Indirect call through poison function pointer.";
return;
}
Callee = Ctx.getTargetFunction(CalleeVal.asPointer());
if (!Callee) {
- reportImmediateUB("Indirect call through invalid function pointer.");
+ reportImmediateUB()
+ << "Indirect call through invalid function pointer.";
return;
}
if (Callee->getFunctionType() != CB.getFunctionType()) {
- reportImmediateUB("Indirect call through a function pointer with "
- "mismatched signature.");
+ reportImmediateUB() << "Indirect call through a function pointer with "
+ "mismatched signature. Expected: "
+ << *CB.getFunctionType()
+ << ", Actual: " << *Callee->getFunctionType();
return;
}
}
@@ -972,7 +975,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
} else {
uint32_t MaxStackDepth = Ctx.getMaxStackDepth();
if (MaxStackDepth && CallStack.size() >= MaxStackDepth) {
- reportError("Maximum stack depth exceeded.");
+ reportError() << "Maximum stack depth exceeded.";
return;
}
assert(!Callee->empty() && "Expected a defined function.");
@@ -1014,23 +1017,23 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
// Priority: Immediate UB > poison > normal value
if (RHS.isPoison()) {
- reportImmediateUB("Division by zero (refine RHS to 0).");
+ reportImmediateUB() << "Division by zero (refine RHS to 0).";
return AnyValue::poison();
}
const APInt &RHSVal = RHS.asInteger();
if (RHSVal.isZero()) {
- reportImmediateUB("Division by zero.");
+ reportImmediateUB() << "Division by zero.";
return AnyValue::poison();
}
if (LHS.isPoison()) {
if (RHSVal.isAllOnes())
- reportImmediateUB(
- "Signed division overflow (refine LHS to INT_MIN).");
+ reportImmediateUB()
+ << "Signed division overflow (refine LHS to INT_MIN).";
return AnyValue::poison();
}
const APInt &LHSVal = LHS.asInteger();
if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
- reportImmediateUB("Signed division overflow.");
+ reportImmediateUB() << "Signed division overflow.";
return AnyValue::poison();
}
@@ -1050,23 +1053,24 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
// Priority: Immediate UB > poison > normal value
if (RHS.isPoison()) {
- reportImmediateUB("Division by zero (refine RHS to 0).");
+ reportImmediateUB() << "Division by zero (refine RHS to 0).";
return AnyValue::poison();
}
const APInt &RHSVal = RHS.asInteger();
if (RHSVal.isZero()) {
- reportImmediateUB("Division by zero.");
+ reportImmediateUB() << "Division by zero.";
return AnyValue::poison();
}
if (LHS.isPoison()) {
if (RHSVal.isAllOnes())
- reportImmediateUB(
- "Signed division overflow (refine LHS to INT_MIN).");
+ reportImmediateUB()
+ << "Signed division overflow (refine LHS to INT_MIN).";
return AnyValue::poison();
}
const APInt &LHSVal = LHS.asInteger();
if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
- reportImmediateUB("Signed division overflow.");
+ reportImmediateUB() << "Signed division overflow. LHS: " << LHSVal
+ << ", RHS: " << RHSVal;
return AnyValue::poison();
}
@@ -1078,12 +1082,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
// Priority: Immediate UB > poison > normal value
if (RHS.isPoison()) {
- reportImmediateUB("Division by zero (refine RHS to 0).");
+ reportImmediateUB() << "Division by zero (refine RHS to 0).";
return AnyValue::poison();
}
const APInt &RHSVal = RHS.asInteger();
if (RHSVal.isZero()) {
- reportImmediateUB("Division by zero.");
+ reportImmediateUB() << "Division by zero.";
return AnyValue::poison();
}
if (LHS.isPoison())
@@ -1106,12 +1110,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
// Priority: Immediate UB > poison > normal value
if (RHS.isPoison()) {
- reportImmediateUB("Division by zero (refine RHS to 0).");
+ reportImmediateUB() << "Division by zero (refine RHS to 0).";
return AnyValue::poison();
}
const APInt &RHSVal = RHS.asInteger();
if (RHSVal.isZero()) {
- reportImmediateUB("Division by zero.");
+ reportImmediateUB() << "Division by zero.";
return AnyValue::poison();
}
if (LHS.isPoison())
@@ -1257,20 +1261,22 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
if (AI.isArrayAllocation()) {
auto &Size = getValue(AI.getArraySize());
if (Size.isPoison()) {
- reportImmediateUB("Alloca with poison array size.");
+ reportImmediateUB() << "Alloca with poison array size.";
return;
}
if (Size.asInteger().getActiveBits() > 64) {
- reportImmediateUB(
- "Alloca with large array size that overflows uint64_t.");
+ reportImmediateUB()
+ << "Alloca with large array size that overflows uint64_t. Size: "
+ << Size.asInteger();
return;
}
bool Overflowed = false;
AllocSize = SaturatingMultiply(AllocSize, Size.asInteger().getZExtValue(),
&Overflowed);
if (Overflowed) {
- reportImmediateUB(
- "Alloca with allocation size that overflows uint64_t.");
+ reportImmediateUB()
+ << "Alloca with allocation size that overflows uint64_t. Size: "
+ << Size.asInteger();
return;
}
}
@@ -1284,7 +1290,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
: MemInitKind::Uninitialized,
MemAllocKind::Stack);
if (!Obj) {
- reportError("Insufficient stack space.");
+ reportError() << "Insufficient stack space.";
return;
}
CurrentFrame->Allocas.push_back(Obj);
@@ -1502,7 +1508,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
assert(Top.State == FrameState::Running &&
"Expected to be in running state.");
if (MaxSteps != 0 && Steps >= MaxSteps) {
- reportError("Exceeded maximum number of execution steps.");
+ reportError() << "Exceeded maximum number of execution steps.";
break;
}
++Steps;
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index fb626e2ad7dd7..a1e58e4e57f47 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -31,9 +31,8 @@ Library::Library(Context &Ctx, EventHandler &Handler, const DataLayout &DL,
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.");
+ Executor.reportImmediateUB()
+ << "Invalid memory access via a pointer with nullary provenance.";
return std::nullopt;
}
@@ -49,8 +48,10 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
Byte B = (*MO)[*ValidOffset];
if (B.ConcreteMask != 0xFF) {
- Executor.reportImmediateUB("Read uninitialized or poison memory while "
- "parsing C-string.");
+ Executor.reportImmediateUB()
+ << "Read uninitialized or poison memory while "
+ "parsing C-string at offset "
+ << Offset << ".";
return std::nullopt;
}
@@ -83,7 +84,7 @@ AnyValue Library::executeMalloc(StringRef Name, Type *Type,
if (AllocKind == MemAllocKind::New || AllocKind == MemAllocKind::NewArray) {
// FIXME: As llubi doesn't support stack unwinding yet, we report an error
// when new/new[] fails.
- Executor.reportError("Insufficient heap space.");
+ Executor.reportError() << "Insufficient heap space.";
return AnyValue::poison();
}
return AnyValue::getNullValue(Ctx, Type);
@@ -129,24 +130,31 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
MemoryObject *Obj = Ptr.getMemoryObject();
if (!Obj) {
- Executor.reportImmediateUB("freeing a pointer with nullary provenance.");
+ Executor.reportImmediateUB()
+ << "freeing a pointer with nullary provenance.";
return AnyValue::poison();
}
if (const uint64_t Address = Ptr.address().getZExtValue();
Address != Obj->getAddress()) {
- Executor.reportImmediateUB(
- "freeing a pointer that does not point to the start of an allocation.");
+ Executor.reportImmediateUB()
+ << "freeing a pointer that does not point to "
+ "the start of an allocation. Pointer address: 0x"
+ << Twine::utohexstr(Address) << ", allocation base: 0x"
+ << Twine::utohexstr(Obj->getAddress()) << ".";
return AnyValue::poison();
}
if (Obj->getState() == MemoryObjectState::Freed) {
- Executor.reportImmediateUB("double-freeing a memory object.");
+ Executor.reportImmediateUB()
+ << "double-freeing a memory object allocated at 0x"
+ << Twine::utohexstr(Obj->getAddress()) << ".";
return AnyValue::poison();
}
if (!Obj->isHeapAllocated()) {
- Executor.reportImmediateUB("freeing a non-heap allocation.");
+ Executor.reportImmediateUB() << "freeing a non-heap allocation at 0x"
+ << Twine::utohexstr(Obj->getAddress()) << ".";
return AnyValue::poison();
}
@@ -155,7 +163,9 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
// function comes from a different family (C++ delete, etc.)
if (!Ctx.free(*Obj)) {
- Executor.reportImmediateUB("freeing an invalid pointer.");
+ Executor.reportImmediateUB()
+ << "freeing an invalid pointer at 0x"
+ << Twine::utohexstr(Ptr.address().getZExtValue()) << ".";
return AnyValue::poison();
}
@@ -206,9 +216,8 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
++I;
if (I >= FormatStr.size()) {
- Executor.reportImmediateUB(
- "Invalid format string in printf: missing conversion "
- "specifier.");
+ Executor.reportImmediateUB()
+ << "Invalid format string in printf: missing conversion specifier.";
return AnyValue::poison();
}
@@ -220,14 +229,17 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
CleanChunk.end());
if (ArgIndex >= Args.size()) {
- Executor.reportImmediateUB(
- "Not enough arguments provided for the format string.");
+ Executor.reportImmediateUB() << "Not enough arguments provided for the "
+ "format string. Required argument for '"
+ << Specifier << "'.";
return AnyValue::poison();
}
const auto &Arg = Args[ArgIndex++];
if (Arg.isPoison()) {
- Executor.reportImmediateUB("Poison argument passed to printf.");
+ Executor.reportImmediateUB()
+ << "Poison argument passed to printf for format specifier '"
+ << Specifier << "' at argument index " << ArgIndex << ".";
return AnyValue::poison();
}
@@ -290,8 +302,9 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
break;
}
default:
- Executor.reportImmediateUB(
- "Unknown or unsupported format specifier in printf.");
+ Executor.reportImmediateUB()
+ << "Unknown or unsupported format specifier '" << Specifier
+ << "' in printf.";
return AnyValue::poison();
}
}
@@ -322,11 +335,15 @@ AnyValue Library::executeTerminate() {
std::optional<AnyValue> Library::executeLibcall(LibFunc LF, StringRef Name,
Type *Type,
ArrayRef<AnyValue> Args) {
+ unsigned Index = 0;
for (const AnyValue &Arg : Args) {
if (Arg.isPoison()) {
- Executor.reportImmediateUB("Poison argument passed to a library call.");
+ Executor.reportImmediateUB()
+ << "Poison argument passed to a library call at argument index "
+ << Index << ".";
return AnyValue::poison();
}
+ ++Index;
}
switch (LF) {
>From d9a9f5e89a1473f5907a780aa018549e4662958e Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Sat, 2 May 2026 21:19:34 +0800
Subject: [PATCH 2/3] [llubi] Remove finished TODO
---
llvm/tools/llubi/lib/ExecutorBase.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 2146bf19d208f..9cf1297aa518f 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -41,7 +41,6 @@ void ExecutorBase::reportImmediateUBString(StringRef Msg) {
return;
dumpStackTrace();
requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
- // TODO: Provide stack trace information.
Handler.onImmediateUB(Msg);
}
>From 823eb23f7f3043aecaccd375a41c259ce260e06f Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Sat, 2 May 2026 21:48:06 +0800
Subject: [PATCH 3/3] [llubi] Minor fixes
---
llvm/tools/llubi/lib/ExecutorBase.cpp | 10 +++++-----
llvm/tools/llubi/lib/ExecutorBase.h | 8 +++++++-
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 9cf1297aa518f..233497e041b00 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -169,16 +169,16 @@ unsigned ExecutorBase::getIntSize() const {
void ExecutorBase::dumpStackTrace() const {
errs() << "Stacktrace:\n";
- const Frame *Frm = CurrentFrame;
+ const Frame *TheFrame = CurrentFrame;
unsigned Index = 0;
- while (Frm != nullptr) {
- if (Frm->BB) {
- Instruction &Inst = *Frm->PC;
+ while (TheFrame != nullptr) {
+ if (TheFrame->BB) {
+ Instruction &Inst = *TheFrame->PC;
errs() << "#" << Index++ << " " << Inst << " at ";
Inst.getFunction()->printAsOperand(errs(), /*PrintType=*/false);
errs() << "\n";
}
- Frm = Frm->LastFrame;
+ TheFrame = TheFrame->LastFrame;
}
}
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index 00e399bd30f9d..64933929fdc35 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -131,8 +131,14 @@ class DiagnosticReporter {
public:
DiagnosticReporter(ExecutorBase &E, DiagnosticKind K)
: Executor(E), OS(Buf), Kind(K) {}
+
+ DiagnosticReporter(const DiagnosticReporter &) = delete;
+ DiagnosticReporter(DiagnosticReporter &&) noexcept = delete;
+
+ DiagnosticReporter &operator=(const DiagnosticReporter &) = delete;
+ DiagnosticReporter &operator=(DiagnosticReporter &&) noexcept = delete;
+
~DiagnosticReporter() {
- OS.flush();
switch (Kind) {
case DiagnosticKind::ImmediateUB:
Executor.reportImmediateUBString(Buf);
More information about the llvm-commits
mailing list