diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -777,8 +777,8 @@ - Introduced the new function ``clang_CXXMethod_isCopyAssignmentOperator``, which identifies whether a method cursor is a copy-assignment operator. -- ``clang_Cursor_getNumTemplateArguments``, ``clang_Cursor_getTemplateArgumentKind``, - ``clang_Cursor_getTemplateArgumentType``, ``clang_Cursor_getTemplateArgumentValue`` and +- ``clang_Cursor_getNumTemplateArguments``, ``clang_Cursor_getTemplateArgumentKind``, + ``clang_Cursor_getTemplateArgumentType``, ``clang_Cursor_getTemplateArgumentValue`` and ``clang_Cursor_getTemplateArgumentUnsignedValue`` now work on struct, class, and partial template specialization cursors in addition to function cursors. @@ -798,6 +798,11 @@ returning uninitialized variables from functions is more aggressively reported. ``-fno-sanitize-memory-param-retval`` restores the previous behavior. +- A new Undefined Behavior Sanitizer check has been implemented: + ``-fsanitize-exception-escape`` (part of ``-fsanitize=undefined``), + which catches cases of C++ exceptions trying to unwind + out of non-unwindable functions. + Core Analysis Improvements ========================== diff --git a/clang/docs/UndefinedBehaviorSanitizer.rst b/clang/docs/UndefinedBehaviorSanitizer.rst --- a/clang/docs/UndefinedBehaviorSanitizer.rst +++ b/clang/docs/UndefinedBehaviorSanitizer.rst @@ -180,6 +180,8 @@ 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=exception-escape``: A C++ exception unwinding out of an + non-unwind function is an undefined behavior. This catches such situations. You can also use the following check groups: - ``-fsanitize=undefined``: All of the checks listed above other than diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def --- a/clang/include/clang/Basic/Sanitizers.def +++ b/clang/include/clang/Basic/Sanitizers.def @@ -107,6 +107,7 @@ SANITIZER("unreachable", Unreachable) SANITIZER("vla-bound", VLABound) SANITIZER("vptr", Vptr) +SANITIZER("exception-escape", ExceptionEscape) // IntegerSanitizer SANITIZER("unsigned-integer-overflow", UnsignedIntegerOverflow) @@ -144,7 +145,7 @@ IntegerDivideByZero | NonnullAttribute | Null | ObjectSize | PointerOverflow | Return | ReturnsNonnullAttribute | Shift | SignedIntegerOverflow | Unreachable | VLABound | Function | - Vptr) + Vptr | ExceptionEscape) // -fsanitize=undefined-trap is an alias for -fsanitize=undefined. SANITIZER_GROUP("undefined-trap", UndefinedTrap, Undefined) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -5368,7 +5368,7 @@ pushFullExprCleanup(NormalEHLifetimeMarker, SRetAlloca, UnusedReturnSizePtr); - llvm::BasicBlock *InvokeDest = CannotThrow ? nullptr : getInvokeDest(); + llvm::BasicBlock *InvokeDest = CannotThrow ? nullptr : getInvokeDest(Loc); SmallVector BundleList = getBundlesForFunclet(CalleePtr); diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -754,7 +754,7 @@ llvm_unreachable("Invalid EHScope Kind!"); } -llvm::BasicBlock *CodeGenFunction::getInvokeDestImpl() { +llvm::BasicBlock *CodeGenFunction::getInvokeDestImpl(SourceLocation Loc) { assert(EHStack.requiresLandingPad()); assert(!EHStack.empty()); @@ -789,7 +789,7 @@ LP = getEHDispatchBlock(EHStack.getInnermostEHScope()); } else { // Build the landing pad for this scope. - LP = EmitLandingPad(); + LP = EmitLandingPad(Loc); } assert(LP); @@ -804,7 +804,7 @@ return LP; } -llvm::BasicBlock *CodeGenFunction::EmitLandingPad() { +llvm::BasicBlock *CodeGenFunction::EmitLandingPad(SourceLocation Loc) { assert(EHStack.requiresLandingPad()); assert(!CGM.getLangOpts().IgnoreExceptions && "LandingPad should not be emitted when -fignore-exceptions are in " @@ -812,7 +812,7 @@ EHScope &innermostEHScope = *EHStack.find(EHStack.getInnermostEHScope()); switch (innermostEHScope.getKind()) { case EHScope::Terminate: - return getTerminateLandingPad(); + return getTerminateLandingPad(Loc); case EHScope::Catch: case EHScope::Cleanup: @@ -943,7 +943,7 @@ Builder.restoreIP(savedIP); return lpad; -} + } static void emitCatchPadBlock(CodeGenFunction &CGF, EHCatchScope &CatchScope) { llvm::BasicBlock *DispatchBlock = CatchScope.getCachedEHDispatchBlock(); @@ -1507,7 +1507,7 @@ CGF.PopCleanupBlock(); } -llvm::BasicBlock *CodeGenFunction::getTerminateLandingPad() { +llvm::BasicBlock *CodeGenFunction::getTerminateLandingPad(SourceLocation Loc) { if (TerminateLandingPad) return TerminateLandingPad; @@ -1517,6 +1517,13 @@ TerminateLandingPad = createBasicBlock("terminate.lpad"); Builder.SetInsertPoint(TerminateLandingPad); + if (SanOpts.has(SanitizerKind::ExceptionEscape)) { + llvm::Constant *CheckSourceLocation = EmitCheckSourceLocation(Loc); + TerminateLandingPadLocPHI = Builder.CreatePHI( + CheckSourceLocation->getType(), /*NumReservedValues=*/10); + // But *DON'T* add it as incoming, `getInvokeDest()` deals with that. + } + // Tell the backend that this is a landing pad. const EHPersonality &Personality = EHPersonality::get(*this); @@ -1530,6 +1537,17 @@ llvm::Value *Exn = nullptr; if (getLangOpts().CPlusPlus) Exn = Builder.CreateExtractValue(LPadInst, 0); + + if (SanOpts.has(SanitizerKind::ExceptionEscape)) { + SanitizerScope SanScope(this); + llvm::Value *DynamicData[] = {TerminateLandingPadLocPHI, Exn}; + // FIXME: can we do anything interesting with `Exn`? + EmitCheck({std::make_pair(llvm::ConstantInt::getFalse(getLLVMContext()), + SanitizerKind::ExceptionEscape)}, + SanitizerHandler::ExceptionEscape, /*StaticData=*/{}, + DynamicData); + } + llvm::CallInst *terminateCall = CGM.getCXXABI().emitTerminateForUnexpectedException(*this, Exn); terminateCall->setDoesNotReturn(); diff --git a/clang/lib/CodeGen/CMakeLists.txt b/clang/lib/CodeGen/CMakeLists.txt --- a/clang/lib/CodeGen/CMakeLists.txt +++ b/clang/lib/CodeGen/CMakeLists.txt @@ -1,3 +1,5 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g3 -ggdb3") + set(LLVM_LINK_COMPONENTS Analysis BitReader diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -134,7 +134,8 @@ SANITIZER_CHECK(SubOverflow, sub_overflow, 0) \ SANITIZER_CHECK(TypeMismatch, type_mismatch, 1) \ SANITIZER_CHECK(AlignmentAssumption, alignment_assumption, 0) \ - SANITIZER_CHECK(VLABoundNotPositive, vla_bound_not_positive, 0) + SANITIZER_CHECK(VLABoundNotPositive, vla_bound_not_positive, 0) \ + SANITIZER_CHECK(ExceptionEscape, exception_escape, 0) enum SanitizerHandler { #define SANITIZER_CHECK(Enum, Name, Version) Enum, @@ -676,9 +677,9 @@ llvm::Value *SEHInfo = nullptr; /// Emits a landing pad for the current EH stack. - llvm::BasicBlock *EmitLandingPad(); + llvm::BasicBlock *EmitLandingPad(SourceLocation Loc); - llvm::BasicBlock *getInvokeDestImpl(); + llvm::BasicBlock *getInvokeDestImpl(SourceLocation Loc); /// Parent loop-based directive for scan directive. const OMPExecutableDirective *OMPParentLoopDirectiveForScan = nullptr; @@ -1958,6 +1959,7 @@ bool requiresReturnValueCheck() const; llvm::BasicBlock *TerminateLandingPad = nullptr; + llvm::PHINode *TerminateLandingPadLocPHI = nullptr; llvm::BasicBlock *TerminateHandler = nullptr; llvm::SmallVector TrapBBs; @@ -2016,9 +2018,20 @@ return UnreachableBlock; } - llvm::BasicBlock *getInvokeDest() { + llvm::BasicBlock *getInvokeDest(SourceLocation Loc = SourceLocation()) { if (!EHStack.requiresLandingPad()) return nullptr; - return getInvokeDestImpl(); + + llvm::BasicBlock *InvokeDest = getInvokeDestImpl(Loc); + + // FIXME: what about FuncletPads? + if (SanOpts.has(SanitizerKind::ExceptionEscape) && + TerminateLandingPadLocPHI) { + llvm::BasicBlock *InvokeBB = Builder.GetInsertBlock(); + llvm::Constant *CheckSourceLocation = EmitCheckSourceLocation(Loc); + TerminateLandingPadLocPHI->addIncoming(CheckSourceLocation, InvokeBB); + } + + return InvokeDest; } bool currentFunctionUsesSEHTry() const { return CurSEHParent != nullptr; } @@ -2389,7 +2402,7 @@ void EmitEndEHSpec(const Decl *D); /// getTerminateLandingPad - Return a landing pad that just calls terminate. - llvm::BasicBlock *getTerminateLandingPad(); + llvm::BasicBlock *getTerminateLandingPad(SourceLocation Loc); /// getTerminateLandingPad - Return a cleanup funclet that just calls /// terminate. diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -58,8 +58,9 @@ SanitizerKind::Undefined | SanitizerKind::Integer | SanitizerKind::ImplicitConversion | SanitizerKind::Nullability | SanitizerKind::FloatDivideByZero | SanitizerKind::ObjCCast; -static const SanitizerMask Unrecoverable = - SanitizerKind::Unreachable | SanitizerKind::Return; +static const SanitizerMask Unrecoverable = SanitizerKind::Unreachable | + SanitizerKind::Return | + SanitizerKind::ExceptionEscape; static const SanitizerMask AlwaysRecoverable = SanitizerKind::KernelAddress | SanitizerKind::KernelHWAddress | SanitizerKind::KCFI; diff --git a/clang/test/CodeGenCXX/catch-exception-escape.cpp b/clang/test/CodeGenCXX/catch-exception-escape.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/catch-exception-escape.cpp @@ -0,0 +1,177 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py +// RUN: %clang_cc1 -emit-llvm %s -o - -triple x86_64-linux-gnu -fexceptions -fcxx-exceptions | FileCheck %s --check-prefixes=CHECK-NOSANITIZE +// RUN: %clang_cc1 -fsanitize=exception-escape -emit-llvm %s -o - -triple x86_64-linux-gnu -fexceptions -fcxx-exceptions | FileCheck %s --check-prefixes=CHECK-SANITIZE-NORECOVER +// RUN: %clang_cc1 -fsanitize=exception-escape -fno-sanitize-recover=exception-escape -emit-llvm %s -o - -triple x86_64-linux-gnu -fexceptions -fcxx-exceptions | FileCheck %s --check-prefixes=CHECK-SANITIZE-NORECOVER +// RUN: %clang_cc1 -fsanitize=exception-escape -fsanitize-recover=exception-escape -emit-llvm %s -o - -triple x86_64-linux-gnu -fexceptions -fcxx-exceptions | FileCheck %s --check-prefixes=CHECK-SANITIZE-RECOVER +// RUN: %clang_cc1 -fsanitize=exception-escape -fsanitize-trap=exception-escape -emit-llvm %s -o - -triple x86_64-linux-gnu -fexceptions -fcxx-exceptions | FileCheck %s --check-prefixes=CHECK-SANITIZE-TRAP + +void thrower(); +void ok(); + +// CHECK-NOSANITIZE-LABEL: @_Z7footguni( +// CHECK-NOSANITIZE-NEXT: entry: +// CHECK-NOSANITIZE-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4 +// CHECK-NOSANITIZE-NEXT: store i32 [[X:%.*]], ptr [[X_ADDR]], align 4 +// CHECK-NOSANITIZE-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-NOSANITIZE-NEXT: [[CMP:%.*]] = icmp eq i32 [[TMP0]], 2 +// CHECK-NOSANITIZE-NEXT: br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +// CHECK-NOSANITIZE: if.then: +// CHECK-NOSANITIZE-NEXT: invoke void @_Z7throwerv() +// CHECK-NOSANITIZE-NEXT: to label [[INVOKE_CONT:%.*]] unwind label [[TERMINATE_LPAD:%.*]] +// CHECK-NOSANITIZE: invoke.cont: +// CHECK-NOSANITIZE-NEXT: br label [[IF_END]] +// CHECK-NOSANITIZE: if.end: +// CHECK-NOSANITIZE-NEXT: [[TMP1:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-NOSANITIZE-NEXT: [[CMP1:%.*]] = icmp eq i32 [[TMP1]], 3 +// CHECK-NOSANITIZE-NEXT: br i1 [[CMP1]], label [[IF_THEN2:%.*]], label [[IF_END4:%.*]] +// CHECK-NOSANITIZE: if.then2: +// CHECK-NOSANITIZE-NEXT: invoke void @_Z7throwerv() +// CHECK-NOSANITIZE-NEXT: to label [[INVOKE_CONT3:%.*]] unwind label [[TERMINATE_LPAD]] +// CHECK-NOSANITIZE: invoke.cont3: +// CHECK-NOSANITIZE-NEXT: br label [[IF_END4]] +// CHECK-NOSANITIZE: if.end4: +// CHECK-NOSANITIZE-NEXT: invoke void @_Z2okv() +// CHECK-NOSANITIZE-NEXT: to label [[INVOKE_CONT5:%.*]] unwind label [[TERMINATE_LPAD]] +// CHECK-NOSANITIZE: invoke.cont5: +// CHECK-NOSANITIZE-NEXT: ret void +// CHECK-NOSANITIZE: terminate.lpad: +// CHECK-NOSANITIZE-NEXT: [[TMP2:%.*]] = landingpad { ptr, i32 } +// CHECK-NOSANITIZE-NEXT: catch ptr null +// CHECK-NOSANITIZE-NEXT: [[TMP3:%.*]] = extractvalue { ptr, i32 } [[TMP2]], 0 +// CHECK-NOSANITIZE-NEXT: call void @__clang_call_terminate(ptr [[TMP3]]) #[[ATTR3:[0-9]+]] +// CHECK-NOSANITIZE-NEXT: unreachable +// +// CHECK-SANITIZE-NORECOVER-LABEL: @_Z7footguni( +// CHECK-SANITIZE-NORECOVER-NEXT: entry: +// CHECK-SANITIZE-NORECOVER-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4 +// CHECK-SANITIZE-NORECOVER-NEXT: [[TMP:%.*]] = alloca { ptr, i32, i32 }, align 8 +// CHECK-SANITIZE-NORECOVER-NEXT: store i32 [[X:%.*]], ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-NORECOVER-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-NORECOVER-NEXT: [[CMP:%.*]] = icmp eq i32 [[TMP0]], 2 +// CHECK-SANITIZE-NORECOVER-NEXT: br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +// CHECK-SANITIZE-NORECOVER: if.then: +// CHECK-SANITIZE-NORECOVER-NEXT: invoke void @_Z7throwerv() +// CHECK-SANITIZE-NORECOVER-NEXT: to label [[INVOKE_CONT:%.*]] unwind label [[TERMINATE_LPAD:%.*]] +// CHECK-SANITIZE-NORECOVER: invoke.cont: +// CHECK-SANITIZE-NORECOVER-NEXT: br label [[IF_END]] +// CHECK-SANITIZE-NORECOVER: handler.exception_escape: +// CHECK-SANITIZE-NORECOVER-NEXT: store { ptr, i32, i32 } [[TMP4:%.*]], ptr [[TMP]], align 8, !nosanitize !2 +// CHECK-SANITIZE-NORECOVER-NEXT: [[TMP1:%.*]] = ptrtoint ptr [[TMP]] to i64, !nosanitize !2 +// CHECK-SANITIZE-NORECOVER-NEXT: [[TMP2:%.*]] = ptrtoint ptr [[TMP6:%.*]] to i64, !nosanitize !2 +// CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_exception_escape_abort(i64 [[TMP1]], i64 [[TMP2]]) #[[ATTR4:[0-9]+]], !nosanitize !2 +// CHECK-SANITIZE-NORECOVER-NEXT: unreachable, !nosanitize !2 +// CHECK-SANITIZE-NORECOVER: cont: +// CHECK-SANITIZE-NORECOVER-NEXT: call void @__clang_call_terminate(ptr [[TMP6]]) #[[ATTR4]] +// CHECK-SANITIZE-NORECOVER-NEXT: unreachable +// CHECK-SANITIZE-NORECOVER: if.end: +// CHECK-SANITIZE-NORECOVER-NEXT: [[TMP3:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-NORECOVER-NEXT: [[CMP1:%.*]] = icmp eq i32 [[TMP3]], 3 +// CHECK-SANITIZE-NORECOVER-NEXT: br i1 [[CMP1]], label [[IF_THEN2:%.*]], label [[IF_END4:%.*]] +// CHECK-SANITIZE-NORECOVER: if.then2: +// CHECK-SANITIZE-NORECOVER-NEXT: invoke void @_Z7throwerv() +// CHECK-SANITIZE-NORECOVER-NEXT: to label [[INVOKE_CONT3:%.*]] unwind label [[TERMINATE_LPAD]] +// CHECK-SANITIZE-NORECOVER: invoke.cont3: +// CHECK-SANITIZE-NORECOVER-NEXT: br label [[IF_END4]] +// CHECK-SANITIZE-NORECOVER: if.end4: +// CHECK-SANITIZE-NORECOVER-NEXT: invoke void @_Z2okv() +// CHECK-SANITIZE-NORECOVER-NEXT: to label [[INVOKE_CONT5:%.*]] unwind label [[TERMINATE_LPAD]] +// CHECK-SANITIZE-NORECOVER: invoke.cont5: +// CHECK-SANITIZE-NORECOVER-NEXT: ret void +// CHECK-SANITIZE-NORECOVER: terminate.lpad: +// CHECK-SANITIZE-NORECOVER-NEXT: [[TMP4]] = phi { ptr, i32, i32 } [ { ptr @.src, i32 101, i32 5 }, [[IF_THEN]] ], [ { ptr @.src, i32 201, i32 5 }, [[IF_THEN2]] ], [ { ptr @.src, i32 202, i32 3 }, [[IF_END4]] ] +// CHECK-SANITIZE-NORECOVER-NEXT: [[TMP5:%.*]] = landingpad { ptr, i32 } +// CHECK-SANITIZE-NORECOVER-NEXT: catch ptr null +// CHECK-SANITIZE-NORECOVER-NEXT: [[TMP6]] = extractvalue { ptr, i32 } [[TMP5]], 0 +// CHECK-SANITIZE-NORECOVER-NEXT: br i1 false, label [[CONT:%.*]], label [[HANDLER_EXCEPTION_ESCAPE:%.*]], !prof [[PROF3:![0-9]+]], !nosanitize !2 +// +// CHECK-SANITIZE-RECOVER-LABEL: @_Z7footguni( +// CHECK-SANITIZE-RECOVER-NEXT: entry: +// CHECK-SANITIZE-RECOVER-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4 +// CHECK-SANITIZE-RECOVER-NEXT: [[TMP:%.*]] = alloca { ptr, i32, i32 }, align 8 +// CHECK-SANITIZE-RECOVER-NEXT: store i32 [[X:%.*]], ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-RECOVER-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-RECOVER-NEXT: [[CMP:%.*]] = icmp eq i32 [[TMP0]], 2 +// CHECK-SANITIZE-RECOVER-NEXT: br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +// CHECK-SANITIZE-RECOVER: if.then: +// CHECK-SANITIZE-RECOVER-NEXT: invoke void @_Z7throwerv() +// CHECK-SANITIZE-RECOVER-NEXT: to label [[INVOKE_CONT:%.*]] unwind label [[TERMINATE_LPAD:%.*]] +// CHECK-SANITIZE-RECOVER: invoke.cont: +// CHECK-SANITIZE-RECOVER-NEXT: br label [[IF_END]] +// CHECK-SANITIZE-RECOVER: handler.exception_escape: +// CHECK-SANITIZE-RECOVER-NEXT: store { ptr, i32, i32 } [[TMP4:%.*]], ptr [[TMP]], align 8, !nosanitize !2 +// CHECK-SANITIZE-RECOVER-NEXT: [[TMP1:%.*]] = ptrtoint ptr [[TMP]] to i64, !nosanitize !2 +// CHECK-SANITIZE-RECOVER-NEXT: [[TMP2:%.*]] = ptrtoint ptr [[TMP6:%.*]] to i64, !nosanitize !2 +// CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_exception_escape(i64 [[TMP1]], i64 [[TMP2]]) #[[ATTR4:[0-9]+]], !nosanitize !2 +// CHECK-SANITIZE-RECOVER-NEXT: br label [[CONT:%.*]], !nosanitize !2 +// CHECK-SANITIZE-RECOVER: cont: +// CHECK-SANITIZE-RECOVER-NEXT: call void @__clang_call_terminate(ptr [[TMP6]]) #[[ATTR5:[0-9]+]] +// CHECK-SANITIZE-RECOVER-NEXT: unreachable +// CHECK-SANITIZE-RECOVER: if.end: +// CHECK-SANITIZE-RECOVER-NEXT: [[TMP3:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-RECOVER-NEXT: [[CMP1:%.*]] = icmp eq i32 [[TMP3]], 3 +// CHECK-SANITIZE-RECOVER-NEXT: br i1 [[CMP1]], label [[IF_THEN2:%.*]], label [[IF_END4:%.*]] +// CHECK-SANITIZE-RECOVER: if.then2: +// CHECK-SANITIZE-RECOVER-NEXT: invoke void @_Z7throwerv() +// CHECK-SANITIZE-RECOVER-NEXT: to label [[INVOKE_CONT3:%.*]] unwind label [[TERMINATE_LPAD]] +// CHECK-SANITIZE-RECOVER: invoke.cont3: +// CHECK-SANITIZE-RECOVER-NEXT: br label [[IF_END4]] +// CHECK-SANITIZE-RECOVER: if.end4: +// CHECK-SANITIZE-RECOVER-NEXT: invoke void @_Z2okv() +// CHECK-SANITIZE-RECOVER-NEXT: to label [[INVOKE_CONT5:%.*]] unwind label [[TERMINATE_LPAD]] +// CHECK-SANITIZE-RECOVER: invoke.cont5: +// CHECK-SANITIZE-RECOVER-NEXT: ret void +// CHECK-SANITIZE-RECOVER: terminate.lpad: +// CHECK-SANITIZE-RECOVER-NEXT: [[TMP4]] = phi { ptr, i32, i32 } [ { ptr @.src, i32 101, i32 5 }, [[IF_THEN]] ], [ { ptr @.src, i32 201, i32 5 }, [[IF_THEN2]] ], [ { ptr @.src, i32 202, i32 3 }, [[IF_END4]] ] +// CHECK-SANITIZE-RECOVER-NEXT: [[TMP5:%.*]] = landingpad { ptr, i32 } +// CHECK-SANITIZE-RECOVER-NEXT: catch ptr null +// CHECK-SANITIZE-RECOVER-NEXT: [[TMP6]] = extractvalue { ptr, i32 } [[TMP5]], 0 +// CHECK-SANITIZE-RECOVER-NEXT: br i1 false, label [[CONT]], label [[HANDLER_EXCEPTION_ESCAPE:%.*]], !prof [[PROF3:![0-9]+]], !nosanitize !2 +// +// CHECK-SANITIZE-TRAP-LABEL: @_Z7footguni( +// CHECK-SANITIZE-TRAP-NEXT: entry: +// CHECK-SANITIZE-TRAP-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4 +// CHECK-SANITIZE-TRAP-NEXT: store i32 [[X:%.*]], ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-TRAP-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-TRAP-NEXT: [[CMP:%.*]] = icmp eq i32 [[TMP0]], 2 +// CHECK-SANITIZE-TRAP-NEXT: br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +// CHECK-SANITIZE-TRAP: if.then: +// CHECK-SANITIZE-TRAP-NEXT: invoke void @_Z7throwerv() +// CHECK-SANITIZE-TRAP-NEXT: to label [[INVOKE_CONT:%.*]] unwind label [[TERMINATE_LPAD:%.*]] +// CHECK-SANITIZE-TRAP: invoke.cont: +// CHECK-SANITIZE-TRAP-NEXT: br label [[IF_END]] +// CHECK-SANITIZE-TRAP: trap: +// CHECK-SANITIZE-TRAP-NEXT: call void @llvm.ubsantrap(i8 25) #[[ATTR4:[0-9]+]], !nosanitize !2 +// CHECK-SANITIZE-TRAP-NEXT: unreachable, !nosanitize !2 +// CHECK-SANITIZE-TRAP: cont: +// CHECK-SANITIZE-TRAP-NEXT: call void @__clang_call_terminate(ptr [[TMP4:%.*]]) #[[ATTR4]] +// CHECK-SANITIZE-TRAP-NEXT: unreachable +// CHECK-SANITIZE-TRAP: if.end: +// CHECK-SANITIZE-TRAP-NEXT: [[TMP1:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-SANITIZE-TRAP-NEXT: [[CMP1:%.*]] = icmp eq i32 [[TMP1]], 3 +// CHECK-SANITIZE-TRAP-NEXT: br i1 [[CMP1]], label [[IF_THEN2:%.*]], label [[IF_END4:%.*]] +// CHECK-SANITIZE-TRAP: if.then2: +// CHECK-SANITIZE-TRAP-NEXT: invoke void @_Z7throwerv() +// CHECK-SANITIZE-TRAP-NEXT: to label [[INVOKE_CONT3:%.*]] unwind label [[TERMINATE_LPAD]] +// CHECK-SANITIZE-TRAP: invoke.cont3: +// CHECK-SANITIZE-TRAP-NEXT: br label [[IF_END4]] +// CHECK-SANITIZE-TRAP: if.end4: +// CHECK-SANITIZE-TRAP-NEXT: invoke void @_Z2okv() +// CHECK-SANITIZE-TRAP-NEXT: to label [[INVOKE_CONT5:%.*]] unwind label [[TERMINATE_LPAD]] +// CHECK-SANITIZE-TRAP: invoke.cont5: +// CHECK-SANITIZE-TRAP-NEXT: ret void +// CHECK-SANITIZE-TRAP: terminate.lpad: +// CHECK-SANITIZE-TRAP-NEXT: [[TMP2:%.*]] = phi { ptr, i32, i32 } [ { ptr @.src, i32 101, i32 5 }, [[IF_THEN]] ], [ { ptr @.src, i32 201, i32 5 }, [[IF_THEN2]] ], [ { ptr @.src, i32 202, i32 3 }, [[IF_END4]] ] +// CHECK-SANITIZE-TRAP-NEXT: [[TMP3:%.*]] = landingpad { ptr, i32 } +// CHECK-SANITIZE-TRAP-NEXT: catch ptr null +// CHECK-SANITIZE-TRAP-NEXT: [[TMP4]] = extractvalue { ptr, i32 } [[TMP3]], 0 +// CHECK-SANITIZE-TRAP-NEXT: br i1 false, label [[CONT:%.*]], label [[TRAP:%.*]], !nosanitize !2 +// +void footgun(int x) noexcept { +#line 100 + if (x == 2) + thrower(); +#line 200 + if (x == 3) + thrower(); + ok(); +} diff --git a/compiler-rt/lib/ubsan/ubsan_checks.inc b/compiler-rt/lib/ubsan/ubsan_checks.inc --- a/compiler-rt/lib/ubsan/ubsan_checks.inc +++ b/compiler-rt/lib/ubsan/ubsan_checks.inc @@ -69,3 +69,4 @@ "nullability-arg") UBSAN_CHECK(DynamicTypeMismatch, "dynamic-type-mismatch", "vptr") UBSAN_CHECK(CFIBadType, "cfi-bad-type", "cfi") +UBSAN_CHECK(ExceptionEscape, "exception-escape", "exception-escape") diff --git a/compiler-rt/lib/ubsan/ubsan_handlers.h b/compiler-rt/lib/ubsan/ubsan_handlers.h --- a/compiler-rt/lib/ubsan/ubsan_handlers.h +++ b/compiler-rt/lib/ubsan/ubsan_handlers.h @@ -99,6 +99,13 @@ /// \brief Handle reaching the end of a value-returning function. UNRECOVERABLE(missing_return, UnreachableData *Data) +struct ExceptionEscapeData { + SourceLocation Loc; +}; + +/// \brief Handle exception escaping out of C++ `noexcept` function. +UNRECOVERABLE(exception_escape, ExceptionEscapeData *Data, ValueHandle Exn) + struct VLABoundData { SourceLocation Loc; const TypeDescriptor &Type; diff --git a/compiler-rt/lib/ubsan/ubsan_handlers.cpp b/compiler-rt/lib/ubsan/ubsan_handlers.cpp --- a/compiler-rt/lib/ubsan/ubsan_handlers.cpp +++ b/compiler-rt/lib/ubsan/ubsan_handlers.cpp @@ -433,6 +433,17 @@ Die(); } +void __ubsan::__ubsan_handle_exception_escape(ExceptionEscapeData *Data, + ValueHandle Exn) { + GET_REPORT_OPTIONS(true); + ErrorType ET = ErrorType::ExceptionEscape; + ScopedReport R(Opts, Data->Loc, ET); + Diag(Data->Loc, DL_Error, ET, + "exception escapes out of function that should not throw exception"); + // FIXME: can we do anything useful with the \p Exn? + Die(); +} + static void handleVLABoundNotPositive(VLABoundData *Data, ValueHandle Bound, ReportOptions Opts) { SourceLocation Loc = Data->Loc.acquire(); diff --git a/compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp b/compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp --- a/compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp +++ b/compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp @@ -141,3 +141,4 @@ HANDLER(nullability_return, "nullability-return") HANDLER(pointer_overflow, "pointer-overflow") HANDLER(cfi_check_fail, "cfi-check-fail") +HANDLER(exception_escape, "exception-escape") diff --git a/compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp b/compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp @@ -0,0 +1,33 @@ +// RUN: %clangxx -fsanitize=exception-escape %s -O3 -o %t +// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefixes=CHECK-ALL,CHECK-ONE +// RUN: not %run %t a 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefixes=CHECK-ALL,CHECK-TWO +// RUN: not %run %t a b 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefixes=CHECK-ALL,CHECK-THREE + +#include + +void thrower() { throw 0; } + +void footgun(int x) noexcept { +#line 100 + if (x == 2) + thrower(); +#line 200 + if (x == 3) + thrower(); +} + +// CHECK-ALL: TEST + +// CHECK-ONE-EMPTY: +// CHECK-TWO: exception-escape.cpp:101:5: runtime error: exception escapes out of function that should not throw exception +// CHECK-THREE: exception-escape.cpp:201:5: runtime error: exception escapes out of function that should not throw exception + +int main(int argc, char **argv) { + fprintf(stderr, "TEST\n"); + + try { + footgun(argc); + } catch (...) { + } + return 0; +}