lebedev.ri created this revision.
lebedev.ri added reviewers: rjmccall, morehouse, aaron.ballman, dvyukov, 
MaskRay, vsk, Sanitizers.
lebedev.ri added a project: LLVM.
Herald added subscribers: Enna1, StephenFan, dberris.
Herald added a project: All.
lebedev.ri requested review of this revision.
Herald added projects: clang, Sanitizers.
Herald added a subscriber: cfe-commits.

I've stumbled into this in 
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=52989&q=label%3AProj-librawspeed
which manifested as an obscure leak, and originated from a seemingly simple 
refactoring:
https://github.com/darktable-org/rawspeed/commit/1fd09b9cffbddc65753eb523f7ba5528d35fe34d#diff-c832cc8366d36ca1165ecef7f4a256a2643ec09c4405a1238222a4529df619a1R172-R174

Reduced, this looked like:

  #include <vector>
  
  std::vector<int> handle() {
    std::vector<int> v(42, 42); // this somehow leaks
    return v;
  }
  
  __attribute__((pure)) // double yikes
  std::vector<int> footgun(int argc) {
    std::vector<int> v(24, 24);
    if(argc != 42)
      throw int(0); // yikes
    return v;
  }
  
  int main(int argc, char* argv[]) {
      try {
          auto v = handle();
          auto v2 = footgun(argc);
      } catch(...) {}
      return 0;
  }

https://godbolt.org/z/zdavdKnfa

Surprisingly, we did not handle it before.
I'm not yet sure if we can pretty-print the actual exception on compiler-rt 
side.

(I may need to update a couple tests still)


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D137381

Files:
  clang/docs/ReleaseNotes.rst
  clang/docs/UndefinedBehaviorSanitizer.rst
  clang/include/clang/Basic/Sanitizers.def
  clang/lib/CodeGen/CGCall.cpp
  clang/lib/CodeGen/CGException.cpp
  clang/lib/CodeGen/CMakeLists.txt
  clang/lib/CodeGen/CodeGenFunction.h
  clang/lib/Driver/SanitizerArgs.cpp
  clang/test/CodeGenCXX/catch-exception-escape.cpp
  compiler-rt/lib/ubsan/ubsan_checks.inc
  compiler-rt/lib/ubsan/ubsan_handlers.cpp
  compiler-rt/lib/ubsan/ubsan_handlers.h
  compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp
  compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp

Index: compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp
===================================================================
--- /dev/null
+++ 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 <stdio.h>
+
+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;
+}
Index: compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp
===================================================================
--- compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp
+++ 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")
Index: compiler-rt/lib/ubsan/ubsan_handlers.h
===================================================================
--- compiler-rt/lib/ubsan/ubsan_handlers.h
+++ 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;
Index: compiler-rt/lib/ubsan/ubsan_handlers.cpp
===================================================================
--- compiler-rt/lib/ubsan/ubsan_handlers.cpp
+++ 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();
Index: compiler-rt/lib/ubsan/ubsan_checks.inc
===================================================================
--- compiler-rt/lib/ubsan/ubsan_checks.inc
+++ 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")
Index: clang/test/CodeGenCXX/catch-exception-escape.cpp
===================================================================
--- /dev/null
+++ 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();
+}
Index: clang/lib/Driver/SanitizerArgs.cpp
===================================================================
--- clang/lib/Driver/SanitizerArgs.cpp
+++ 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;
Index: clang/lib/CodeGen/CodeGenFunction.h
===================================================================
--- clang/lib/CodeGen/CodeGenFunction.h
+++ 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<llvm::BasicBlock *, 2> 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.
Index: clang/lib/CodeGen/CMakeLists.txt
===================================================================
--- clang/lib/CodeGen/CMakeLists.txt
+++ 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
Index: clang/lib/CodeGen/CGException.cpp
===================================================================
--- clang/lib/CodeGen/CGException.cpp
+++ 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();
Index: clang/lib/CodeGen/CGCall.cpp
===================================================================
--- clang/lib/CodeGen/CGCall.cpp
+++ clang/lib/CodeGen/CGCall.cpp
@@ -5368,7 +5368,7 @@
     pushFullExprCleanup<CallLifetimeEnd>(NormalEHLifetimeMarker, SRetAlloca,
                                          UnusedReturnSizePtr);
 
-  llvm::BasicBlock *InvokeDest = CannotThrow ? nullptr : getInvokeDest();
+  llvm::BasicBlock *InvokeDest = CannotThrow ? nullptr : getInvokeDest(Loc);
 
   SmallVector<llvm::OperandBundleDef, 1> BundleList =
       getBundlesForFunclet(CalleePtr);
Index: clang/include/clang/Basic/Sanitizers.def
===================================================================
--- clang/include/clang/Basic/Sanitizers.def
+++ 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)
Index: clang/docs/UndefinedBehaviorSanitizer.rst
===================================================================
--- clang/docs/UndefinedBehaviorSanitizer.rst
+++ 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
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ 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
 ==========================
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to