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 ========================== 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: 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: lib/CodeGen/CGExprScalar.cpp =================================================================== --- lib/CodeGen/CGExprScalar.cpp +++ lib/CodeGen/CGExprScalar.cpp @@ -303,6 +303,7 @@ /// 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 @@ -310,15 +311,23 @@ 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(SrcTy) && isa(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(CE)) - Opts.EmitImplicitIntegerTruncationChecks = !ICE->isPartOfExplicitCast(); + if (auto *ICE = dyn_cast(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: 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: 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-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.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-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/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(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(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(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(src); +} + +} // extern "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}"}}