diff --git a/llvm/lib/Analysis/StackSafetyAnalysis.cpp b/llvm/lib/Analysis/StackSafetyAnalysis.cpp --- a/llvm/lib/Analysis/StackSafetyAnalysis.cpp +++ b/llvm/lib/Analysis/StackSafetyAnalysis.cpp @@ -413,6 +413,11 @@ } const auto &CB = cast(*I); + if (CB.getReturnedArgOperand() == V) { + if (Visited.insert(I).second) + WorkList.push_back(cast(I)); + } + if (!CB.isArgOperand(&UI)) { US.addRange(I, UnknownRange); break; diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp --- a/llvm/lib/Analysis/ValueTracking.cpp +++ b/llvm/lib/Analysis/ValueTracking.cpp @@ -4533,6 +4533,12 @@ if (OffsetZero && !GEP->hasAllZeroIndices()) return nullptr; AddWork(GEP->getPointerOperand()); + } else if (CallBase *CB = dyn_cast(V)) { + Value *Returned = CB->getReturnedArgOperand(); + if (Returned) + AddWork(Returned); + else + return nullptr; } else { return nullptr; } diff --git a/llvm/test/Analysis/StackSafetyAnalysis/local.ll b/llvm/test/Analysis/StackSafetyAnalysis/local.ll --- a/llvm/test/Analysis/StackSafetyAnalysis/local.ll +++ b/llvm/test/Analysis/StackSafetyAnalysis/local.ll @@ -11,6 +11,8 @@ declare void @llvm.memmove.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 %isvolatile) declare void @llvm.memset.p0i8.i64(i8* %dest, i8 %val, i64 %len, i1 %isvolatile) +declare i8* @retptr(i8* returned) + ; Address leaked. define void @LeakAddress() { ; CHECK-LABEL: @LeakAddress dso_preemptable{{$}} @@ -88,6 +90,23 @@ ret void } +define void @StoreInBounds6() { +; CHECK-LABEL: @StoreInBounds6 dso_preemptable{{$}} +; CHECK-NEXT: args uses: +; CHECK-NEXT: allocas uses: +; GLOBAL-NEXT: x[4]: full-set, @retptr(arg0, [0,1)){{$}} +; LOCAL-NEXT: x[4]: [0,1), @retptr(arg0, [0,1)){{$}} +; GLOBAL-NEXT: safe accesses: +; GLOBAL-NEXT: store i8 0, i8* %x2, align 1 +; CHECK-EMPTY: +entry: + %x = alloca i32, align 4 + %x1 = bitcast i32* %x to i8* + %x2 = call i8* @retptr(i8* %x1) + store i8 0, i8* %x2, align 1 + ret void +} + define dso_local void @WriteMinMax(i8* %p) { ; CHECK-LABEL: @WriteMinMax{{$}} ; CHECK-NEXT: args uses: @@ -133,6 +152,24 @@ ret void } +define void @StoreOutOfBounds2() { +; CHECK-LABEL: @StoreOutOfBounds2 dso_preemptable{{$}} +; CHECK-NEXT: args uses: +; CHECK-NEXT: allocas uses: +; GLOBAL-NEXT: x[4]: full-set, @retptr(arg0, [2,3)){{$}} +; LOCAL-NEXT: x[4]: [2,6), @retptr(arg0, [2,3)){{$}} +; GLOBAL-NEXT: safe accesses: +; CHECK-EMPTY: +entry: + %x = alloca i32, align 4 + %x1 = bitcast i32* %x to i8* + %x2 = getelementptr i8, i8* %x1, i64 2 + %x3 = call i8* @retptr(i8* %x2) + %x4 = bitcast i8* %x3 to i32* + store i32 0, i32* %x4, align 1 + ret void +} + ; There is no difference in load vs store handling. define void @LoadInBounds() { ; CHECK-LABEL: @LoadInBounds dso_preemptable{{$}} diff --git a/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll b/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll --- a/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll +++ b/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll @@ -155,6 +155,24 @@ ret i32 0 } +; Check whether we see through the returns attribute of functions. +define i32 @test_retptr(i32* %a) sanitize_hwaddress { +entry: + ; CHECK-LABEL: @test_retptr + ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store + ; SAFETY: call {{.*}}__hwasan_generate_tag + ; SAFETY-NOT: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK-NOT: call {{.*}}__hwasan_store + %buf.sroa.0 = alloca i8, align 4 + call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %buf.sroa.0) + %ptr = call i8* @retptr(i8* %buf.sroa.0) + store volatile i8 0, i8* %ptr, align 4, !tbaa !8 + call void @llvm.lifetime.end.p0i8(i64 1, i8* nonnull %buf.sroa.0) + ret i32 0 +} + ; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) @@ -165,6 +183,7 @@ declare void @use(i8* nocapture) declare i32 @getoffset() declare i8* @getptr(i8* nocapture) +declare i8* @retptr(i8* returned) !8 = !{!9, !9, i64 0} !9 = !{!"omnipotent char", !10, i64 0}