lebedev.ri created this revision. lebedev.ri added reviewers: vsk, rsmith, rjmccall, Sanitizers. lebedev.ri added a project: Sanitizers.
C and C++ are interesting languages. They are statically typed, but weakly. The implicit conversions are allowed. This is nice, allows to write code while balancing between getting drowned in everything being convertible, and nothing being convertible. As usual, this comes with a price: void consume(unsigned int val); void test(int val) { consume(val); // The 'val' is `signed int`, but `consume()` takes `unsigned int`. // If val is negative, then consume() will be operating on a large // unsigned value, and you may or may not have a bug. // But yes, sometimes this is intentional. // Making the conversion explicit silences the sanitizer. consume((unsigned int)val); } Yes, there is a `-Wsign-conversion`` diagnostic group, but first, it is kinda noisy, since it warns on everything (unlike sanitizers, warning on an actual issues), and second, likely there are cases where it does **not** warn. The actual detection is pretty easy. We just need to check each of the values whether it is negative, and equality-compare the results of those comparisons. The unsigned value is obviously non-negative. Zero is non-negative too. https://godbolt.org/g/w93oj2 We do not have to emit the check *always*, there are obvious situations where we can avoid emitting it, since it would **always** get optimized-out. But i do think the tautological IR (`icmp ult %x, 0`, which is always false) should be emitted, and the middle-end should cleanup it. This sanitizer is in the `-fsanitize=implicit-conversion` group, and is a logical continuation of https://reviews.llvm.org/D48958 `-fsanitize=implicit-integer-truncation`. As for the ordering, i'we opted to emit the check **after** `-fsanitize=implicit-integer-truncation`. At least on these simple 16 test cases, this results in 1 of the 12 emitted checks being optimized away, as compared to 0 checks being optimized away if the order is reversed. This is a clang part. The compiler-rt part is ???. Finishes fixing PR21530 <https://bugs.llvm.org/show_bug.cgi?id=21530>, PR37552 <https://bugs.llvm.org/show_bug.cgi?id=37552>, PR35409 <https://bugs.llvm.org/show_bug.cgi?id=35409>. Finishes partially fixing PR9821 <https://bugs.llvm.org/show_bug.cgi?id=9821>. Finishes fixing https://github.com/google/sanitizers/issues/940. Only the bitfield handling is missing. Repository: rC Clang https://reviews.llvm.org/D50250 Files: docs/ReleaseNotes.rst docs/UndefinedBehaviorSanitizer.rst include/clang/Basic/Sanitizers.def lib/CodeGen/CGExprScalar.cpp test/CodeGen/catch-implicit-integer-conversions.c test/CodeGen/catch-implicit-integer-sign-changes-basics.c test/CodeGen/catch-implicit-integer-sign-changes-true-negatives.c test/CodeGen/catch-implicit-integer-sign-changes.c test/CodeGen/catch-implicit-integer-truncations.c test/CodeGenCXX/catch-implicit-integer-sign-changes-true-negatives.cpp test/Driver/fsanitize.c
Index: test/Driver/fsanitize.c =================================================================== --- test/Driver/fsanitize.c +++ test/Driver/fsanitize.c @@ -29,22 +29,22 @@ // CHECK-COVERAGE-WIN64: "--dependent-lib={{[^"]*}}ubsan_standalone-x86_64.lib" // RUN: %clang -target x86_64-linux-gnu -fsanitize=integer %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INTEGER -implicit-check-not="-fsanitize-address-use-after-scope" -// CHECK-INTEGER: "-fsanitize={{((signed-integer-overflow|unsigned-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|implicit-integer-truncation),?){6}"}} +// CHECK-INTEGER: "-fsanitize={{((signed-integer-overflow|unsigned-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|implicit-integer-truncation|implicit-integer-sign-change),?){7}"}} // RUN: %clang -target x86_64-linux-gnu -fsanitize=implicit-conversion %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-implicit-conversion,CHECK-implicit-conversion-RECOVER // RUN: %clang -target x86_64-linux-gnu -fsanitize=implicit-conversion -fsanitize-recover=implicit-conversion %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-implicit-conversion,CHECK-implicit-conversion-RECOVER // RUN: %clang -target x86_64-linux-gnu -fsanitize=implicit-conversion -fno-sanitize-recover=implicit-conversion %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-implicit-conversion,CHECK-implicit-conversion-NORECOVER // RUN: %clang -target x86_64-linux-gnu -fsanitize=implicit-conversion -fsanitize-trap=implicit-conversion %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-implicit-conversion,CHECK-implicit-conversion-TRAP -// CHECK-implicit-conversion: "-fsanitize={{((implicit-integer-truncation),?){1}"}} -// CHECK-implicit-conversion-RECOVER: "-fsanitize-recover={{((implicit-integer-truncation),?){1}"}} -// CHECK-implicit-conversion-RECOVER-NOT: "-fno-sanitize-recover={{((implicit-integer-truncation),?){1}"}} -// CHECK-implicit-conversion-RECOVER-NOT: "-fsanitize-trap={{((implicit-integer-truncation),?){1}"}} -// CHECK-implicit-conversion-NORECOVER-NOT: "-fno-sanitize-recover={{((implicit-integer-truncation),?){1}"}} // ??? -// CHECK-implicit-conversion-NORECOVER-NOT: "-fsanitize-recover={{((implicit-integer-truncation),?){1}"}} -// CHECK-implicit-conversion-NORECOVER-NOT: "-fsanitize-trap={{((implicit-integer-truncation),?){1}"}} -// CHECK-implicit-conversion-TRAP: "-fsanitize-trap={{((implicit-integer-truncation),?){1}"}} -// CHECK-implicit-conversion-TRAP-NOT: "-fsanitize-recover={{((implicit-integer-truncation),?){1}"}} -// CHECK-implicit-conversion-TRAP-NOT: "-fno-sanitize-recover={{((implicit-integer-truncation),?){1}"}} +// CHECK-implicit-conversion: "-fsanitize={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} +// CHECK-implicit-conversion-RECOVER: "-fsanitize-recover={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} +// CHECK-implicit-conversion-RECOVER-NOT: "-fno-sanitize-recover={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} +// CHECK-implicit-conversion-RECOVER-NOT: "-fsanitize-trap={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} +// CHECK-implicit-conversion-NORECOVER-NOT: "-fno-sanitize-recover={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} // ??? +// CHECK-implicit-conversion-NORECOVER-NOT: "-fsanitize-recover={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} +// CHECK-implicit-conversion-NORECOVER-NOT: "-fsanitize-trap={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} +// CHECK-implicit-conversion-TRAP: "-fsanitize-trap={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} +// CHECK-implicit-conversion-TRAP-NOT: "-fsanitize-recover={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} +// CHECK-implicit-conversion-TRAP-NOT: "-fno-sanitize-recover={{((implicit-integer-truncation|implicit-integer-sign-change),?){2}"}} // RUN: %clang -fsanitize=bounds -### -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK-BOUNDS // CHECK-BOUNDS: "-fsanitize={{((array-bounds|local-bounds),?){2}"}} Index: test/CodeGenCXX/catch-implicit-integer-sign-changes-true-negatives.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/catch-implicit-integer-sign-changes-true-negatives.cpp @@ -0,0 +1,149 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fno-sanitize-recover=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-NORECOVER +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fsanitize-recover=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-RECOVER +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fsanitize-trap=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-TRAP + +extern "C" { // Disable name mangling. + +// ========================================================================== // +// The expected true-negatives. +// ========================================================================== // + +// Sanitization is explicitly disabled. +// ========================================================================== // + +// CHECK-LABEL: @blacklist_0 +__attribute__((no_sanitize("undefined"))) unsigned int blacklist_0(signed int src) { + // We are not in "undefined" group, so that doesn't work. + // CHECK-SANITIZE: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @blacklist_1 +__attribute__((no_sanitize("integer"))) unsigned int blacklist_1(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @blacklist_2 +__attribute__((no_sanitize("implicit-conversion"))) unsigned int blacklist_2(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @blacklist_3 +__attribute__((no_sanitize("implicit-integer-sign-change"))) unsigned int blacklist_3(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// Explicit sign-changing conversions. +// ========================================================================== // + +// CHECK-LABEL: @explicit_signed_int_to_unsigned_int +unsigned int explicit_signed_int_to_unsigned_int(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (unsigned int)src; +} + +// CHECK-LABEL: @explicit_unsigned_int_to_signed_int +signed int explicit_unsigned_int_to_signed_int(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (signed int)src; +} + +// Explicit NOP conversions. +// ========================================================================== // + +// CHECK-LABEL: @explicit_unsigned_int_to_unsigned_int +unsigned int explicit_unsigned_int_to_unsigned_int(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (unsigned int)src; +} + +// CHECK-LABEL: @explicit_signed_int_to_signed_int +signed int explicit_signed_int_to_signed_int(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (signed int)src; +} + +// Explicit functional sign-changing casts. +// ========================================================================== // + +using UnsignedInt = unsigned int; +using SignedInt = signed int; + +// CHECK-LABEL: explicit_functional_unsigned_int_to_signed_int +signed int explicit_functional_unsigned_int_to_signed_int(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return SignedInt(src); +} + +// CHECK-LABEL: @explicit_functional_signed_int_to_unsigned_int +unsigned int explicit_functional_signed_int_to_unsigned_int(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return UnsignedInt(src); +} + +// Explicit functional NOP casts. +// ========================================================================== // + +// CHECK-LABEL: @explicit_functional_unsigned_int_to_unsigned_int +unsigned int explicit_functional_unsigned_int_to_unsigned_int(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return UnsignedInt(src); +} + +// CHECK-LABEL: @explicit_functional_signed_int_to_signed_int +signed int explicit_functional_signed_int_to_signed_int(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return SignedInt(src); +} + +// Explicit C++-style sign-changing casts. +// ========================================================================== // + +// CHECK-LABEL: @explicit_cppstyle_unsigned_int_to_signed_int +signed int explicit_cppstyle_unsigned_int_to_signed_int(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return static_cast<signed int>(src); +} + +// CHECK-LABEL: @explicit_cppstyle_signed_int_to_unsigned_int +unsigned int explicit_cppstyle_signed_int_to_unsigned_int(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return static_cast<unsigned int>(src); +} + +// Explicit C++-style casts NOP casts. +// ========================================================================== // + +// CHECK-LABEL: @explicit_cppstyle_unsigned_int_to_unsigned_int +unsigned int explicit_cppstyle_unsigned_int_to_unsigned_int(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return static_cast<unsigned int>(src); +} + +// CHECK-LABEL: @explicit_cppstyle_signed_int_to_signed_int +signed int explicit_cppstyle_signed_int_to_signed_int(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return static_cast<signed int>(src); +} + +} // extern "C" Index: test/CodeGen/catch-implicit-integer-truncations.c =================================================================== --- test/CodeGen/catch-implicit-integer-truncations.c +++ test/CodeGen/catch-implicit-integer-truncations.c @@ -166,14 +166,21 @@ } // CHECK-LABEL: @blacklist_1 -__attribute__((no_sanitize("implicit-conversion"))) unsigned char blacklist_1(unsigned int src) { +__attribute__((no_sanitize("integer"))) unsigned char blacklist_1(unsigned int src) { // CHECK-SANITIZE-NOT: call // CHECK: } return src; } // CHECK-LABEL: @blacklist_2 -__attribute__((no_sanitize("implicit-integer-truncation"))) unsigned char blacklist_2(unsigned int src) { +__attribute__((no_sanitize("implicit-conversion"))) unsigned char blacklist_2(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @blacklist_3 +__attribute__((no_sanitize("implicit-integer-truncation"))) unsigned char blacklist_3(unsigned int src) { // CHECK-SANITIZE-NOT: call // CHECK: } return src; Index: test/CodeGen/catch-implicit-integer-sign-changes.c =================================================================== --- /dev/null +++ test/CodeGen/catch-implicit-integer-sign-changes.c @@ -0,0 +1,276 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fno-sanitize-recover=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-NORECOVER +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fsanitize-recover=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-RECOVER +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fsanitize-trap=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-TRAP + +// CHECK-SANITIZE-ANYRECOVER: @[[UNSIGNED_INT:.*]] = {{.*}} c"'unsigned int'\00" } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[SIGNED_INT:.*]] = {{.*}} c"'int'\00" } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_100:.*]] = {{.*}}, i32 100, i32 10 }, {{.*}}* @[[UNSIGNED_INT]], {{.*}}* @[[SIGNED_INT]], i8 1 } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_200:.*]] = {{.*}}, i32 200, i32 10 }, {{.*}}* @[[SIGNED_INT]], {{.*}}* @[[UNSIGNED_INT]], i8 1 } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[UNSIGNED_CHAR:.*]] = {{.*}} c"'unsigned char'\00" } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_300:.*]] = {{.*}}, i32 300, i32 10 }, {{.*}}* @[[SIGNED_INT]], {{.*}}* @[[UNSIGNED_CHAR]], i8 1 } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[SIGNED_CHAR:.*]] = {{.*}} c"'signed char'\00" } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_400:.*]] = {{.*}}, i32 400, i32 10 }, {{.*}}* @[[SIGNED_CHAR]], {{.*}}* @[[UNSIGNED_CHAR]], i8 1 } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_500:.*]] = {{.*}}, i32 500, i32 10 }, {{.*}}* @[[UNSIGNED_CHAR]], {{.*}}* @[[SIGNED_CHAR]], i8 1 } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_600:.*]] = {{.*}}, i32 600, i32 10 }, {{.*}}* @[[SIGNED_CHAR]], {{.*}}* @[[UNSIGNED_INT]], i8 1 } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_700:.*]] = {{.*}}, i32 700, i32 10 }, {{.*}}* @[[UNSIGNED_INT]], {{.*}}* @[[SIGNED_CHAR]], i8 1 } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_800:.*]] = {{.*}}, i32 800, i32 10 }, {{.*}}* @[[SIGNED_INT]], {{.*}}* @[[SIGNED_CHAR]], i8 1 } +// CHECK-SANITIZE-ANYRECOVER: @[[UINT32:.*]] = {{.*}} c"'uint32_t' (aka 'unsigned int')\00" } +// CHECK-SANITIZE-ANYRECOVER: @[[INT32:.*]] = {{.*}} c"'int32_t' (aka 'int')\00" } +// CHECK-SANITIZE-ANYRECOVER: @[[LINE_900:.*]] = {{.*}}, i32 900, i32 10 }, {{.*}}* @[[UINT32]], {{.*}}* @[[INT32]], i8 1 } + +// ========================================================================== // +// The expected true-positives. +// These are implicit, potentially sign-altering, conversions. +// ========================================================================== // + +// These 3 result (after optimizations) in simple 'icmp sge i32 %src, 0'. + +// CHECK-LABEL: @unsigned_int_to_signed_int +// CHECK-SAME: (i32 %[[SRC:.*]]) +signed int unsigned_int_to_signed_int(unsigned int src) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i32 + // CHECK-NEXT: store i32 %[[SRC]], i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i32, i32* %[[SRC_ADDR]] + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp ult i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[DST_NEGATIVITYCHECK:.*]] = icmp slt i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[DST_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTDST:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_100]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_100]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i32 %[[DST]] +#line 100 + return src; +} + +// CHECK-LABEL: @signed_int_to_unsigned_int +// CHECK-SAME: (i32 %[[SRC:.*]]) +unsigned int signed_int_to_unsigned_int(signed int src) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i32 + // CHECK-NEXT: store i32 %[[SRC]], i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i32, i32* %[[SRC_ADDR]] + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp slt i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[DST_NEGATIVITYCHECK:.*]] = icmp ult i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[DST_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTDST:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_200]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_200]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i32 %[[DST]] +#line 200 + return src; +} + +// CHECK-LABEL: @signed_int_to_unsigned_char +// CHECK-SAME: (i32 %[[SRC:.*]]) +unsigned char signed_int_to_unsigned_char(signed int src) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i32 + // CHECK-NEXT: store i32 %[[SRC]], i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i32, i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[CONV:.*]] = trunc i32 %[[DST]] to i8 + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp slt i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[CONV_NEGATIVITYCHECK:.*]] = icmp ult i8 %[[CONV]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[CONV_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTCONV:.*]] = zext i8 %[[CONV]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_300]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_300]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8 %[[CONV]] +#line 300 + return src; +} + +// These 3 result (after optimizations) in simple 'icmp sge i8 %src, 0' + +// CHECK-LABEL: @signed_char_to_unsigned_char +// CHECK-SAME: (i8 signext %[[SRC:.*]]) +unsigned char signed_char_to_unsigned_char(signed char src) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i8 + // CHECK-NEXT: store i8 %[[SRC]], i8* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i8, i8* %[[SRC_ADDR]] + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp slt i8 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[DST_NEGATIVITYCHECK:.*]] = icmp ult i8 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[DST_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i8 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTDST:.*]] = zext i8 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_400]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_400]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8 %[[DST]] +#line 400 + return src; +} + +// CHECK-LABEL: @unsigned_char_to_signed_char +// CHECK-SAME: (i8 zeroext %[[SRC:.*]]) +signed char unsigned_char_to_signed_char(unsigned char src) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i8 + // CHECK-NEXT: store i8 %[[SRC]], i8* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i8, i8* %[[SRC_ADDR]] + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp ult i8 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[DST_NEGATIVITYCHECK:.*]] = icmp slt i8 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[DST_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i8 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTDST:.*]] = zext i8 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_500]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_500]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8 %[[DST]] +#line 500 + return src; +} + +// CHECK-LABEL: @signed_char_to_unsigned_int +// CHECK-SAME: (i8 signext %[[SRC:.*]]) +unsigned int signed_char_to_unsigned_int(signed char src) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i8 + // CHECK-NEXT: store i8 %[[SRC]], i8* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i8, i8* %[[SRC_ADDR]] + // CHECK-NEXT: %[[CONV:.*]] = sext i8 %[[DST]] to i32 + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp slt i8 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[CONV_NEGATIVITYCHECK:.*]] = icmp ult i32 %[[CONV]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[CONV_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i8 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTCONV:.*]] = zext i32 %[[CONV]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_600]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_600]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i32 %[[CONV]] +#line 600 + return src; +} + +// This one result (after optimizations) in 'icmp sge i8 (trunc i32 %src), 0' + +// CHECK-LABEL: @unsigned_int_to_signed_char +// CHECK-SAME: (i32 %[[SRC:.*]]) +signed char unsigned_int_to_signed_char(unsigned int src) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i32 + // CHECK-NEXT: store i32 %[[SRC]], i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i32, i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[CONV:.*]] = trunc i32 %[[DST]] to i8 + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp ult i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[CONV_NEGATIVITYCHECK:.*]] = icmp slt i8 %[[CONV]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[CONV_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTCONV:.*]] = zext i8 %[[CONV]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_700]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_700]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8 %[[CONV]] +#line 700 + return src; +} + +// The worst one: 'xor i1 (icmp sge i8 (trunc i32 %x), 0), (icmp sge i32 %x, 0)' + +// CHECK-LABEL: @signed_int_to_signed_char +// CHECK-SAME: (i32 %[[SRC:.*]]) +signed char signed_int_to_signed_char(signed int x) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i32 + // CHECK-NEXT: store i32 %[[SRC]], i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i32, i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[CONV:.*]] = trunc i32 %[[DST]] to i8 + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp slt i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[CONV_NEGATIVITYCHECK:.*]] = icmp slt i8 %[[CONV]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[CONV_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTCONV:.*]] = zext i8 %[[CONV]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_800]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_800]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8 %[[CONV]] +#line 800 + return x; +} + +// ========================================================================== // +// Check canonical type stuff +// ========================================================================== // + +typedef unsigned int uint32_t; +typedef signed int int32_t; + +// CHECK-LABEL: @uint32_t_to_int32_t +// CHECK-SAME: (i32 %[[SRC:.*]]) +int32_t uint32_t_to_int32_t(uint32_t src) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i32 + // CHECK-NEXT: store i32 %[[SRC]], i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i32, i32* %[[SRC_ADDR]] + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp ult i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[DST_NEGATIVITYCHECK:.*]] = icmp slt i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[DST_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTDST:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_900]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_900]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTDST]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i32 %[[DST]] +#line 900 + return src; +} + +// ========================================================================== // +// Check that explicit conversion does not interfere with implicit conversion +// ========================================================================== // +// These contain one implicit and one explicit sign-changing conversion. +// We want to make sure that we still diagnose the implicit conversion. + +// Implicit sign-change after explicit sign-change. +// CHECK-LABEL: @explicit_conversion_interference0 +unsigned int explicit_conversion_interference0(unsigned int c) { + // CHECK-SANITIZE: call + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (signed int)c; +} + +// Implicit sign-change before explicit sign-change. +// CHECK-LABEL: @explicit_conversion_interference1 +unsigned int explicit_conversion_interference1(unsigned int c) { + // CHECK-SANITIZE: call + // CHECK-SANITIZE-NOT: call + // CHECK: } + signed int b; + return (unsigned int)(b = c); +} Index: test/CodeGen/catch-implicit-integer-sign-changes-true-negatives.c =================================================================== --- /dev/null +++ test/CodeGen/catch-implicit-integer-sign-changes-true-negatives.c @@ -0,0 +1,181 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fno-sanitize-recover=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-NORECOVER +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fsanitize-recover=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-RECOVER +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fsanitize-trap=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-TRAP + +// ========================================================================== // +// The expected true-negatives. +// ========================================================================== // + +// Sanitization is explicitly disabled. +// ========================================================================== // + +// CHECK-LABEL: @blacklist_0 +__attribute__((no_sanitize("undefined"))) unsigned int blacklist_0(signed int src) { + // We are not in "undefined" group, so that doesn't work. + // CHECK-SANITIZE: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @blacklist_1 +__attribute__((no_sanitize("integer"))) unsigned int blacklist_1(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @blacklist_2 +__attribute__((no_sanitize("implicit-conversion"))) unsigned int blacklist_2(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @blacklist_3 +__attribute__((no_sanitize("implicit-integer-sign-change"))) unsigned int blacklist_3(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// Explicit sign-changing conversions. +// ========================================================================== // + +// CHECK-LABEL: explicit_signed_int_to_unsigned_int +unsigned int explicit_signed_int_to_unsigned_int(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (unsigned int)src; +} + +// CHECK-LABEL: explicit_unsigned_int_to_signed_int +signed int explicit_unsigned_int_to_signed_int(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (signed int)src; +} + +// Explicit NOP conversions. +// ========================================================================== // + +// CHECK-LABEL: @explicit_ununsigned_int_to_ununsigned_int +unsigned int explicit_ununsigned_int_to_ununsigned_int(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (unsigned int)src; +} + +// CHECK-LABEL: @explicit_unsigned_int_to_unsigned_int +signed int explicit_unsigned_int_to_unsigned_int(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (signed int)src; +} + +// conversions to to boolean type are not counted as sign-change. +// ========================================================================== // + +// CHECK-LABEL: @unsigned_int_to_bool +_Bool unsigned_int_to_bool(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @signed_int_to_bool +_Bool signed_int_to_bool(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @explicit_unsigned_int_to_bool +_Bool explicit_unsigned_int_to_bool(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (_Bool)src; +} + +// CHECK-LABEL: @explicit_signed_int_to_bool +_Bool explicit_signed_int_to_bool(signed int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (_Bool)src; +} + +// Explicit conversions from pointer to an integer. +// Can not have an implicit conversion from pointer to an integer. +// Can not have an implicit conversion between two enums. +// ========================================================================== // + +// CHECK-LABEL: @explicit_voidptr_to_unsigned_int +unsigned int explicit_voidptr_to_unsigned_int(void *src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (unsigned int)src; +} + +// CHECK-LABEL: @explicit_voidptr_to_signed_int +signed int explicit_voidptr_to_signed_int(void *src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return (signed int)src; +} + +// Implicit conversions from floating-point. +// ========================================================================== // + +// CHECK-LABEL: @float_to_unsigned_int +unsigned int float_to_unsigned_int(float src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @float_to_signed_int +signed int float_to_signed_int(float src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @double_to_unsigned_int +unsigned int double_to_unsigned_int(double src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @double_to_signed_int +signed int double_to_signed_int(double src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// Sugar. +// ========================================================================== // + +typedef unsigned int uint32_t; + +// CHECK-LABEL: @uint32_to_unsigned_int +unsigned int uint32_to_unsigned_int(uint32_t src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @unsigned_int_to_uint32 +uint32_t unsigned_int_to_uint32(unsigned int src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} + +// CHECK-LABEL: @uint32_to_uint32 +uint32_t uint32_to_uint32(uint32_t src) { + // CHECK-SANITIZE-NOT: call + // CHECK: } + return src; +} Index: test/CodeGen/catch-implicit-integer-sign-changes-basics.c =================================================================== --- /dev/null +++ test/CodeGen/catch-implicit-integer-sign-changes-basics.c @@ -0,0 +1,165 @@ +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fno-sanitize-recover=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK +// RUN: %clang_cc1 -fsanitize=implicit-integer-sign-change -fsanitize-recover=implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK + +// Test plan: +// * Two types - int and char +// * Two signs - signed and unsigned +// * Square that - we have input and output types. +// Thus, there are total of (2*2)^2 == 16 tests. +// These are all the possible variations/combinations of casts. +// However, not all of them should result in the check. +// So here, we *only* check which should and which should not result in checks. + +//============================================================================// +// Half of the cases do not need the check. // +//============================================================================// + +//----------------------------------------------------------------------------// +// No cast happens at all. No check needed. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: @convert_unsigned_int_to_unsigned_int +unsigned int convert_unsigned_int_to_unsigned_int(unsigned int x) { + // CHECK-NOT: call + // CHECK: } + return x; +} + +// CHECK-LABEL: @convert_unsigned_char_to_unsigned_char +unsigned char convert_unsigned_char_to_unsigned_char(unsigned char x) { + // CHECK-NOT: call + // CHECK: } + return x; +} + +// CHECK-LABEL: @convert_signed_int_to_signed_int +signed int convert_signed_int_to_signed_int(signed int x) { + // CHECK-NOT: call + // CHECK: } + return x; +} + +// CHECK-LABEL: @convert_signed_char_to_signed_char +signed char convert_signed_char_to_signed_char(signed char x) { + // CHECK-NOT: call + // CHECK: } + return x; +} + +//----------------------------------------------------------------------------// +// Both types are unsigned. No check needed. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: @convert_unsigned_int_to_unsigned_char +unsigned char convert_unsigned_int_to_unsigned_char(unsigned int x) { + // CHECK-NOT: call + // CHECK: trunc + // CHECK-NOT: call + // CHECK: } + return x; +} + +// CHECK-LABEL: @convert_unsigned_char_to_unsigned_int +unsigned int convert_unsigned_char_to_unsigned_int(unsigned char x) { + // CHECK-NOT: call + // CHECK: zext + // CHECK-NOT: call + // CHECK: } + return x; +} + +//----------------------------------------------------------------------------// +// Source type was unsigned, destination type is signed, but non-negative. +// Because zero-extension happens - the sign bit will be 0. No check needed. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: @convert_unsigned_char_to_signed_int +signed int convert_unsigned_char_to_signed_int(unsigned char x) { + // CHECK-NOT: call + // CHECK: zext + // CHECK-NOT: call + // CHECK: } + return x; +} + +//----------------------------------------------------------------------------// +// Both types are signed, and have the same sign, since sign-extension happens, +// i.e. the sign bit will be propagated. No check needed. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: @convert_signed_char_to_signed_int +signed int convert_signed_char_to_signed_int(signed char x) { + // CHECK-NOT: call + // CHECK: sext + // CHECK-NOT: call + // CHECK: } + return x; +} + +//============================================================================// +// The remaining 8 cases *do* need the check. // +//============================================================================// + +// These 3 result in simple 'icmp sge i32 %x, 0' + +// CHECK-LABEL: @convert_unsigned_int_to_signed_int +signed int convert_unsigned_int_to_signed_int(unsigned int x) { + // CHECK: call void @__ubsan_handle_implicit_conversion + // CHECK: } + return x; +} + +// CHECK-LABEL: @convert_signed_int_to_unsigned_int +unsigned int convert_signed_int_to_unsigned_int(signed int x) { + // CHECK: call void @__ubsan_handle_implicit_conversion + // CHECK: } + return x; +} + +// CHECK-LABEL: @convert_signed_int_to_unsigned_char +unsigned char convert_signed_int_to_unsigned_char(signed int x) { + // CHECK: call void @__ubsan_handle_implicit_conversion + // CHECK: } + return x; +} + +// These 3 result in simple 'icmp sge i8 %x, 0' + +// CHECK-LABEL: @convert_signed_char_to_unsigned_char +unsigned char convert_signed_char_to_unsigned_char(signed char x) { + // CHECK: call void @__ubsan_handle_implicit_conversion + // CHECK: } + return x; +} + +// CHECK-LABEL: @convert_unsigned_char_to_signed_char +signed char convert_unsigned_char_to_signed_char(unsigned char x) { + // CHECK: call void @__ubsan_handle_implicit_conversion + // CHECK: } + return x; +} + +// CHECK-LABEL: @convert_signed_char_to_unsigned_int +unsigned int convert_signed_char_to_unsigned_int(signed char x) { + // CHECK: call void @__ubsan_handle_implicit_conversion + // CHECK: } + return x; +} + +// 'icmp sge i8 (trunc i32 %x), 0' + +// CHECK-LABEL: @convert_unsigned_int_to_signed_char +signed char convert_unsigned_int_to_signed_char(unsigned int x) { + // CHECK: call void @__ubsan_handle_implicit_conversion + // CHECK: } + return x; +} + +// 'xor i1 (icmp sge i8 (trunc i32 %x), 0), (icmp sge i32 %x, 0)' + +// CHECK-LABEL: @convert_signed_int_to_signed_char +signed char convert_signed_int_to_signed_char(signed int x) { + // CHECK: call void @__ubsan_handle_implicit_conversion + // CHECK: } + return x; +} Index: test/CodeGen/catch-implicit-integer-conversions.c =================================================================== --- /dev/null +++ test/CodeGen/catch-implicit-integer-conversions.c @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK +// RUN: %clang_cc1 -fsanitize=implicit-integer-truncation,implicit-integer-sign-change -fno-sanitize-recover=implicit-integer-truncation,implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-NORECOVER +// RUN: %clang_cc1 -fsanitize=implicit-integer-truncation,implicit-integer-sign-change -fsanitize-recover=implicit-integer-truncation,implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-RECOVER +// RUN: %clang_cc1 -fsanitize=implicit-integer-truncation,implicit-integer-sign-change -fsanitize-trap=implicit-integer-truncation,implicit-integer-sign-change -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-TRAP + +// In which order do we emit truncation and sign-change checks? +// First, truncation, then sign change. + +// CHECK-SANITIZE-ANYRECOVER: @[[SIGNED_INT:.*]] = {{.*}} c"'int'\00" } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[SIGNED_CHAR:.*]] = {{.*}} c"'signed char'\00" } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_100_TRUNCATION:.*]] = {{.*}}, i32 100, i32 10 }, {{.*}}* @[[SIGNED_INT]], {{.*}}* @[[SIGNED_CHAR]], i8 0 } +// CHECK-SANITIZE-ANYRECOVER-NEXT: @[[LINE_100_SIGNCHANGE:.*]] = {{.*}}, i32 100, i32 10 }, {{.*}}* @[[SIGNED_INT]], {{.*}}* @[[SIGNED_CHAR]], i8 1 } + +// CHECK-LABEL: @signed_int_to_signed_char +// CHECK-SAME: (i32 %[[SRC:.*]]) +signed char signed_int_to_signed_char(signed int x) { + // CHECK: %[[SRC_ADDR:.*]] = alloca i32 + // CHECK-NEXT: store i32 %[[SRC]], i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[DST:.*]] = load i32, i32* %[[SRC_ADDR]] + // CHECK-NEXT: %[[CONV:.*]] = trunc i32 %[[DST]] to i8 + // CHECK-SANITIZE-NEXT: %[[ANYEXT:.*]] = sext i8 %[[CONV]] to i32, !nosanitize + // CHECK-SANITIZE-NEXT: %[[TRUNCHECK:.*]] = icmp eq i32 %[[ANYEXT]], %[[DST]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[TRUNCHECK]], label %[[CONT:.*]], label %[[HANDLER_IMPLICIT_CONVERSION_TRUNCATION:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION_TRUNCATION]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTDST:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTCONV:.*]] = zext i8 %[[CONV]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_100_TRUNCATION]] to i8*), i64 %[[EXTDST]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_100_TRUNCATION]] to i8*), i64 %[[EXTDST]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-SANITIZE-NEXT: %[[SRC_NEGATIVITYCHECK:.*]] = icmp slt i32 %[[DST]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[CONV_NEGATIVITYCHECK:.*]] = icmp slt i8 %[[CONV]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[SIGNCHANGECHECK:.*]] = icmp eq i1 %[[SRC_NEGATIVITYCHECK]], %[[CONV_NEGATIVITYCHECK]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[SIGNCHANGECHECK]], label %[[FINALLY:.*]], label %[[HANDLER_IMPLICIT_CONVERSION_SIGNCHANGE:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_IMPLICIT_CONVERSION_SIGNCHANGE]]: + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTSRC:.*]] = zext i32 %[[DST]] to i64, !nosanitize + // CHECK-SANITIZE-ANYRECOVER-NEXT: %[[EXTCONV:.*]] = zext i8 %[[CONV]] to i64, !nosanitize + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_implicit_conversion_abort(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_100_SIGNCHANGE]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_implicit_conversion(i8* bitcast ({ {{{.*}}}, {{{.*}}}*, {{{.*}}}*, i8 }* @[[LINE_100_SIGNCHANGE]] to i8*), i64 %[[EXTSRC]], i64 %[[EXTCONV]]){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[FINALLY]]: + // CHECK-NEXT: ret i8 %[[CONV]] +#line 100 + return x; +} Index: lib/CodeGen/CGExprScalar.cpp =================================================================== --- lib/CodeGen/CGExprScalar.cpp +++ lib/CodeGen/CGExprScalar.cpp @@ -303,22 +303,31 @@ /// Keep in sync with the enum of the same name in ubsan_handlers.h enum ImplicitConversionCheckKind : unsigned char { ICCK_IntegerTruncation = 0, + ICCK_IntegerSignChange = 1, }; /// Emit a check that an [implicit] truncation of an integer does not /// discard any bits. It is not UB, so we use the value after truncation. void EmitIntegerTruncationCheck(Value *Src, QualType SrcType, Value *Dst, QualType DstType, SourceLocation Loc); + /// Emit a check that an [implicit] conversion of an integer does not change + /// the sign of the value. It is not UB, so we use the value after conversion. + /// NOTE: Src and Dst may be the exact same value! (point to the same thing) + void EmitIntegerSignChangeCheck(Value *Src, QualType SrcType, Value *Dst, + QualType DstType, SourceLocation Loc); + /// Emit a conversion from the specified type to the specified destination /// type, both of which are LLVM scalar types. struct ScalarConversionOpts { bool TreatBooleanAsSigned; bool EmitImplicitIntegerTruncationChecks; + bool EmitImplicitIntegerSignChangeChecks; ScalarConversionOpts() : TreatBooleanAsSigned(false), - EmitImplicitIntegerTruncationChecks(false) {} + EmitImplicitIntegerTruncationChecks(false), + EmitImplicitIntegerSignChangeChecks(false) {} }; Value * EmitScalarConversion(Value *Src, QualType SrcTy, QualType DstTy, @@ -985,6 +994,111 @@ SanitizerHandler::ImplicitConversion, StaticArgs, {Src, Dst}); } +void ScalarExprEmitter::EmitIntegerSignChangeCheck(Value *Src, QualType SrcType, + Value *Dst, QualType DstType, + SourceLocation Loc) { + if (!CGF.SanOpts.has(SanitizerKind::ImplicitIntegerSignChange)) + return; + + llvm::Type *SrcTy = Src->getType(); + llvm::Type *DstTy = Dst->getType(); + + // We only care about int->int conversions here. + // We ignore conversions to/from pointer and/or bool. + if (!(SrcType->isIntegerType() && DstType->isIntegerType())) + return; + + assert(isa<llvm::IntegerType>(SrcTy) && isa<llvm::IntegerType>(DstTy) && + "clang integer type lowered to non-integer llvm type"); + + bool SrcSigned = SrcType->isSignedIntegerOrEnumerationType(); + bool DstSigned = DstType->isSignedIntegerOrEnumerationType(); + unsigned SrcBits = SrcTy->getScalarSizeInBits(); + unsigned DstBits = DstTy->getScalarSizeInBits(); + + // Now, we do not need to emit the check in *all* of the cases. + // We can avoid emitting it in some obvious cases where it would have been + // dropped by the opt passes (instcombine) always anyways. + { + // If it's a cast between the same type, just differently-sugared. no check. + QualType CanonSrcType = CGF.getContext().getCanonicalType(SrcType); + QualType CanonDstType = CGF.getContext().getCanonicalType(DstType); + if (CanonSrcType == CanonDstType) + return; + } + { + // At least one of the values needs to have signed type. + // If both are unsigned, then obviously, neither of them can be negative. + if (!(SrcSigned || DstSigned)) + return; + } + { + // If the conversion is to *larger* *signed* type, then no check is needed. + // Because either sign-extension happens (so the sign will remain), + // or zero-extension will happen (the sign bit will be zero.) + if ((DstBits > SrcBits) && DstSigned) + return; + } + // That's it. We can't rule out any more cases with the data we have. + + assert(!DstType->isBooleanType() && "we should not get here with booleans."); + + assert(((SrcBits != DstBits) || (SrcSigned != DstSigned)) && + "either the widths should be different, or the signednesses."); + + CodeGenFunction::SanitizerScope SanScope(&CGF); + + // NOTE: zero value is considered to be non-negative. + auto EmitIsNegativeTest = [this](Value *V, QualType VType, + const char *Name) -> Value * { + // Does this Value has signed type? + bool VSigned = VType->isSignedIntegerOrEnumerationType(); + // So which 'lt' predicate for the comparison do we have to use? + // NOTE: if it is unsigned, then the comparison is naturally always 'false'. + llvm::ICmpInst::Predicate Pred = + VSigned ? llvm::ICmpInst::ICMP_SLT : llvm::ICmpInst::ICMP_ULT; + // Get the zero of the same type with which we will be comparing. + llvm::Type *VTy = V->getType(); + llvm::Constant *Zero = llvm::ConstantInt::get(VTy, 0); + // %V.isnegative = icmp [s/u]lt %V, 0 + // I.e is %V *strictly* less than zero, does it have negative value? + return Builder.CreateICmp(Pred, V, Zero, + llvm::Twine(Name) + "." + V->getName() + + ".negativitycheck"); + }; + + // 1. Was the old Value negative? + llvm::Value *SrcIsNegative = EmitIsNegativeTest(Src, SrcType, "src"); + // 2. Is the new Value negative? + llvm::Value *DstIsNegative = EmitIsNegativeTest(Dst, DstType, "dst"); + // 3. Now, was the 'negativity status' preserved during the conversion? + // NOTE: conversion from negative to zero is considered to change the sign. + // (We want to get 'false' when the conversion changed the sign) + // Truth table: + // /-----------------|-----------------|-----------------\ + // | src is negative | dst is negative | no sign change? | + // |-----------------|-----------------|-----------------| + // | false | false | true | + // |-----------------|-----------------|-----------------| + // | false | true | false (!) | + // |-----------------|-----------------|-----------------| + // | true | false | false (!) | + // |-----------------|-----------------|-----------------| + // | true | true | true | + // |-----------------|-----------------|-----------------| + // So we should just equality-compare the negativity statuses. + llvm::Value *Check = + Builder.CreateICmpEQ(SrcIsNegative, DstIsNegative, "signchangecheck"); + // If the comparison result is 'false', then the conversion changed the sign. + + llvm::Constant *StaticArgs[] = { + CGF.EmitCheckSourceLocation(Loc), CGF.EmitCheckTypeDescriptor(SrcType), + CGF.EmitCheckTypeDescriptor(DstType), + llvm::ConstantInt::get(Builder.getInt8Ty(), ICCK_IntegerSignChange)}; + CGF.EmitCheck(std::make_pair(Check, SanitizerKind::ImplicitIntegerSignChange), + SanitizerHandler::ImplicitConversion, StaticArgs, {Src, Dst}); +} + /// Emit a conversion from the specified type to the specified destination type, /// both of which are LLVM scalar types. Value *ScalarExprEmitter::EmitScalarConversion(Value *Src, QualType SrcType, @@ -1036,8 +1150,13 @@ } // Ignore conversions like int -> uint. - if (SrcTy == DstTy) + if (SrcTy == DstTy) { + if (Opts.EmitImplicitIntegerSignChangeChecks) + EmitIntegerSignChangeCheck(Src, NoncanonicalSrcType, Src, + NoncanonicalDstType, Loc); + return Src; + } // Handle pointer conversions next: pointers can only be converted to/from // other pointers and integers. Check for pointer types in terms of LLVM, as @@ -1181,6 +1300,10 @@ EmitIntegerTruncationCheck(Src, NoncanonicalSrcType, Res, NoncanonicalDstType, Loc); + if (Opts.EmitImplicitIntegerSignChangeChecks) + EmitIntegerSignChangeCheck(Src, NoncanonicalSrcType, Res, + NoncanonicalDstType, Loc); + return Res; } @@ -1877,9 +2000,14 @@ case CK_IntegralCast: { ScalarConversionOpts Opts; - if (CGF.SanOpts.has(SanitizerKind::ImplicitIntegerTruncation)) { - if (auto *ICE = dyn_cast<ImplicitCastExpr>(CE)) - Opts.EmitImplicitIntegerTruncationChecks = !ICE->isPartOfExplicitCast(); + if (auto *ICE = dyn_cast<ImplicitCastExpr>(CE)) { + if (CGF.SanOpts.hasOneOf(SanitizerKind::ImplicitConversion) && + !ICE->isPartOfExplicitCast()) { + Opts.EmitImplicitIntegerTruncationChecks = + CGF.SanOpts.has(SanitizerKind::ImplicitIntegerTruncation); + Opts.EmitImplicitIntegerSignChangeChecks = + CGF.SanOpts.has(SanitizerKind::ImplicitIntegerSignChange); + } } return EmitScalarConversion(Visit(E), E->getType(), DestTy, CE->getExprLoc(), Opts); Index: include/clang/Basic/Sanitizers.def =================================================================== --- include/clang/Basic/Sanitizers.def +++ include/clang/Basic/Sanitizers.def @@ -133,12 +133,14 @@ // ImplicitConversionSanitizer SANITIZER("implicit-integer-truncation", ImplicitIntegerTruncation) +SANITIZER("implicit-integer-sign-change", ImplicitIntegerSignChange) SANITIZER_GROUP("implicit-conversion", ImplicitConversion, - ImplicitIntegerTruncation) + ImplicitIntegerSignChange | ImplicitIntegerTruncation) SANITIZER_GROUP("integer", Integer, - ImplicitIntegerTruncation | IntegerDivideByZero | Shift | - SignedIntegerOverflow | UnsignedIntegerOverflow) + ImplicitIntegerSignChange | ImplicitIntegerTruncation | + IntegerDivideByZero | Shift | SignedIntegerOverflow | + UnsignedIntegerOverflow) SANITIZER("local-bounds", LocalBounds) SANITIZER_GROUP("bounds", Bounds, ArrayBounds | LocalBounds) Index: docs/UndefinedBehaviorSanitizer.rst =================================================================== --- docs/UndefinedBehaviorSanitizer.rst +++ docs/UndefinedBehaviorSanitizer.rst @@ -95,6 +95,12 @@ width, is not equal to the original value before the downcast. Issues caught by this sanitizer are not undefined behavior, but are often unintentional. + - ``-fsanitize=implicit-integer-sign-change``: Implicit conversion between + integer types, if that changes the sign of the value. That is, if the the + original value was negative and the new value is positive (or zero), + or the original value was positive, and the new value is negative. + Issues caught by this sanitizer are not undefined behavior, + but are often unintentional. - ``-fsanitize=integer-divide-by-zero``: Integer division by zero. - ``-fsanitize=nonnull-attribute``: Passing null pointer as a function parameter which is declared to never be null. @@ -159,10 +165,11 @@ - ``-fsanitize=integer``: Checks for undefined or suspicious integer behavior (e.g. unsigned integer overflow). Enables ``signed-integer-overflow``, ``unsigned-integer-overflow``, - ``shift``, ``integer-divide-by-zero``, and ``implicit-integer-truncation``. + ``shift``, ``integer-divide-by-zero``, ``implicit-integer-truncation`` and + ``implicit-integer-sign-change``. - ``-fsanitize=implicit-conversion``: Checks for suspicious behaviours of - implicit conversions. - Currently, only ``-fsanitize=implicit-integer-truncation`` is implemented. + implicit conversions. Enables ```implicit-integer-truncation`` and + ``implicit-integer-sign-change``. - ``-fsanitize=nullability``: Enables ``nullability-arg``, ``nullability-assign``, and ``nullability-return``. While violating nullability does not have undefined behavior, it is often unintentional, Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -168,6 +168,25 @@ Undefined Behavior Sanitizer (UBSan) ------------------------------------ +* The Implicit Conversion Sanitizer (``-fsanitize=implicit-conversion``) group + was extended. One more type of issues is caught - implicit integer sign change. + (``-fsanitize=implicit-integer-sign-change``). + + .. code-block:: c++ + + bool consume(unsigned int val); + + void test(int val) { + (void)consume(val); // If the value was negative, it is now large positive. + (void)consume((unsigned int)val); // OK, the conversion is explicit. + } + + Just like other ``-fsanitize=integer`` checks, these issues are **not** + undefined behaviour. But they are not *always* intentional, and are somewhat + hard to track down. This group is **not** enabled by ``-fsanitize=undefined``, + but the ``-fsanitize=implicit-integer-sign-change`` check + (as is ``-fsanitize=implicit-integer-truncation`` check) + is enabled by ``-fsanitize=integer``. Core Analysis Improvements ==========================
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits