Currently, when inferring noundef, we only check that the return value is not undef/poison. However, we fail to account for the possibility that a poison-generating return attribute will convert the value to poison, and then violate the noundef attribute, resulting in immediate UB. For the relevant return attributes (align, nonnull and range), check whether we can trivially re-prove the relevant property, otherwise do not infer noundef. This fixes the FunctionAttrs side of https://github.com/llvm/llvm-project/issues/88026.
217 lines
6.2 KiB
LLVM
217 lines
6.2 KiB
LLVM
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 4
|
|
; RUN: opt < %s -passes='function-attrs' -S | FileCheck %s
|
|
|
|
@g_var = external global [0 x i8]
|
|
|
|
define i32 @test_ret_constant() {
|
|
; CHECK-LABEL: define noundef i32 @test_ret_constant(
|
|
; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
|
|
; CHECK-NEXT: ret i32 0
|
|
;
|
|
ret i32 0
|
|
}
|
|
|
|
define i32 @test_ret_poison() {
|
|
; CHECK-LABEL: define i32 @test_ret_poison(
|
|
; CHECK-SAME: ) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret i32 poison
|
|
;
|
|
ret i32 poison
|
|
}
|
|
|
|
define i32 @test_ret_undef() {
|
|
; CHECK-LABEL: define i32 @test_ret_undef(
|
|
; CHECK-SAME: ) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret i32 undef
|
|
;
|
|
ret i32 undef
|
|
}
|
|
|
|
define i32 @test_ret_param(i32 %x) {
|
|
; CHECK-LABEL: define i32 @test_ret_param(
|
|
; CHECK-SAME: i32 returned [[X:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret i32 [[X]]
|
|
;
|
|
ret i32 %x
|
|
}
|
|
|
|
define i32 @test_ret_noundef_param(i32 noundef %x) {
|
|
; CHECK-LABEL: define noundef i32 @test_ret_noundef_param(
|
|
; CHECK-SAME: i32 noundef returned [[X:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret i32 [[X]]
|
|
;
|
|
ret i32 %x
|
|
}
|
|
|
|
define i32 @test_ret_noundef_expr(i32 noundef %x) {
|
|
; CHECK-LABEL: define noundef i32 @test_ret_noundef_expr(
|
|
; CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: [[Y:%.*]] = add i32 [[X]], 1
|
|
; CHECK-NEXT: ret i32 [[Y]]
|
|
;
|
|
%y = add i32 %x, 1
|
|
ret i32 %y
|
|
}
|
|
|
|
define i32 @test_ret_create_poison_expr(i32 noundef %x) {
|
|
; CHECK-LABEL: define i32 @test_ret_create_poison_expr(
|
|
; CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: [[Y:%.*]] = add nsw i32 [[X]], 1
|
|
; CHECK-NEXT: ret i32 [[Y]]
|
|
;
|
|
%y = add nsw i32 %x, 1
|
|
ret i32 %y
|
|
}
|
|
|
|
define i32 @test_ret_freezed(i32 noundef %x) {
|
|
; CHECK-LABEL: define noundef i32 @test_ret_freezed(
|
|
; CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: [[Y:%.*]] = add nsw i32 [[X]], 1
|
|
; CHECK-NEXT: [[Z:%.*]] = freeze i32 [[Y]]
|
|
; CHECK-NEXT: ret i32 [[Z]]
|
|
;
|
|
%y = add nsw i32 %x, 1
|
|
%z = freeze i32 %y
|
|
ret i32 %z
|
|
}
|
|
|
|
define i32 @test_ret_control_flow(i32 noundef %x) {
|
|
; CHECK-LABEL: define noundef i32 @test_ret_control_flow(
|
|
; CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[X]], 0
|
|
; CHECK-NEXT: br i1 [[COND]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
|
|
; CHECK: if.then:
|
|
; CHECK-NEXT: ret i32 2
|
|
; CHECK: if.else:
|
|
; CHECK-NEXT: [[RET:%.*]] = add i32 [[X]], 1
|
|
; CHECK-NEXT: ret i32 [[RET]]
|
|
;
|
|
%cond = icmp eq i32 %x, 0
|
|
br i1 %cond, label %if.then, label %if.else
|
|
if.then:
|
|
ret i32 2
|
|
if.else:
|
|
%ret = add i32 %x, 1
|
|
ret i32 %ret
|
|
}
|
|
|
|
define i32 @test_ret_control_flow_may_poison(i32 noundef %x) {
|
|
; CHECK-LABEL: define i32 @test_ret_control_flow_may_poison(
|
|
; CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[X]], 0
|
|
; CHECK-NEXT: br i1 [[COND]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
|
|
; CHECK: if.then:
|
|
; CHECK-NEXT: ret i32 2
|
|
; CHECK: if.else:
|
|
; CHECK-NEXT: [[RET:%.*]] = add nsw i32 [[X]], 1
|
|
; CHECK-NEXT: ret i32 [[RET]]
|
|
;
|
|
%cond = icmp eq i32 %x, 0
|
|
br i1 %cond, label %if.then, label %if.else
|
|
if.then:
|
|
ret i32 2
|
|
if.else:
|
|
%ret = add nsw i32 %x, 1
|
|
ret i32 %ret
|
|
}
|
|
|
|
; TODO: use context-sensitive analysis
|
|
define i32 @test_ret_control_flow_never_poison(i32 noundef %x) {
|
|
; CHECK-LABEL: define i32 @test_ret_control_flow_never_poison(
|
|
; CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[X]], 2147483647
|
|
; CHECK-NEXT: br i1 [[COND]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
|
|
; CHECK: if.then:
|
|
; CHECK-NEXT: ret i32 2
|
|
; CHECK: if.else:
|
|
; CHECK-NEXT: [[RET:%.*]] = add nsw i32 [[X]], 1
|
|
; CHECK-NEXT: ret i32 [[RET]]
|
|
;
|
|
%cond = icmp eq i32 %x, 2147483647
|
|
br i1 %cond, label %if.then, label %if.else
|
|
if.then:
|
|
ret i32 2
|
|
if.else:
|
|
%ret = add nsw i32 %x, 1
|
|
ret i32 %ret
|
|
}
|
|
|
|
define i32 @test_noundef_prop() {
|
|
; CHECK-LABEL: define noundef i32 @test_noundef_prop(
|
|
; CHECK-SAME: ) #[[ATTR0]] {
|
|
; CHECK-NEXT: [[RET:%.*]] = call i32 @test_ret_constant()
|
|
; CHECK-NEXT: ret i32 [[RET]]
|
|
;
|
|
%ret = call i32 @test_ret_constant()
|
|
ret i32 %ret
|
|
}
|
|
|
|
; Don't deduce noundef for functions with sanitize_memory.
|
|
define i32 @test_ret_constant_msan() sanitize_memory {
|
|
; CHECK-LABEL: define i32 @test_ret_constant_msan(
|
|
; CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
|
|
; CHECK-NEXT: ret i32 0
|
|
;
|
|
ret i32 0
|
|
}
|
|
|
|
define i64 @test_trunc_with_constexpr() {
|
|
; CHECK-LABEL: define noundef i64 @test_trunc_with_constexpr(
|
|
; CHECK-SAME: ) #[[ATTR0]] {
|
|
; CHECK-NEXT: [[ADD:%.*]] = add i32 trunc (i64 sub (i64 0, i64 ptrtoint (ptr @g_var to i64)) to i32), 1
|
|
; CHECK-NEXT: [[CONV:%.*]] = sext i32 [[ADD]] to i64
|
|
; CHECK-NEXT: ret i64 [[CONV]]
|
|
;
|
|
%add = add i32 trunc (i64 sub (i64 0, i64 ptrtoint (ptr @g_var to i64)) to i32), 1
|
|
%conv = sext i32 %add to i64
|
|
ret i64 %conv
|
|
}
|
|
|
|
define align 4 ptr @maybe_not_aligned(ptr noundef %p) {
|
|
; CHECK-LABEL: define align 4 ptr @maybe_not_aligned(
|
|
; CHECK-SAME: ptr noundef readnone returned [[P:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret ptr [[P]]
|
|
;
|
|
ret ptr %p
|
|
}
|
|
|
|
define align 4 ptr @definitely_aligned(ptr noundef align 4 %p) {
|
|
; CHECK-LABEL: define noundef align 4 ptr @definitely_aligned(
|
|
; CHECK-SAME: ptr noundef readnone returned align 4 [[P:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret ptr [[P]]
|
|
;
|
|
ret ptr %p
|
|
}
|
|
|
|
define nonnull ptr @maybe_not_nonnull(ptr noundef %p) {
|
|
; CHECK-LABEL: define nonnull ptr @maybe_not_nonnull(
|
|
; CHECK-SAME: ptr noundef readnone returned [[P:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret ptr [[P]]
|
|
;
|
|
ret ptr %p
|
|
}
|
|
|
|
define nonnull ptr @definitely_nonnull(ptr noundef nonnull %p) {
|
|
; CHECK-LABEL: define noundef nonnull ptr @definitely_nonnull(
|
|
; CHECK-SAME: ptr noundef nonnull readnone returned [[P:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret ptr [[P]]
|
|
;
|
|
ret ptr %p
|
|
}
|
|
|
|
define range(i8 0, 10) i8 @maybe_not_in_range(i8 noundef %v) {
|
|
; CHECK-LABEL: define range(i8 0, 10) i8 @maybe_not_in_range(
|
|
; CHECK-SAME: i8 noundef returned [[V:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret i8 [[V]]
|
|
;
|
|
ret i8 %v
|
|
}
|
|
|
|
define range(i8 0, 10) i8 @definitely_in_range(i8 noundef range(i8 0, 10) %v) {
|
|
; CHECK-LABEL: define noundef range(i8 0, 10) i8 @definitely_in_range(
|
|
; CHECK-SAME: i8 noundef returned range(i8 0, 10) [[V:%.*]]) #[[ATTR0]] {
|
|
; CHECK-NEXT: ret i8 [[V]]
|
|
;
|
|
ret i8 %v
|
|
}
|