Index: cfe/trunk/docs/ReleaseNotes.rst =================================================================== --- cfe/trunk/docs/ReleaseNotes.rst +++ cfe/trunk/docs/ReleaseNotes.rst @@ -155,7 +155,9 @@ Undefined Behavior Sanitizer (UBSan) ------------------------------------ -... +The C++ dynamic type check now requires run-time null checking (i.e, +`-fsanitize=vptr` cannot be used without `-fsanitize=null`). This change does +not impact users who rely on UBSan check groups (e.g `-fsanitize=undefined`). Core Analysis Improvements ========================== Index: cfe/trunk/docs/UndefinedBehaviorSanitizer.rst =================================================================== --- cfe/trunk/docs/UndefinedBehaviorSanitizer.rst +++ cfe/trunk/docs/UndefinedBehaviorSanitizer.rst @@ -130,11 +130,11 @@ it is often unintentional, so UBSan offers to catch it. - ``-fsanitize=vla-bound``: A variable-length array whose bound does not evaluate to a positive value. - - ``-fsanitize=vptr``: Use of an object whose vptr indicates that - it is of the wrong dynamic type, or that its lifetime has not - begun or has ended. Incompatible with ``-fno-rtti``. Link must - be performed by ``clang++``, not ``clang``, to make sure C++-specific - parts of the runtime library and C++ standard libraries are present. + - ``-fsanitize=vptr``: Use of an object whose vptr indicates that it is of + the wrong dynamic type, or that its lifetime has not begun or has ended. + Incompatible with ``-fno-rtti`` and ``-fno-sanitize=null``. Link must be + performed by ``clang++``, not ``clang``, to make sure C++-specific parts of + the runtime library and C++ standard libraries are present. You can also use the following check groups: - ``-fsanitize=undefined``: All of the checks listed above other than Index: cfe/trunk/include/clang/Basic/DiagnosticDriverKinds.td =================================================================== --- cfe/trunk/include/clang/Basic/DiagnosticDriverKinds.td +++ cfe/trunk/include/clang/Basic/DiagnosticDriverKinds.td @@ -230,7 +230,10 @@ InGroup>; def warn_drv_disabling_vptr_no_rtti_default : Warning< "implicitly disabling vptr sanitizer because rtti wasn't enabled">, - InGroup>; + InGroup; +def warn_drv_disabling_vptr_no_null_check : Warning< + "implicitly disabling vptr sanitizer because null checking wasn't enabled">, + InGroup; def warn_drv_object_size_disabled_O0 : Warning< "the object size sanitizer has no effect at -O0, but is explicitly enabled: %0">, InGroup; Index: cfe/trunk/include/clang/Basic/DiagnosticGroups.td =================================================================== --- cfe/trunk/include/clang/Basic/DiagnosticGroups.td +++ cfe/trunk/include/clang/Basic/DiagnosticGroups.td @@ -27,6 +27,7 @@ def GNUAutoType : DiagGroup<"gnu-auto-type">; def ArrayBounds : DiagGroup<"array-bounds">; def ArrayBoundsPointerArithmetic : DiagGroup<"array-bounds-pointer-arithmetic">; +def AutoDisableVptrSanitizer : DiagGroup<"auto-disable-vptr-sanitizer">; def Availability : DiagGroup<"availability">; def Section : DiagGroup<"section">; def AutoImport : DiagGroup<"auto-import">; Index: cfe/trunk/lib/CodeGen/CGExpr.cpp =================================================================== --- cfe/trunk/lib/CodeGen/CGExpr.cpp +++ cfe/trunk/lib/CodeGen/CGExpr.cpp @@ -604,20 +604,23 @@ auto PtrToAlloca = dyn_cast(Ptr->stripPointerCastsNoFollowAliases()); + llvm::Value *IsNonNull = nullptr; + bool IsGuaranteedNonNull = + SkippedChecks.has(SanitizerKind::Null) || PtrToAlloca; bool AllowNullPointers = TCK == TCK_DowncastPointer || TCK == TCK_Upcast || TCK == TCK_UpcastToVirtualBase; if ((SanOpts.has(SanitizerKind::Null) || AllowNullPointers) && - !SkippedChecks.has(SanitizerKind::Null) && !PtrToAlloca) { + !IsGuaranteedNonNull) { // The glvalue must not be an empty glvalue. - llvm::Value *IsNonNull = Builder.CreateIsNotNull(Ptr); + IsNonNull = Builder.CreateIsNotNull(Ptr); // The IR builder can constant-fold the null check if the pointer points to // a constant. - bool PtrIsNonNull = + IsGuaranteedNonNull = IsNonNull == llvm::ConstantInt::getTrue(getLLVMContext()); // Skip the null check if the pointer is known to be non-null. - if (!PtrIsNonNull) { + if (!IsGuaranteedNonNull) { if (AllowNullPointers) { // When performing pointer casts, it's OK if the value is null. // Skip the remaining checks in that case. @@ -691,12 +694,24 @@ // -- the [pointer or glvalue] is used to access a non-static data member // or call a non-static member function CXXRecordDecl *RD = Ty->getAsCXXRecordDecl(); + bool HasNullCheck = IsGuaranteedNonNull || IsNonNull; if (SanOpts.has(SanitizerKind::Vptr) && - !SkippedChecks.has(SanitizerKind::Vptr) && + !SkippedChecks.has(SanitizerKind::Vptr) && HasNullCheck && (TCK == TCK_MemberAccess || TCK == TCK_MemberCall || TCK == TCK_DowncastPointer || TCK == TCK_DowncastReference || TCK == TCK_UpcastToVirtualBase) && RD && RD->hasDefinition() && RD->isDynamicClass()) { + // Ensure that the pointer is non-null before loading it. If there is no + // compile-time guarantee, reuse the run-time null check. + if (!IsGuaranteedNonNull) { + assert(IsNonNull && "Missing run-time null check"); + if (!Done) + Done = createBasicBlock("vptr.null"); + llvm::BasicBlock *VptrNotNull = createBasicBlock("vptr.not.null"); + Builder.CreateCondBr(IsNonNull, VptrNotNull, Done); + EmitBlock(VptrNotNull); + } + // Compute a hash of the mangled name of the type. // // FIXME: This is not guaranteed to be deterministic! Move to a Index: cfe/trunk/lib/Driver/SanitizerArgs.cpp =================================================================== --- cfe/trunk/lib/Driver/SanitizerArgs.cpp +++ cfe/trunk/lib/Driver/SanitizerArgs.cpp @@ -306,6 +306,13 @@ Kinds &= ~Vptr; } + // Disable -fsanitize=vptr if -fsanitize=null is not enabled (the vptr + // instrumentation is broken without run-time null checks). + if ((Kinds & Vptr) && !(Kinds & Null)) { + Kinds &= ~Vptr; + D.Diag(diag::warn_drv_disabling_vptr_no_null_check); + } + // Check that LTO is enabled if we need it. if ((Kinds & NeedsLTO) && !D.isUsingLTO()) { D.Diag(diag::err_drv_argument_only_allowed_with) Index: cfe/trunk/test/CodeGenCXX/catch-undef-behavior.cpp =================================================================== --- cfe/trunk/test/CodeGenCXX/catch-undef-behavior.cpp +++ cfe/trunk/test/CodeGenCXX/catch-undef-behavior.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -std=c++11 -fsanitize=signed-integer-overflow,integer-divide-by-zero,float-divide-by-zero,shift-base,shift-exponent,unreachable,return,vla-bound,alignment,null,vptr,object-size,float-cast-overflow,bool,enum,array-bounds,function -fsanitize-recover=signed-integer-overflow,integer-divide-by-zero,float-divide-by-zero,shift-base,shift-exponent,vla-bound,alignment,null,vptr,object-size,float-cast-overflow,bool,enum,array-bounds,function -emit-llvm %s -o - -triple x86_64-linux-gnu | opt -instnamer -S | FileCheck %s -// RUN: %clang_cc1 -std=c++11 -fsanitize=vptr,address -fsanitize-recover=vptr,address -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-ASAN -// RUN: %clang_cc1 -std=c++11 -fsanitize=vptr -fsanitize-recover=vptr -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=DOWNCAST-NULL +// RUN: %clang_cc1 -std=c++11 -fsanitize=null,vptr,address -fsanitize-recover=null,vptr,address -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-ASAN +// RUN: %clang_cc1 -std=c++11 -fsanitize=null,vptr -fsanitize-recover=null,vptr -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=DOWNCAST-NULL // RUN: %clang_cc1 -std=c++11 -fsanitize=function -emit-llvm %s -o - -triple x86_64-linux-gnux32 | FileCheck %s --check-prefix=CHECK-X32 // RUN: %clang_cc1 -std=c++11 -fsanitize=function -emit-llvm %s -o - -triple i386-linux-gnu | FileCheck %s --check-prefix=CHECK-X86 Index: cfe/trunk/test/CodeGenCXX/ubsan-devirtualized-calls.cpp =================================================================== --- cfe/trunk/test/CodeGenCXX/ubsan-devirtualized-calls.cpp +++ cfe/trunk/test/CodeGenCXX/ubsan-devirtualized-calls.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++11 -triple %itanium_abi_triple -emit-llvm -fsanitize=vptr %s -o - | FileCheck %s +// RUN: %clang_cc1 -std=c++11 -triple %itanium_abi_triple -emit-llvm -fsanitize=null,vptr %s -o - | FileCheck %s struct Base1 { virtual void f1() {} @@ -64,6 +64,11 @@ // CHECK-NEXT: call void @__ubsan_handle_dynamic_type_cache{{[_a-z]*}}({{.*}} [[UBSAN_TI_DERIVED3]] {{.*}}, i{{[0-9]+}} %[[P1]] static_cast(badp)->f1(); //< No devirt, test 'badp isa Base1'. + // We were able to skip the null check on the first type check because 'p' + // is backed by an alloca. We can't skip the second null check because 'badp' + // is a (bitcast (load ...)). + // CHECK: call void @__ubsan_handle_type_mismatch + // // CHECK: %[[BADP1:[0-9]+]] = ptrtoint %struct.Base1* {{%[0-9]+}} to i{{[0-9]+}}, !nosanitize // CHECK-NEXT: call void @__ubsan_handle_dynamic_type_cache{{[_a-z]*}}({{.*}} [[UBSAN_TI_BASE1]] {{.*}}, i{{[0-9]+}} %[[BADP1]] } @@ -76,6 +81,8 @@ // CHECK-NEXT: call void @__ubsan_handle_dynamic_type_cache{{[_a-z]*}}({{.*}} [[UBSAN_TI_DERIVED4_1]] {{.*}}, i{{[0-9]+}} %[[P1]] static_cast(badp)->f1(); //< Devirt Base1::f1 to Derived4::f1. + // CHECK: call void @__ubsan_handle_type_mismatch + // // CHECK: %[[BADP1:[0-9]+]] = ptrtoint %struct.Derived4* {{%[0-9]+}} to i{{[0-9]+}}, !nosanitize // CHECK-NEXT: call void @__ubsan_handle_dynamic_type_cache{{[_a-z]*}}({{.*}} [[UBSAN_TI_DERIVED4_2]] {{.*}}, i{{[0-9]+}} %[[BADP1]] } Index: cfe/trunk/test/CodeGenCXX/ubsan-type-checks.cpp =================================================================== --- cfe/trunk/test/CodeGenCXX/ubsan-type-checks.cpp +++ cfe/trunk/test/CodeGenCXX/ubsan-type-checks.cpp @@ -1,6 +1,8 @@ // RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=alignment | FileCheck %s -check-prefixes=ALIGN,COMMON // RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=null | FileCheck %s -check-prefixes=NULL,COMMON // RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=object-size | FileCheck %s -check-prefixes=OBJSIZE,COMMON +// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=null,vptr | FileCheck %s -check-prefixes=VPTR +// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=vptr | FileCheck %s -check-prefixes=VPTR_NO_NULL struct A { // COMMON-LABEL: define linkonce_odr void @_ZN1A10do_nothingEv @@ -24,13 +26,55 @@ // NULL: icmp ne %struct.B* %{{.*}}, null, !nosanitize // OBJSIZE-NOT: call i64 @llvm.objectsize + // OBJSIZE: ret void } }; -void force_irgen() { +struct Animal { + virtual const char *speak() = 0; +}; + +struct Cat : Animal { + const char *speak() override { return "meow"; } +}; + +struct Dog : Animal { + const char *speak() override { return "woof"; } +}; + +// VPTR-LABEL: define void @_Z12invalid_castP3Cat +void invalid_cast(Cat *cat = nullptr) { + // First, null check the pointer: + // + // VPTR: [[ICMP:%.*]] = icmp ne %struct.Dog* {{.*}}, null + // VPTR-NEXT: br i1 [[ICMP]] + // VPTR: call void @__ubsan_handle_type_mismatch + // + // Once we're done emitting the null check, reuse the check to see if we can + // proceed to the vptr check: + // + // VPTR: br i1 [[ICMP]] + // VPTR: call void @__ubsan_handle_dynamic_type_cache_miss + auto *badDog = reinterpret_cast(cat); + badDog->speak(); +} + +// VPTR_NO_NULL-LABEL: define void @_Z13invalid_cast2v +void invalid_cast2() { + // We've got a pointer to an alloca, so there's no run-time null check needed. + // VPTR_NO_NULL-NOT: call void @__ubsan_handle_type_mismatch + // VPTR_NO_NULL: call void @__ubsan_handle_dynamic_type_cache_miss + Cat cat; + cat.speak(); +} + +int main() { A a; a.do_nothing(); B b; b.do_nothing(); + + invalid_cast(); + return 0; } Index: cfe/trunk/test/CodeGenCXX/ubsan-vtable-checks.cpp =================================================================== --- cfe/trunk/test/CodeGenCXX/ubsan-vtable-checks.cpp +++ cfe/trunk/test/CodeGenCXX/ubsan-vtable-checks.cpp @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux -emit-llvm -fsanitize=null %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NULL --check-prefix=ITANIUM // RUN: %clang_cc1 -std=c++11 -triple x86_64-windows -emit-llvm -fsanitize=null %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NULL --check-prefix=MSABI -// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux -emit-llvm -fsanitize=vptr %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-VPTR --check-prefix=ITANIUM -// RUN: %clang_cc1 -std=c++11 -triple x86_64-windows -emit-llvm -fsanitize=vptr %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-VPTR --check-prefix=MSABI +// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux -emit-llvm -fsanitize=null,vptr %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-VPTR --check-prefix=ITANIUM +// RUN: %clang_cc1 -std=c++11 -triple x86_64-windows -emit-llvm -fsanitize=null,vptr %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-VPTR --check-prefix=MSABI struct T { virtual ~T() {} virtual int v() { return 1; } Index: cfe/trunk/test/Driver/fsanitize.c =================================================================== --- cfe/trunk/test/Driver/fsanitize.c +++ cfe/trunk/test/Driver/fsanitize.c @@ -58,6 +58,10 @@ // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fno-rtti %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-NO-RTTI // CHECK-UNDEFINED-NO-RTTI-NOT: vptr +// RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fno-sanitize=null %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-NO-NULL +// RUN: %clang -target x86_64-linux-gnu -fsanitize=vptr %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-NO-NULL +// CHECK-VPTR-NO-NULL: warning: implicitly disabling vptr sanitizer because null checking wasn't enabled + // RUN: %clang -target x86_64-linux-gnu -fsanitize=address,thread -fno-rtti %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANA-SANT // CHECK-SANA-SANT: '-fsanitize=address' not allowed with '-fsanitize=thread' @@ -362,8 +366,8 @@ // RUN: %clang -target x86_64-apple-darwin10 -mmacosx-version-min=10.8 -fsanitize=vptr %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-DARWIN-OLD // CHECK-VPTR-DARWIN-OLD: unsupported option '-fsanitize=vptr' for target 'x86_64-apple-darwin10' -// RUN: %clang -target x86_64-apple-darwin10 -mmacosx-version-min=10.9 -fsanitize=alignment,vptr %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-DARWIN-NEW -// CHECK-VPTR-DARWIN-NEW: -fsanitize=alignment,vptr +// RUN: %clang -target x86_64-apple-darwin10 -mmacosx-version-min=10.9 -fsanitize=alignment,null,vptr %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-DARWIN-NEW +// CHECK-VPTR-DARWIN-NEW: -fsanitize=alignment,null,vptr // RUN: %clang -target armv7-apple-ios7 -miphoneos-version-min=7.0 -fsanitize=address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ASAN-IOS // CHECK-ASAN-IOS: -fsanitize=address Index: cfe/trunk/test/Driver/rtti-options.cpp =================================================================== --- cfe/trunk/test/Driver/rtti-options.cpp +++ cfe/trunk/test/Driver/rtti-options.cpp @@ -16,14 +16,14 @@ // Make sure we only error/warn once, when trying to enable vptr and // undefined and have -fno-rtti // RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=undefined -fsanitize=vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR -check-prefix=CHECK-OK %s -// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=vptr %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s -// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=vptr -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s -// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR %s +// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=null,vptr %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s +// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=null,vptr -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s +// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=null,vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR %s // RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=undefined %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s // RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=undefined -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s -// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=vptr %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-WARN %s -// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=vptr -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s -// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR %s +// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=null,vptr %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-WARN %s +// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=null,vptr -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s +// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=null,vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR %s // RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=undefined -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s // Exceptions + no/default rtti