diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp --- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp +++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp @@ -2814,6 +2814,26 @@ } assert(FreeInstrBB->size() == 1 && "Only the branch instruction should remain"); + + // Now that we've moved the call to free before the NULL check, we have to + // remove any attributes on its parameter that imply it's non-null, because + // those attributes might have only been valid because of the NULL check, and + // we can get miscompiles if we keep them. This is conservative if non-null is + // also implied by something other than the NULL check, but it's guaranteed to + // be correct, and the conservativeness won't matter in practice, since the + // attributes are irrelevant for the call to free itself and the pointer + // shouldn't be used after the call. + AttributeList Attrs = FI.getAttributes(); + Attrs = Attrs.removeParamAttribute(FI.getContext(), 0, Attribute::NonNull); + Attribute Dereferenceable = Attrs.getParamAttr(0, Attribute::Dereferenceable); + if (Dereferenceable.isValid()) { + uint64_t Bytes = Dereferenceable.getDereferenceableBytes(); + Attrs = Attrs.removeParamAttribute(FI.getContext(), 0, + Attribute::Dereferenceable); + Attrs = Attrs.addDereferenceableOrNullParamAttr(FI.getContext(), 0, Bytes); + } + FI.setAttributes(Attrs); + return &FI; } diff --git a/llvm/test/Transforms/InstCombine/malloc-free.ll b/llvm/test/Transforms/InstCombine/malloc-free.ll --- a/llvm/test/Transforms/InstCombine/malloc-free.ll +++ b/llvm/test/Transforms/InstCombine/malloc-free.ll @@ -170,6 +170,78 @@ ret void } +;; Test that nonnull-implying attributes on the parameter are adjusted when the +;; call is moved, since they may no longer be valid and result in miscompiles if +;; kept unchanged. +define void @test_nonnull_free_move(i8* %foo) minsize { +; CHECK-LABEL: @test_nonnull_free_move( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[TOBOOL:%.*]] = icmp eq i8* [[FOO:%.*]], null +; CHECK-NEXT: tail call void @free(i8* [[FOO]]) +; CHECK-NEXT: br i1 [[TOBOOL]], label [[IF_END:%.*]], label [[IF_THEN:%.*]] +; CHECK: if.then: +; CHECK-NEXT: br label [[IF_END]] +; CHECK: if.end: +; CHECK-NEXT: ret void +; +entry: + %tobool = icmp eq i8* %foo, null + br i1 %tobool, label %if.end, label %if.then + +if.then: ; preds = %entry + tail call void @free(i8* nonnull %foo) + br label %if.end + +if.end: ; preds = %entry, %if.then + ret void +} + +define void @test_dereferenceable_free_move(i8* %foo) minsize { +; CHECK-LABEL: @test_dereferenceable_free_move( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[TOBOOL:%.*]] = icmp eq i8* [[FOO:%.*]], null +; CHECK-NEXT: tail call void @free(i8* dereferenceable_or_null(4) [[FOO]]) +; CHECK-NEXT: br i1 [[TOBOOL]], label [[IF_END:%.*]], label [[IF_THEN:%.*]] +; CHECK: if.then: +; CHECK-NEXT: br label [[IF_END]] +; CHECK: if.end: +; CHECK-NEXT: ret void +; +entry: + %tobool = icmp eq i8* %foo, null + br i1 %tobool, label %if.end, label %if.then + +if.then: ; preds = %entry + tail call void @free(i8* dereferenceable(4) %foo) + br label %if.end + +if.end: ; preds = %entry, %if.then + ret void +} + +define void @test_nonnull_dereferenceable_free_move(i8* %foo) minsize { +; CHECK-LABEL: @test_nonnull_dereferenceable_free_move( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[TOBOOL:%.*]] = icmp eq i8* [[FOO:%.*]], null +; CHECK-NEXT: tail call void @free(i8* dereferenceable_or_null(16) [[FOO]]) +; CHECK-NEXT: br i1 [[TOBOOL]], label [[IF_END:%.*]], label [[IF_THEN:%.*]] +; CHECK: if.then: +; CHECK-NEXT: br label [[IF_END]] +; CHECK: if.end: +; CHECK-NEXT: ret void +; +entry: + %tobool = icmp eq i8* %foo, null + br i1 %tobool, label %if.end, label %if.then + +if.then: ; preds = %entry + tail call void @free(i8* nonnull dereferenceable(16) %foo) + br label %if.end + +if.end: ; preds = %entry, %if.then + ret void +} + ; The next four tests cover the semantics of the nofree attributes. These ; are thought to be legal transforms, but an implementation thereof has ; been reverted once due to difficult to isolate fallout.