[safestack] Rewrite isAllocaSafe using SCEV.

Use ScalarEvolution to calculate memory access bounds.
Handle function calls based on readnone/nocapture attributes.
Handle memory intrinsics with constant size.

This change improves both recall and precision of IsAllocaSafe.
See the new tests (ex. BitCastWide) for the kind of code that was wrongly
classified as safe.

SCEV efficiency seems to be limited by the fact the SafeStack runs late
(in CodeGenPrepare), and many loops are unrolled or otherwise not in LCSSA.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@253083 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Evgeniy Stepanov
2015-11-13 21:21:42 +00:00
parent 6dc74bec88
commit e528e5711e
5 changed files with 430 additions and 112 deletions
+159 -1
View File
@@ -6,10 +6,11 @@
; no arrays / no nested arrays
; Requires no protector.
; CHECK-LABEL: @foo(
define void @foo(i8* %a) nounwind uwtable safestack {
entry:
; CHECK-LABEL: define void @foo(
; CHECK-NOT: __safestack_unsafe_stack_ptr
; CHECK: ret void
%a.addr = alloca i8*, align 8
store i8* %a, i8** %a.addr, align 8
%0 = load i8*, i8** %a.addr, align 8
@@ -18,3 +19,160 @@ entry:
}
declare i32 @printf(i8*, ...)
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
define void @call_memset(i64 %len) safestack {
entry:
; CHECK-LABEL: define void @call_memset
; CHECK: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
call void @llvm.memset.p0i8.i64(i8* %arraydecay, i8 1, i64 %len, i32 1, i1 false)
ret void
}
define void @call_constant_memset() safestack {
entry:
; CHECK-LABEL: define void @call_constant_memset
; CHECK-NOT: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 2
call void @llvm.memset.p0i8.i64(i8* %arraydecay, i8 1, i64 7, i32 1, i1 false)
ret void
}
define void @call_constant_overflow_memset() safestack {
entry:
; CHECK-LABEL: define void @call_constant_overflow_memset
; CHECK: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 7
call void @llvm.memset.p0i8.i64(i8* %arraydecay, i8 1, i64 5, i32 1, i1 false)
ret void
}
define void @call_constant_underflow_memset() safestack {
entry:
; CHECK-LABEL: define void @call_constant_underflow_memset
; CHECK: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr [10 x i8], [10 x i8]* %q, i32 0, i32 -1
call void @llvm.memset.p0i8.i64(i8* %arraydecay, i8 1, i64 3, i32 1, i1 false)
ret void
}
; Readnone nocapture -> safe
define void @call_readnone(i64 %len) safestack {
entry:
; CHECK-LABEL: define void @call_readnone
; CHECK-NOT: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
call void @readnone(i8* %arraydecay)
ret void
}
; Arg0 is readnone, arg1 is not. Pass alloca ptr as arg0 -> safe
define void @call_readnone0_0(i64 %len) safestack {
entry:
; CHECK-LABEL: define void @call_readnone0_0
; CHECK-NOT: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
call void @readnone0(i8* %arraydecay, i8* zeroinitializer)
ret void
}
; Arg0 is readnone, arg1 is not. Pass alloca ptr as arg1 -> unsafe
define void @call_readnone0_1(i64 %len) safestack {
entry:
; CHECK-LABEL: define void @call_readnone0_1
; CHECK: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
call void @readnone0(i8 *zeroinitializer, i8* %arraydecay)
ret void
}
; Readonly nocapture -> unsafe
define void @call_readonly(i64 %len) safestack {
entry:
; CHECK-LABEL: define void @call_readonly
; CHECK: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
call void @readonly(i8* %arraydecay)
ret void
}
; Readonly nocapture -> unsafe
define void @call_arg_readonly(i64 %len) safestack {
entry:
; CHECK-LABEL: define void @call_arg_readonly
; CHECK: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
call void @arg_readonly(i8* %arraydecay)
ret void
}
; Readwrite nocapture -> unsafe
define void @call_readwrite(i64 %len) safestack {
entry:
; CHECK-LABEL: define void @call_readwrite
; CHECK: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
call void @readwrite(i8* %arraydecay)
ret void
}
; Captures the argument -> unsafe
define void @call_capture(i64 %len) safestack {
entry:
; CHECK-LABEL: define void @call_capture
; CHECK: @__safestack_unsafe_stack_ptr
; CHECK: ret void
%q = alloca [10 x i8], align 1
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
call void @capture(i8* %arraydecay)
ret void
}
; Lifetime intrinsics are always safe.
define void @call_lifetime(i32* %p) {
; CHECK-LABEL: define void @call_lifetime
; CHECK-NOT: @__safestack_unsafe_stack_ptr
; CHECK: ret void
entry:
%q = alloca [100 x i8], align 16
%0 = bitcast [100 x i8]* %q to i8*
call void @llvm.lifetime.start(i64 100, i8* %0)
call void @llvm.lifetime.end(i64 100, i8* %0)
ret void
}
declare void @readonly(i8* nocapture) readonly
declare void @arg_readonly(i8* readonly nocapture)
declare void @readwrite(i8* nocapture)
declare void @capture(i8* readnone) readnone
declare void @readnone(i8* nocapture) readnone
declare void @readnone0(i8* nocapture readnone, i8* nocapture)
declare void @llvm.memset.p0i8.i64(i8* nocapture, i8, i64, i32, i1) nounwind argmemonly
declare void @llvm.lifetime.start(i64, i8* nocapture) nounwind argmemonly
declare void @llvm.lifetime.end(i64, i8* nocapture) nounwind argmemonly
+25 -3
View File
@@ -4,14 +4,36 @@
@.str = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1
; PtrToInt/IntToPtr Cast
; Requires no protector.
; CHECK-LABEL: @foo(
define void @foo() nounwind uwtable safestack {
define void @IntToPtr() nounwind uwtable safestack {
entry:
; CHECK-LABEL: @IntToPtr(
; CHECK-NOT: __safestack_unsafe_stack_ptr
; CHECK: ret void
%a = alloca i32, align 4
%0 = ptrtoint i32* %a to i64
%1 = inttoptr i64 %0 to i32*
ret void
}
define i8 @BitCastNarrow() nounwind uwtable safestack {
entry:
; CHECK-LABEL: @BitCastNarrow(
; CHECK-NOT: __safestack_unsafe_stack_ptr
; CHECK: ret i8
%a = alloca i32, align 4
%0 = bitcast i32* %a to i8*
%1 = load i8, i8* %0, align 1
ret i8 %1
}
define i64 @BitCastWide() nounwind uwtable safestack {
entry:
; CHECK-LABEL: @BitCastWide(
; CHECK: __safestack_unsafe_stack_ptr
; CHECK: ret i64
%a = alloca i32, align 4
%0 = bitcast i32* %a to i64*
%1 = load i64, i64* %0, align 1
ret i64 %1
}
+17
View File
@@ -0,0 +1,17 @@
; RUN: opt -safe-stack -S -mtriple=i386-pc-linux-gnu < %s -o - | FileCheck %s
; RUN: opt -safe-stack -S -mtriple=x86_64-pc-linux-gnu < %s -o - | FileCheck %s
@.str = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1
; Returns an alloca address.
; Requires protector.
define i64 @foo() nounwind readnone safestack {
entry:
; CHECK-LABEL: define i64 @foo(
; CHECK: __safestack_unsafe_stack_ptr
; CHECK: ret i64
%x = alloca [100 x i32], align 16
%0 = ptrtoint [100 x i32]* %x to i64
ret i64 %0
}
+63
View File
@@ -0,0 +1,63 @@
; RUN: opt -safe-stack -S -mtriple=i386-pc-linux-gnu < %s -o - | FileCheck %s
; RUN: opt -safe-stack -S -mtriple=x86_64-pc-linux-gnu < %s -o - | FileCheck %s
@.str = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1
define void @bad_store() nounwind uwtable safestack {
entry:
; CHECK-LABEL: @bad_store(
; CHECK: __safestack_unsafe_stack_ptr
; CHECK: ret void
%a = alloca i32, align 4
%0 = ptrtoint i32* %a to i64
%1 = inttoptr i64 %0 to i64*
store i64 zeroinitializer, i64* %1
ret void
}
define void @good_store() nounwind uwtable safestack {
entry:
; CHECK-LABEL: @good_store(
; CHECK-NOT: __safestack_unsafe_stack_ptr
; CHECK: ret void
%a = alloca i32, align 4
%0 = bitcast i32* %a to i8*
store i8 zeroinitializer, i8* %0
ret void
}
define void @overflow_gep_store() nounwind uwtable safestack {
entry:
; CHECK-LABEL: @overflow_gep_store(
; CHECK: __safestack_unsafe_stack_ptr
; CHECK: ret void
%a = alloca i32, align 4
%0 = bitcast i32* %a to i8*
%1 = getelementptr i8, i8* %0, i32 4
store i8 zeroinitializer, i8* %1
ret void
}
define void @underflow_gep_store() nounwind uwtable safestack {
entry:
; CHECK-LABEL: @underflow_gep_store(
; CHECK: __safestack_unsafe_stack_ptr
; CHECK: ret void
%a = alloca i32, align 4
%0 = bitcast i32* %a to i8*
%1 = getelementptr i8, i8* %0, i32 -1
store i8 zeroinitializer, i8* %1
ret void
}
define void @good_gep_store() nounwind uwtable safestack {
entry:
; CHECK-LABEL: @good_gep_store(
; CHECK-NOT: __safestack_unsafe_stack_ptr
; CHECK: ret void
%a = alloca i32, align 4
%0 = bitcast i32* %a to i8*
%1 = getelementptr i8, i8* %0, i32 3
store i8 zeroinitializer, i8* %1
ret void
}