lebedev.ri updated this revision to Diff 475649.
lebedev.ri marked an inline comment as done.
lebedev.ri added a comment.

@rjmccall thank you for taking a look!
I do believe this now does the right thing.

I would like to keep this patch minimal.
I have not looked into `-fno-exceptions`,
and i'm not sure about funclets.
I will need to double-check tests.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D137381/new/

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/CGCleanup.cpp
  clang/lib/CodeGen/CGCleanup.h
  clang/lib/CodeGen/CGException.cpp
  clang/lib/CodeGen/CGExpr.cpp
  clang/lib/CodeGen/CodeGenFunction.cpp
  clang/lib/CodeGen/CodeGenFunction.h
  clang/lib/CodeGen/EHScopeStack.h
  clang/lib/Driver/SanitizerArgs.cpp
  clang/test/CodeGenCXX/catch-exception-escape.cpp
  clang/test/Driver/fsanitize.c
  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
  compiler-rt/test/ubsan_minimal/TestCases/exception-escape.cpp

Index: compiler-rt/test/ubsan_minimal/TestCases/exception-escape.cpp
===================================================================
--- /dev/null
+++ compiler-rt/test/ubsan_minimal/TestCases/exception-escape.cpp
@@ -0,0 +1,47 @@
+// RUN: %clangxx -fsanitize=exception-escape %s -O3 -o %t
+// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not="exception-escape" --check-prefixes=CHECK-ALL,CHECK-ONE
+// RUN: not --crash %run %t a 2>&1 | FileCheck %s --implicit-check-not="exception-escape" --check-prefixes=CHECK-ALL,CHECK-TWO
+// RUN: not --crash %run %t a b 2>&1 | FileCheck %s --implicit-check-not="exception-escape" --check-prefixes=CHECK-ALL,CHECK-THREE
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void thrower() { throw 0; }
+
+void nonthrower() noexcept {}
+
+// pure functions are generally side-effect free,
+// so we need to be smart to defeat optimizer
+// from completely dropping the call to the function...
+
+void *__attribute__((pure)) footgun(int x) {
+  if (x == 2)
+    thrower();
+  if (x == 3)
+    thrower();
+  nonthrower();
+  fprintf(stderr, "SUCCESS\n"); // Should be here, not in `main()`.
+  return malloc(1);
+}
+
+// CHECK-ALL: TEST
+
+// CHECK-ONE: SUCCESS
+// CHECK-TWO: exception-escape
+// CHECK-THREE: exception-escape
+
+int main(int argc, char **argv) {
+  bool status = 0;
+  void *mem = nullptr;
+
+  fprintf(stderr, "TEST\n");
+
+  try {
+    mem = footgun(argc);
+    status = mem != 0;
+  } catch (...) {
+  }
+
+  free(mem);
+  return status;
+}
Index: compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp
===================================================================
--- /dev/null
+++ compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp
@@ -0,0 +1,96 @@
+// 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 1 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefixes=CHECK-ALL,CHECK-TWO
+// RUN: not %run %t 1 2 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefixes=CHECK-ALL,CHECK-THREE
+// RUN: not %run %t 1 2 3 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefixes=CHECK-ALL,CHECK-FOUR
+// RUN: not %run %t 1 2 3 4 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefixes=CHECK-ALL,CHECK-FIVE
+// RUN: not %run %t 1 2 3 4 5 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefixes=CHECK-ALL,CHECK-SIX
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void thrower() { throw 0; }
+
+void maybe_throws() {}
+
+void nonthrower() noexcept {}
+
+// pure functions are generally side-effect free,
+// so we need to be smart to defeat optimizer
+// from completely dropping the call to the function...
+
+void *__attribute__((pure)) footgun(int x) {
+  if (x == 2)
+#line 100
+    thrower();
+
+  if (x == 3)
+#line 200
+    thrower();
+
+  nonthrower();
+
+  if (x == 4) {
+    try {
+#line 300
+      thrower();
+    } catch (...) {
+#line 400
+      throw;
+    }
+  }
+
+  if (x == 5) {
+    try {
+#line 500
+      thrower();
+    } catch (...) {
+#line 600
+      maybe_throws();
+#line 700
+      throw;
+    }
+  }
+
+  if (x == 6) {
+    try {
+#line 800
+      thrower();
+    } catch (...) {
+#line 900
+      thrower();
+#line 1000
+      throw;
+    }
+  }
+
+  fprintf(stderr, "SUCCESS\n"); // Should be here, not in `main()`.
+  return malloc(1);
+}
+
+// CHECK-ALL: TEST
+
+// CHECK-ONE: SUCCESS
+// CHECK-TWO: exception-escape.cpp:100:5: runtime error: exception escapes out of function that should not throw exception
+// CHECK-THREE: exception-escape.cpp:200:5: runtime error: exception escapes out of function that should not throw exception
+// CHECK-FOUR: exception-escape.cpp:300:7: runtime error: exception escapes out of function that should not throw exception
+// CHECK-FIVE: exception-escape.cpp:500:7: runtime error: exception escapes out of function that should not throw exception
+// CHECK-SIX: exception-escape.cpp:900:7: runtime error: exception escapes out of function that should not throw exception
+
+// CHECK-ALL: TEST
+
+int main(int argc, char **argv) {
+  bool status = 0;
+  void *mem = nullptr;
+
+  fprintf(stderr, "TEST\n");
+
+  try {
+    mem = footgun(argc);
+    status = mem != 0;
+  } catch (...) {
+  }
+
+  free(mem);
+  return status;
+}
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/Driver/fsanitize.c
===================================================================
--- clang/test/Driver/fsanitize.c
+++ clang/test/Driver/fsanitize.c
@@ -5,15 +5,15 @@
 // RUN: %clang --target=x86_64-linux-gnu -fsanitize-undefined-trap-on-error -fsanitize=undefined-trap %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-TRAP
 // RUN: %clang --target=x86_64-linux-gnu -fsanitize-trap -fsanitize=undefined-trap %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-TRAP
 // CHECK-UNDEFINED-TRAP-NOT: -fsanitize-recover
-// CHECK-UNDEFINED-TRAP: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function),?){18}"}}
-// CHECK-UNDEFINED-TRAP: "-fsanitize-trap=alignment,array-bounds,bool,builtin,enum,float-cast-overflow,function,integer-divide-by-zero,nonnull-attribute,null,pointer-overflow,return,returns-nonnull-attribute,shift-base,shift-exponent,signed-integer-overflow,unreachable,vla-bound"
-// CHECK-UNDEFINED-TRAP2: "-fsanitize-trap=alignment,array-bounds,bool,builtin,enum,float-cast-overflow,function,integer-divide-by-zero,nonnull-attribute,null,pointer-overflow,return,returns-nonnull-attribute,shift-base,shift-exponent,unreachable,vla-bound"
+// CHECK-UNDEFINED-TRAP: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function|exception-escape),?){19}"}}
+// CHECK-UNDEFINED-TRAP: "-fsanitize-trap=alignment,array-bounds,bool,builtin,enum,float-cast-overflow,function,integer-divide-by-zero,nonnull-attribute,null,pointer-overflow,return,returns-nonnull-attribute,shift-base,shift-exponent,signed-integer-overflow,unreachable,vla-bound,exception-escape"
+// CHECK-UNDEFINED-TRAP2: "-fsanitize-trap=alignment,array-bounds,bool,builtin,enum,float-cast-overflow,function,integer-divide-by-zero,nonnull-attribute,null,pointer-overflow,return,returns-nonnull-attribute,shift-base,shift-exponent,unreachable,vla-bound,exception-escape"
 
 // RUN: %clang --target=x86_64-linux-gnu -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED
-// CHECK-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|vptr|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){19}"}}
+// CHECK-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|vptr|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|exception-escape),?){20}"}}
 
 // RUN: %clang --target=x86_64-apple-darwin10 -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-DARWIN
-// CHECK-UNDEFINED-DARWIN: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){18}"}}
+// CHECK-UNDEFINED-DARWIN: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|exception-escape),?){19}"}}
 
 // RUN: %clang --target=i386-pc-win32 -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-UNDEFINED-WIN32,CHECK-UNDEFINED-MSVC
 // RUN: %clang --target=i386-pc-win32 -fsanitize=undefined -x c++ %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-UNDEFINED-WIN32,CHECK-UNDEFINED-WIN-CXX,CHECK-UNDEFINED-MSVC
@@ -24,8 +24,8 @@
 // CHECK-UNDEFINED-WIN64: "--dependent-lib={{[^"]*}}ubsan_standalone{{(-x86_64)?}}.lib"
 // CHECK-UNDEFINED-WIN64-MINGW: "--dependent-lib={{[^"]*}}libclang_rt.ubsan_standalone{{(-x86_64)?}}.a"
 // CHECK-UNDEFINED-WIN-CXX: "--dependent-lib={{[^"]*}}ubsan_standalone_cxx{{[^"]*}}.lib"
-// CHECK-UNDEFINED-MSVC-SAME: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){17}"}}
-// CHECK-UNDEFINED-WIN64-MINGW-SAME: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|vptr),?){18}"}}
+// CHECK-UNDEFINED-MSVC-SAME: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|exception-escape),?){18}"}}
+// CHECK-UNDEFINED-WIN64-MINGW-SAME: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|vptr|exception-escape),?){19}"}}
 
 // RUN: %clang --target=i386-pc-win32 -fsanitize-coverage=bb %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-COVERAGE-WIN32
 // CHECK-COVERAGE-WIN32: "--dependent-lib={{[^"]*}}ubsan_standalone{{(-i386)?}}.lib"
@@ -90,7 +90,7 @@
 // CHECK-FNO-SANITIZE-ALL: "-fsanitize=thread"
 
 // RUN: %clang --target=x86_64-linux-gnu -fsanitize=thread,undefined -fno-sanitize=thread -fno-sanitize=float-cast-overflow,vptr,bool,builtin,enum %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-PARTIAL-UNDEFINED
-// CHECK-PARTIAL-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|array-bounds|returns-nonnull-attribute|nonnull-attribute),?){14}"}}
+// CHECK-PARTIAL-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|array-bounds|returns-nonnull-attribute|nonnull-attribute|exception-escape),?){15}"}}
 
 // RUN: %clang -fsanitize=shift -fno-sanitize=shift-base %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-FSANITIZE-SHIFT-PARTIAL
 // CHECK-FSANITIZE-SHIFT-PARTIAL: "-fsanitize=shift-exponent"
@@ -823,7 +823,7 @@
 // CHECK-TSAN-MINIMAL: error: invalid argument '-fsanitize-minimal-runtime' not allowed with '-fsanitize=thread'
 
 // RUN: %clang --target=x86_64-linux-gnu -fsanitize=undefined -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-MINIMAL
-// CHECK-UBSAN-MINIMAL: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){17}"}}
+// CHECK-UBSAN-MINIMAL: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|exception-escape),?){18}"}}
 // CHECK-UBSAN-MINIMAL: "-fsanitize-minimal-runtime"
 
 // RUN: %clang --target=x86_64-linux-gnu -fsanitize=integer -fsanitize-trap=integer %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INTSAN-TRAP
Index: clang/test/CodeGenCXX/catch-exception-escape.cpp
===================================================================
--- /dev/null
+++ clang/test/CodeGenCXX/catch-exception-escape.cpp
@@ -0,0 +1,350 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
+// RUN: %clang_cc1                                                                    -emit-llvm %s -o - -triple x86_64-linux-gnu              -fcxx-exceptions | FileCheck %s --check-prefixes=CHECK-NO-EXCEPTIONS
+// 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-trap=exception-escape       -emit-llvm %s -o - -triple x86_64-linux-gnu -fexceptions -fcxx-exceptions | FileCheck %s --check-prefixes=CHECK-SANITIZE-TRAP
+
+void will_throw();
+void may_throw();
+void ok() noexcept;
+
+// CHECK-NO-EXCEPTIONS: Function Attrs: mustprogress noinline nounwind optnone willreturn memory(none)
+// CHECK-NO-EXCEPTIONS-LABEL: define {{[^@]+}}@_Z7footguni
+// CHECK-NO-EXCEPTIONS-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NO-EXCEPTIONS-NEXT:  entry:
+// CHECK-NO-EXCEPTIONS-NEXT:    [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NO-EXCEPTIONS-NEXT:    store i32 [[X]], ptr [[X_ADDR]], align 4
+// CHECK-NO-EXCEPTIONS-NEXT:    [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NO-EXCEPTIONS-NEXT:    [[CMP:%.*]] = icmp eq i32 [[TMP0]], 2
+// CHECK-NO-EXCEPTIONS-NEXT:    br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_END:%.*]]
+// CHECK-NO-EXCEPTIONS:       if.then:
+// CHECK-NO-EXCEPTIONS-NEXT:    call void @_Z10will_throwv()
+// CHECK-NO-EXCEPTIONS-NEXT:    br label [[IF_END]]
+// CHECK-NO-EXCEPTIONS:       if.end:
+// CHECK-NO-EXCEPTIONS-NEXT:    call void @_Z2okv() #[[ATTR3:[0-9]+]]
+// CHECK-NO-EXCEPTIONS-NEXT:    [[TMP1:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NO-EXCEPTIONS-NEXT:    [[CMP1:%.*]] = icmp eq i32 [[TMP1]], 3
+// CHECK-NO-EXCEPTIONS-NEXT:    br i1 [[CMP1]], label [[IF_THEN2:%.*]], label [[IF_END3:%.*]]
+// CHECK-NO-EXCEPTIONS:       if.then2:
+// CHECK-NO-EXCEPTIONS-NEXT:    call void @_Z10will_throwv()
+// CHECK-NO-EXCEPTIONS-NEXT:    br label [[IF_END3]]
+// CHECK-NO-EXCEPTIONS:       if.end3:
+// CHECK-NO-EXCEPTIONS-NEXT:    [[TMP2:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NO-EXCEPTIONS-NEXT:    [[CMP4:%.*]] = icmp eq i32 [[TMP2]], 4
+// CHECK-NO-EXCEPTIONS-NEXT:    br i1 [[CMP4]], label [[IF_THEN5:%.*]], label [[IF_END6:%.*]]
+// CHECK-NO-EXCEPTIONS:       if.then5:
+// CHECK-NO-EXCEPTIONS-NEXT:    call void @_Z2okv() #[[ATTR3]]
+// CHECK-NO-EXCEPTIONS-NEXT:    call void @_Z10will_throwv()
+// CHECK-NO-EXCEPTIONS-NEXT:    br label [[IF_END6]]
+// CHECK-NO-EXCEPTIONS:       if.end6:
+// CHECK-NO-EXCEPTIONS-NEXT:    ret void
+//
+// CHECK-NOSANITIZE: Function Attrs: mustprogress noinline nounwind optnone willreturn memory(none)
+// CHECK-NOSANITIZE-LABEL: define {{[^@]+}}@_Z7footguni
+// CHECK-NOSANITIZE-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0:[0-9]+]] personality ptr @__gxx_personality_v0 {
+// CHECK-NOSANITIZE-NEXT:  entry:
+// CHECK-NOSANITIZE-NEXT:    [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NOSANITIZE-NEXT:    [[EXN_SLOT:%.*]] = alloca ptr, align 8
+// CHECK-NOSANITIZE-NEXT:    [[EHSELECTOR_SLOT:%.*]] = 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:    call void @_Z10will_throwv()
+// CHECK-NOSANITIZE-NEXT:    br label [[IF_END]]
+// CHECK-NOSANITIZE:       if.end:
+// CHECK-NOSANITIZE-NEXT:    call void @_Z2okv() #[[ATTR4:[0-9]+]]
+// 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_END3:%.*]]
+// CHECK-NOSANITIZE:       if.then2:
+// CHECK-NOSANITIZE-NEXT:    call void @_Z10will_throwv()
+// CHECK-NOSANITIZE-NEXT:    br label [[IF_END3]]
+// CHECK-NOSANITIZE:       if.end3:
+// CHECK-NOSANITIZE-NEXT:    [[TMP2:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NOSANITIZE-NEXT:    [[CMP4:%.*]] = icmp eq i32 [[TMP2]], 4
+// CHECK-NOSANITIZE-NEXT:    br i1 [[CMP4]], label [[IF_THEN5:%.*]], label [[IF_END9:%.*]]
+// CHECK-NOSANITIZE:       if.then5:
+// CHECK-NOSANITIZE-NEXT:    call void @_Z2okv() #[[ATTR4]]
+// CHECK-NOSANITIZE-NEXT:    invoke void @_Z10will_throwv()
+// CHECK-NOSANITIZE-NEXT:    to label [[INVOKE_CONT:%.*]] unwind label [[LPAD:%.*]]
+// CHECK-NOSANITIZE:       invoke.cont:
+// CHECK-NOSANITIZE-NEXT:    br label [[TRY_CONT:%.*]]
+// CHECK-NOSANITIZE:       lpad:
+// CHECK-NOSANITIZE-NEXT:    [[TMP3:%.*]] = landingpad { ptr, i32 }
+// CHECK-NOSANITIZE-NEXT:    catch ptr null
+// CHECK-NOSANITIZE-NEXT:    [[TMP4:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 0
+// CHECK-NOSANITIZE-NEXT:    store ptr [[TMP4]], ptr [[EXN_SLOT]], align 8
+// CHECK-NOSANITIZE-NEXT:    [[TMP5:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 1
+// CHECK-NOSANITIZE-NEXT:    store i32 [[TMP5]], ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-NOSANITIZE-NEXT:    br label [[CATCH:%.*]]
+// CHECK-NOSANITIZE:       catch:
+// CHECK-NOSANITIZE-NEXT:    [[EXN:%.*]] = load ptr, ptr [[EXN_SLOT]], align 8
+// CHECK-NOSANITIZE-NEXT:    [[TMP6:%.*]] = call ptr @__cxa_begin_catch(ptr [[EXN]]) #[[ATTR4]]
+// CHECK-NOSANITIZE-NEXT:    call void @_Z2okv() #[[ATTR4]]
+// CHECK-NOSANITIZE-NEXT:    invoke void @_Z9may_throwv()
+// CHECK-NOSANITIZE-NEXT:    to label [[INVOKE_CONT7:%.*]] unwind label [[LPAD6:%.*]]
+// CHECK-NOSANITIZE:       invoke.cont7:
+// CHECK-NOSANITIZE-NEXT:    invoke void @__cxa_rethrow() #[[ATTR5:[0-9]+]]
+// CHECK-NOSANITIZE-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[LPAD6]]
+// CHECK-NOSANITIZE:       lpad6:
+// CHECK-NOSANITIZE-NEXT:    [[TMP7:%.*]] = landingpad { ptr, i32 }
+// CHECK-NOSANITIZE-NEXT:    cleanup
+// CHECK-NOSANITIZE-NEXT:    [[TMP8:%.*]] = extractvalue { ptr, i32 } [[TMP7]], 0
+// CHECK-NOSANITIZE-NEXT:    store ptr [[TMP8]], ptr [[EXN_SLOT]], align 8
+// CHECK-NOSANITIZE-NEXT:    [[TMP9:%.*]] = extractvalue { ptr, i32 } [[TMP7]], 1
+// CHECK-NOSANITIZE-NEXT:    store i32 [[TMP9]], ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-NOSANITIZE-NEXT:    invoke void @__cxa_end_catch()
+// CHECK-NOSANITIZE-NEXT:    to label [[INVOKE_CONT8:%.*]] unwind label [[TERMINATE_LPAD:%.*]]
+// CHECK-NOSANITIZE:       invoke.cont8:
+// CHECK-NOSANITIZE-NEXT:    br label [[EH_RESUME:%.*]]
+// CHECK-NOSANITIZE:       try.cont:
+// CHECK-NOSANITIZE-NEXT:    br label [[IF_END9]]
+// CHECK-NOSANITIZE:       if.end9:
+// CHECK-NOSANITIZE-NEXT:    ret void
+// CHECK-NOSANITIZE:       eh.resume:
+// CHECK-NOSANITIZE-NEXT:    [[EXN10:%.*]] = load ptr, ptr [[EXN_SLOT]], align 8
+// CHECK-NOSANITIZE-NEXT:    [[SEL:%.*]] = load i32, ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-NOSANITIZE-NEXT:    [[LPAD_VAL:%.*]] = insertvalue { ptr, i32 } undef, ptr [[EXN10]], 0
+// CHECK-NOSANITIZE-NEXT:    [[LPAD_VAL11:%.*]] = insertvalue { ptr, i32 } [[LPAD_VAL]], i32 [[SEL]], 1
+// CHECK-NOSANITIZE-NEXT:    resume { ptr, i32 } [[LPAD_VAL11]]
+// CHECK-NOSANITIZE:       terminate.lpad:
+// CHECK-NOSANITIZE-NEXT:    [[TMP10:%.*]] = landingpad { ptr, i32 }
+// CHECK-NOSANITIZE-NEXT:    catch ptr null
+// CHECK-NOSANITIZE-NEXT:    [[TMP11:%.*]] = extractvalue { ptr, i32 } [[TMP10]], 0
+// CHECK-NOSANITIZE-NEXT:    call void @__clang_call_terminate(ptr [[TMP11]]) #[[ATTR6:[0-9]+]]
+// CHECK-NOSANITIZE-NEXT:    unreachable
+// CHECK-NOSANITIZE:       unreachable:
+// CHECK-NOSANITIZE-NEXT:    unreachable
+//
+// CHECK-SANITIZE-NORECOVER: Function Attrs: mustprogress noinline nounwind optnone willreturn memory(none)
+// CHECK-SANITIZE-NORECOVER-LABEL: define {{[^@]+}}@_Z7footguni
+// CHECK-SANITIZE-NORECOVER-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0:[0-9]+]] personality ptr @__gxx_personality_v0 {
+// CHECK-SANITIZE-NORECOVER-NEXT:  entry:
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[EXN_SLOT:%.*]] = alloca ptr, align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[INVOKE_SRCLOC:%.*]] = alloca { ptr, i32, i32 }, align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP:%.*]] = alloca { ptr, i32, i32 }, align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[EHSELECTOR_SLOT:%.*]] = alloca i32, align 4
+// 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:    store { ptr, i32, i32 } { ptr @.src, i32 100, i32 5 }, ptr [[INVOKE_SRCLOC]], align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    invoke void @_Z10will_throwv()
+// CHECK-SANITIZE-NORECOVER-NEXT:    to label [[INVOKE_CONT:%.*]] unwind label [[EXCEPTION_ESCAPE_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 } [[CURR_INVOKE_SRCLOC:%.*]], 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 [[EXN12:%.*]] to i64, !nosanitize !2
+// CHECK-SANITIZE-NORECOVER-NEXT:    call void @__ubsan_handle_exception_escape(i64 [[TMP1]], i64 [[TMP2]]) #[[ATTR5:[0-9]+]], !nosanitize !2
+// CHECK-SANITIZE-NORECOVER-NEXT:    unreachable, !nosanitize !2
+// CHECK-SANITIZE-NORECOVER:       cont:
+// CHECK-SANITIZE-NORECOVER-NEXT:    unreachable, !nosanitize !2
+// CHECK-SANITIZE-NORECOVER:       if.end:
+// CHECK-SANITIZE-NORECOVER-NEXT:    call void @_Z2okv() #[[ATTR6:[0-9]+]]
+// 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:    store { ptr, i32, i32 } { ptr @.src, i32 300, i32 5 }, ptr [[INVOKE_SRCLOC]], align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    invoke void @_Z10will_throwv()
+// CHECK-SANITIZE-NORECOVER-NEXT:    to label [[INVOKE_CONT3:%.*]] unwind label [[EXCEPTION_ESCAPE_LPAD]]
+// CHECK-SANITIZE-NORECOVER:       invoke.cont3:
+// CHECK-SANITIZE-NORECOVER-NEXT:    br label [[IF_END4]]
+// CHECK-SANITIZE-NORECOVER:       if.end4:
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP4:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[CMP5:%.*]] = icmp eq i32 [[TMP4]], 4
+// CHECK-SANITIZE-NORECOVER-NEXT:    br i1 [[CMP5]], label [[IF_THEN6:%.*]], label [[IF_END11:%.*]]
+// CHECK-SANITIZE-NORECOVER:       if.then6:
+// CHECK-SANITIZE-NORECOVER-NEXT:    call void @_Z2okv() #[[ATTR6]]
+// CHECK-SANITIZE-NORECOVER-NEXT:    store { ptr, i32, i32 } { ptr @.src, i32 500, i32 7 }, ptr [[INVOKE_SRCLOC]], align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    invoke void @_Z10will_throwv()
+// CHECK-SANITIZE-NORECOVER-NEXT:    to label [[INVOKE_CONT7:%.*]] unwind label [[LPAD:%.*]]
+// CHECK-SANITIZE-NORECOVER:       invoke.cont7:
+// CHECK-SANITIZE-NORECOVER-NEXT:    br label [[TRY_CONT:%.*]]
+// CHECK-SANITIZE-NORECOVER:       lpad:
+// 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:    store ptr [[TMP6]], ptr [[EXN_SLOT]], align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP7:%.*]] = extractvalue { ptr, i32 } [[TMP5]], 1
+// CHECK-SANITIZE-NORECOVER-NEXT:    store i32 [[TMP7]], ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-SANITIZE-NORECOVER-NEXT:    br label [[CATCH:%.*]]
+// CHECK-SANITIZE-NORECOVER:       catch:
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[EXN:%.*]] = load ptr, ptr [[EXN_SLOT]], align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP8:%.*]] = call ptr @__cxa_begin_catch(ptr [[EXN]]) #[[ATTR6]]
+// CHECK-SANITIZE-NORECOVER-NEXT:    call void @_Z2okv() #[[ATTR6]]
+// CHECK-SANITIZE-NORECOVER-NEXT:    store { ptr, i32, i32 } { ptr @.src, i32 700, i32 7 }, ptr [[INVOKE_SRCLOC]], align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    invoke void @_Z9may_throwv()
+// CHECK-SANITIZE-NORECOVER-NEXT:    to label [[INVOKE_CONT9:%.*]] unwind label [[LPAD8:%.*]]
+// CHECK-SANITIZE-NORECOVER:       invoke.cont9:
+// CHECK-SANITIZE-NORECOVER-NEXT:    invoke void @__cxa_rethrow() #[[ATTR7:[0-9]+]]
+// CHECK-SANITIZE-NORECOVER-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[LPAD8]]
+// CHECK-SANITIZE-NORECOVER:       lpad8:
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP9:%.*]] = landingpad { ptr, i32 }
+// CHECK-SANITIZE-NORECOVER-NEXT:    catch ptr null
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP10:%.*]] = extractvalue { ptr, i32 } [[TMP9]], 0
+// CHECK-SANITIZE-NORECOVER-NEXT:    store ptr [[TMP10]], ptr [[EXN_SLOT]], align 8
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP11:%.*]] = extractvalue { ptr, i32 } [[TMP9]], 1
+// CHECK-SANITIZE-NORECOVER-NEXT:    store i32 [[TMP11]], ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-SANITIZE-NORECOVER-NEXT:    invoke void @__cxa_end_catch()
+// CHECK-SANITIZE-NORECOVER-NEXT:    to label [[INVOKE_CONT10:%.*]] unwind label [[TERMINATE_LPAD:%.*]]
+// CHECK-SANITIZE-NORECOVER:       invoke.cont10:
+// CHECK-SANITIZE-NORECOVER-NEXT:    br label [[EXCEPTION_ESCAPE_HANDLER:%.*]]
+// CHECK-SANITIZE-NORECOVER:       try.cont:
+// CHECK-SANITIZE-NORECOVER-NEXT:    br label [[IF_END11]]
+// CHECK-SANITIZE-NORECOVER:       if.end11:
+// CHECK-SANITIZE-NORECOVER-NEXT:    ret void
+// CHECK-SANITIZE-NORECOVER:       terminate.lpad:
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP12:%.*]] = landingpad { ptr, i32 }
+// CHECK-SANITIZE-NORECOVER-NEXT:    catch ptr null
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP13:%.*]] = extractvalue { ptr, i32 } [[TMP12]], 0
+// CHECK-SANITIZE-NORECOVER-NEXT:    call void @__clang_call_terminate(ptr [[TMP13]]) #[[ATTR5]]
+// CHECK-SANITIZE-NORECOVER-NEXT:    unreachable
+// CHECK-SANITIZE-NORECOVER:       unreachable:
+// CHECK-SANITIZE-NORECOVER-NEXT:    unreachable
+// CHECK-SANITIZE-NORECOVER:       exception-escape.sanitization:
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[EXN12]] = load ptr, ptr [[EXN_SLOT]], align 8, !nosanitize !2
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[CURR_INVOKE_SRCLOC]] = load { ptr, i32, i32 }, ptr [[INVOKE_SRCLOC]], align 8, !nosanitize !2
+// CHECK-SANITIZE-NORECOVER-NEXT:    br i1 false, label [[CONT:%.*]], label [[HANDLER_EXCEPTION_ESCAPE:%.*]], !prof [[PROF3:![0-9]+]], !nosanitize !2
+// CHECK-SANITIZE-NORECOVER:       exception-escape.lpad:
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP14:%.*]] = landingpad { ptr, i32 }
+// CHECK-SANITIZE-NORECOVER-NEXT:    catch ptr null, !nosanitize !2
+// CHECK-SANITIZE-NORECOVER-NEXT:    [[TMP15:%.*]] = extractvalue { ptr, i32 } [[TMP14]], 0, !nosanitize !2
+// CHECK-SANITIZE-NORECOVER-NEXT:    store ptr [[TMP15]], ptr [[EXN_SLOT]], align 8, !nosanitize !2
+// CHECK-SANITIZE-NORECOVER-NEXT:    br label [[EXCEPTION_ESCAPE_SANITIZATION:%.*]], !nosanitize !2
+// CHECK-SANITIZE-NORECOVER:       exception-escape.handler:
+// CHECK-SANITIZE-NORECOVER-NEXT:    br label [[EXCEPTION_ESCAPE_SANITIZATION]]
+//
+// CHECK-SANITIZE-TRAP: Function Attrs: mustprogress noinline nounwind optnone willreturn memory(none)
+// CHECK-SANITIZE-TRAP-LABEL: define {{[^@]+}}@_Z7footguni
+// CHECK-SANITIZE-TRAP-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0:[0-9]+]] personality ptr @__gxx_personality_v0 {
+// CHECK-SANITIZE-TRAP-NEXT:  entry:
+// CHECK-SANITIZE-TRAP-NEXT:    [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-SANITIZE-TRAP-NEXT:    [[EXN_SLOT:%.*]] = alloca ptr, align 8
+// CHECK-SANITIZE-TRAP-NEXT:    [[INVOKE_SRCLOC:%.*]] = alloca { ptr, i32, i32 }, align 8
+// CHECK-SANITIZE-TRAP-NEXT:    [[EHSELECTOR_SLOT:%.*]] = 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:    store { ptr, i32, i32 } { ptr @.src, i32 100, i32 5 }, ptr [[INVOKE_SRCLOC]], align 8
+// CHECK-SANITIZE-TRAP-NEXT:    invoke void @_Z10will_throwv()
+// CHECK-SANITIZE-TRAP-NEXT:    to label [[INVOKE_CONT:%.*]] unwind label [[EXCEPTION_ESCAPE_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) #[[ATTR5:[0-9]+]], !nosanitize !2
+// CHECK-SANITIZE-TRAP-NEXT:    unreachable, !nosanitize !2
+// CHECK-SANITIZE-TRAP:       cont:
+// CHECK-SANITIZE-TRAP-NEXT:    unreachable, !nosanitize !2
+// CHECK-SANITIZE-TRAP:       if.end:
+// CHECK-SANITIZE-TRAP-NEXT:    call void @_Z2okv() #[[ATTR6:[0-9]+]]
+// 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:    store { ptr, i32, i32 } { ptr @.src, i32 300, i32 5 }, ptr [[INVOKE_SRCLOC]], align 8
+// CHECK-SANITIZE-TRAP-NEXT:    invoke void @_Z10will_throwv()
+// CHECK-SANITIZE-TRAP-NEXT:    to label [[INVOKE_CONT3:%.*]] unwind label [[EXCEPTION_ESCAPE_LPAD]]
+// CHECK-SANITIZE-TRAP:       invoke.cont3:
+// CHECK-SANITIZE-TRAP-NEXT:    br label [[IF_END4]]
+// CHECK-SANITIZE-TRAP:       if.end4:
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP2:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-SANITIZE-TRAP-NEXT:    [[CMP5:%.*]] = icmp eq i32 [[TMP2]], 4
+// CHECK-SANITIZE-TRAP-NEXT:    br i1 [[CMP5]], label [[IF_THEN6:%.*]], label [[IF_END11:%.*]]
+// CHECK-SANITIZE-TRAP:       if.then6:
+// CHECK-SANITIZE-TRAP-NEXT:    call void @_Z2okv() #[[ATTR6]]
+// CHECK-SANITIZE-TRAP-NEXT:    store { ptr, i32, i32 } { ptr @.src, i32 500, i32 7 }, ptr [[INVOKE_SRCLOC]], align 8
+// CHECK-SANITIZE-TRAP-NEXT:    invoke void @_Z10will_throwv()
+// CHECK-SANITIZE-TRAP-NEXT:    to label [[INVOKE_CONT7:%.*]] unwind label [[LPAD:%.*]]
+// CHECK-SANITIZE-TRAP:       invoke.cont7:
+// CHECK-SANITIZE-TRAP-NEXT:    br label [[TRY_CONT:%.*]]
+// CHECK-SANITIZE-TRAP:       lpad:
+// 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:    store ptr [[TMP4]], ptr [[EXN_SLOT]], align 8
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP5:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 1
+// CHECK-SANITIZE-TRAP-NEXT:    store i32 [[TMP5]], ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-SANITIZE-TRAP-NEXT:    br label [[CATCH:%.*]]
+// CHECK-SANITIZE-TRAP:       catch:
+// CHECK-SANITIZE-TRAP-NEXT:    [[EXN:%.*]] = load ptr, ptr [[EXN_SLOT]], align 8
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP6:%.*]] = call ptr @__cxa_begin_catch(ptr [[EXN]]) #[[ATTR6]]
+// CHECK-SANITIZE-TRAP-NEXT:    call void @_Z2okv() #[[ATTR6]]
+// CHECK-SANITIZE-TRAP-NEXT:    store { ptr, i32, i32 } { ptr @.src, i32 700, i32 7 }, ptr [[INVOKE_SRCLOC]], align 8
+// CHECK-SANITIZE-TRAP-NEXT:    invoke void @_Z9may_throwv()
+// CHECK-SANITIZE-TRAP-NEXT:    to label [[INVOKE_CONT9:%.*]] unwind label [[LPAD8:%.*]]
+// CHECK-SANITIZE-TRAP:       invoke.cont9:
+// CHECK-SANITIZE-TRAP-NEXT:    invoke void @__cxa_rethrow() #[[ATTR7:[0-9]+]]
+// CHECK-SANITIZE-TRAP-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[LPAD8]]
+// CHECK-SANITIZE-TRAP:       lpad8:
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP7:%.*]] = landingpad { ptr, i32 }
+// CHECK-SANITIZE-TRAP-NEXT:    catch ptr null
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP8:%.*]] = extractvalue { ptr, i32 } [[TMP7]], 0
+// CHECK-SANITIZE-TRAP-NEXT:    store ptr [[TMP8]], ptr [[EXN_SLOT]], align 8
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP9:%.*]] = extractvalue { ptr, i32 } [[TMP7]], 1
+// CHECK-SANITIZE-TRAP-NEXT:    store i32 [[TMP9]], ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-SANITIZE-TRAP-NEXT:    invoke void @__cxa_end_catch()
+// CHECK-SANITIZE-TRAP-NEXT:    to label [[INVOKE_CONT10:%.*]] unwind label [[TERMINATE_LPAD:%.*]]
+// CHECK-SANITIZE-TRAP:       invoke.cont10:
+// CHECK-SANITIZE-TRAP-NEXT:    br label [[EXCEPTION_ESCAPE_HANDLER:%.*]]
+// CHECK-SANITIZE-TRAP:       try.cont:
+// CHECK-SANITIZE-TRAP-NEXT:    br label [[IF_END11]]
+// CHECK-SANITIZE-TRAP:       if.end11:
+// CHECK-SANITIZE-TRAP-NEXT:    ret void
+// CHECK-SANITIZE-TRAP:       terminate.lpad:
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP10:%.*]] = landingpad { ptr, i32 }
+// CHECK-SANITIZE-TRAP-NEXT:    catch ptr null
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP11:%.*]] = extractvalue { ptr, i32 } [[TMP10]], 0
+// CHECK-SANITIZE-TRAP-NEXT:    call void @__clang_call_terminate(ptr [[TMP11]]) #[[ATTR5]]
+// CHECK-SANITIZE-TRAP-NEXT:    unreachable
+// CHECK-SANITIZE-TRAP:       unreachable:
+// CHECK-SANITIZE-TRAP-NEXT:    unreachable
+// CHECK-SANITIZE-TRAP:       exception-escape.sanitization:
+// CHECK-SANITIZE-TRAP-NEXT:    [[EXN12:%.*]] = load ptr, ptr [[EXN_SLOT]], align 8, !nosanitize !2
+// CHECK-SANITIZE-TRAP-NEXT:    [[CURR_INVOKE_SRCLOC:%.*]] = load { ptr, i32, i32 }, ptr [[INVOKE_SRCLOC]], align 8, !nosanitize !2
+// CHECK-SANITIZE-TRAP-NEXT:    br i1 false, label [[CONT:%.*]], label [[TRAP:%.*]], !nosanitize !2
+// CHECK-SANITIZE-TRAP:       exception-escape.lpad:
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP12:%.*]] = landingpad { ptr, i32 }
+// CHECK-SANITIZE-TRAP-NEXT:    catch ptr null, !nosanitize !2
+// CHECK-SANITIZE-TRAP-NEXT:    [[TMP13:%.*]] = extractvalue { ptr, i32 } [[TMP12]], 0, !nosanitize !2
+// CHECK-SANITIZE-TRAP-NEXT:    store ptr [[TMP13]], ptr [[EXN_SLOT]], align 8, !nosanitize !2
+// CHECK-SANITIZE-TRAP-NEXT:    br label [[EXCEPTION_ESCAPE_SANITIZATION:%.*]], !nosanitize !2
+// CHECK-SANITIZE-TRAP:       exception-escape.handler:
+// CHECK-SANITIZE-TRAP-NEXT:    br label [[EXCEPTION_ESCAPE_SANITIZATION]]
+//
+void __attribute__((const)) footgun(int x) {
+  if (x == 2) {
+#line 100
+    will_throw();
+  }
+#line 200
+  ok();
+  if (x == 3) {
+#line 300
+    will_throw();
+  }
+  if (x == 4) {
+    try {
+#line 400
+      ok();
+#line 500
+      will_throw();
+    } catch (...) {
+#line 600
+      ok();
+#line 700
+      may_throw();
+#line 800
+      throw;
+    }
+}
+}
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/EHScopeStack.h
===================================================================
--- clang/lib/CodeGen/EHScopeStack.h
+++ clang/lib/CodeGen/EHScopeStack.h
@@ -342,6 +342,12 @@
   /// Pops a terminate handler off the stack.
   void popTerminate();
 
+  /// Push a UB handler on the stack.
+  void pushUB();
+
+  /// Pops a UB handler off the stack.
+  void popUB();
+
   // Returns true iff the current scope is either empty or contains only
   // lifetime markers, i.e. no real cleanup code
   bool containsOnlyLifetimeMarkers(stable_iterator Old) const;
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,
@@ -1959,11 +1960,19 @@
 
   llvm::BasicBlock *TerminateLandingPad = nullptr;
   llvm::BasicBlock *TerminateHandler = nullptr;
+
+  llvm::BasicBlock *ExceptionEscapeUBSanitizerBB = nullptr;
+  llvm::BasicBlock *ExceptionEscapeUBLandingPad = nullptr;
+  llvm::BasicBlock *ExceptionEscapeUBHandler = nullptr;
+
   llvm::SmallVector<llvm::BasicBlock *, 2> TrapBBs;
 
   /// Terminate funclets keyed by parent funclet pad.
   llvm::MapVector<llvm::Value *, llvm::BasicBlock *> TerminateFunclets;
 
+  llvm::BasicBlock *UBLandingPad = nullptr;
+  llvm::AllocaInst *ExceptionEscapeUBLastInvokeSrcLoc = nullptr;
+
   /// Largest vector width used in ths function. Will be used to create a
   /// function attribute.
   unsigned LargestVectorWidth = 0;
@@ -2018,6 +2027,7 @@
 
   llvm::BasicBlock *getInvokeDest() {
     if (!EHStack.requiresLandingPad()) return nullptr;
+
     return getInvokeDestImpl();
   }
 
@@ -2382,15 +2392,29 @@
   /// Emit a test that checks if the return value \p RV is nonnull.
   void EmitReturnValueCheck(llvm::Value *RV);
 
+  /// Internal to `EmitStartEHSpec()`, do not use directly.
+  bool StartEHSpec_ExceptionEscapeIsProgramTermination(const Decl *D);
+
   /// EmitStartEHSpec - Emit the start of the exception spec.
   void EmitStartEHSpec(const Decl *D);
 
+  /// Internal to `EmitEndEHSpec()`, do not use directly.
+  bool EndEHSpec_ExceptionEscapeIsProgramTermination(const Decl *D);
+
   /// EmitEndEHSpec - Emit the end of the exception spec.
   void EmitEndEHSpec(const Decl *D);
 
+  /// getExceptionEscapeUBLandingPad - Return a simple basic block
+  /// that just calls the ubsan handler, if enabled.
+  llvm::BasicBlock *getExceptionEscapeUBSanitizerBB();
+
   /// getTerminateLandingPad - Return a landing pad that just calls terminate.
   llvm::BasicBlock *getTerminateLandingPad();
 
+  /// getExceptionEscapeUBLandingPad - Return a landing pad that just calls
+  /// ubsan handler, if enabled.
+  llvm::BasicBlock *getExceptionEscapeUBLandingPad();
+
   /// getTerminateLandingPad - Return a cleanup funclet that just calls
   /// terminate.
   llvm::BasicBlock *getTerminateFunclet();
@@ -2400,6 +2424,11 @@
   /// a terminate scope encloses a try.
   llvm::BasicBlock *getTerminateHandler();
 
+  /// getExceptionEscapeUBHandler - Return a handler (not a landing pad, just
+  /// a catch handler) that just calls ubsan check, if it is enabled.
+  /// This is used when a UB scope encloses a try.
+  llvm::BasicBlock *getExceptionEscapeUBHandler();
+
   llvm::Type *ConvertTypeForMem(QualType T);
   llvm::Type *ConvertType(QualType T);
   llvm::Type *ConvertType(const TypeDecl *T) {
Index: clang/lib/CodeGen/CodeGenFunction.cpp
===================================================================
--- clang/lib/CodeGen/CodeGenFunction.cpp
+++ clang/lib/CodeGen/CodeGenFunction.cpp
@@ -446,6 +446,9 @@
   EmitIfUsed(*this, TerminateLandingPad);
   EmitIfUsed(*this, TerminateHandler);
   EmitIfUsed(*this, UnreachableBlock);
+  EmitIfUsed(*this, ExceptionEscapeUBSanitizerBB);
+  EmitIfUsed(*this, ExceptionEscapeUBLandingPad);
+  EmitIfUsed(*this, ExceptionEscapeUBHandler);
 
   for (const auto &FuncletAndParent : TerminateFunclets)
     EmitIfUsed(*this, FuncletAndParent.second);
Index: clang/lib/CodeGen/CGExpr.cpp
===================================================================
--- clang/lib/CodeGen/CGExpr.cpp
+++ clang/lib/CodeGen/CGExpr.cpp
@@ -3187,7 +3187,8 @@
   assert(Kind.countPopulation() == 1);
   if (Kind == SanitizerKind::Function || Kind == SanitizerKind::Vptr)
     return CheckRecoverableKind::AlwaysRecoverable;
-  else if (Kind == SanitizerKind::Return || Kind == SanitizerKind::Unreachable)
+  else if (Kind == SanitizerKind::ExceptionEscape ||
+           Kind == SanitizerKind::Return || Kind == SanitizerKind::Unreachable)
     return CheckRecoverableKind::Unrecoverable;
   else
     return CheckRecoverableKind::Recoverable;
Index: clang/lib/CodeGen/CGException.cpp
===================================================================
--- clang/lib/CodeGen/CGException.cpp
+++ clang/lib/CodeGen/CGException.cpp
@@ -459,22 +459,22 @@
     EmitBlock(createBasicBlock("throw.cont"));
 }
 
-void CodeGenFunction::EmitStartEHSpec(const Decl *D) {
-  if (!CGM.getLangOpts().CXXExceptions)
-    return;
+bool CodeGenFunction::StartEHSpec_ExceptionEscapeIsProgramTermination(
+    const Decl *D) {
+  assert(CGM.getLangOpts().CXXExceptions && "Only for CXX-Exceptions mode.");
 
   const FunctionDecl* FD = dyn_cast_or_null<FunctionDecl>(D);
   if (!FD) {
     // Check if CapturedDecl is nothrow and create terminate scope for it.
     if (const CapturedDecl* CD = dyn_cast_or_null<CapturedDecl>(D)) {
       if (CD->isNothrow())
-        EHStack.pushTerminate();
+        return true;
     }
-    return;
+    return false;
   }
   const FunctionProtoType *Proto = FD->getType()->getAs<FunctionProtoType>();
   if (!Proto)
-    return;
+    return false;
 
   ExceptionSpecificationType EST = Proto->getExceptionSpecType();
   // In C++17 and later, 'throw()' aka EST_DynamicNone is treated the same way
@@ -485,18 +485,18 @@
     // TODO: Revisit exception specifications for the MS ABI.  There is a way to
     // encode these in an object file but MSVC doesn't do anything with it.
     if (getTarget().getCXXABI().isMicrosoft())
-      return;
+      return false;
     // In Wasm EH we currently treat 'throw()' in the same way as 'noexcept'. In
     // case of throw with types, we ignore it and print a warning for now.
     // TODO Correctly handle exception specification in Wasm EH
     if (CGM.getLangOpts().hasWasmExceptions()) {
       if (EST == EST_DynamicNone)
-        EHStack.pushTerminate();
+        return true;
       else
         CGM.getDiags().Report(D->getLocation(),
                               diag::warn_wasm_dynamic_exception_spec_ignored)
             << FD->getExceptionSpecSourceRange();
-      return;
+      return false;
     }
     // Currently Emscripten EH only handles 'throw()' but not 'throw' with
     // types. 'throw()' handling will be done in JS glue code so we don't need
@@ -524,8 +524,31 @@
   } else if (Proto->canThrow() == CT_Cannot) {
     // noexcept functions are simple terminate scopes.
     if (!getLangOpts().EHAsynch) // -EHa: HW exception still can occur
-      EHStack.pushTerminate();
+      return true;
   }
+  return false;
+}
+
+void CodeGenFunction::EmitStartEHSpec(const Decl *D) {
+  if (!CGM.getLangOpts().CXXExceptions)
+    return;
+
+  // Does the EH Specification for the current function mandate that the
+  // exception escaping out of the current function is required to cause program
+  // termination?
+  if (StartEHSpec_ExceptionEscapeIsProgramTermination(D)) {
+    assert(CurFn->hasFnAttribute(llvm::Attribute::NoUnwind) &&
+           "Forgot to manifest nounwind function attribute in LLVM IR.");
+    EHStack.pushTerminate();
+    return;
+  }
+
+  // If it does not mandate function termination, yet the LLVM IR function is
+  // marked as non-unwinding, then exception escape is UB.
+  // Let UBSan handle it from here.
+  if (CurFn->hasFnAttribute(llvm::Attribute::NoUnwind) &&
+      SanOpts.has(SanitizerKind::ExceptionEscape))
+    EHStack.pushUB();
 }
 
 /// Emit the dispatch block for a filter scope if necessary.
@@ -566,22 +589,22 @@
   CGF.Builder.CreateUnreachable();
 }
 
-void CodeGenFunction::EmitEndEHSpec(const Decl *D) {
-  if (!CGM.getLangOpts().CXXExceptions)
-    return;
+bool CodeGenFunction::EndEHSpec_ExceptionEscapeIsProgramTermination(
+    const Decl *D) {
+  assert(CGM.getLangOpts().CXXExceptions && "Only for CXX-Exceptions mode.");
 
   const FunctionDecl* FD = dyn_cast_or_null<FunctionDecl>(D);
   if (!FD) {
     // Check if CapturedDecl is nothrow and pop terminate scope for it.
     if (const CapturedDecl* CD = dyn_cast_or_null<CapturedDecl>(D)) {
       if (CD->isNothrow() && !EHStack.empty())
-        EHStack.popTerminate();
+        return true;
     }
-    return;
+    return false;
   }
   const FunctionProtoType *Proto = FD->getType()->getAs<FunctionProtoType>();
   if (!Proto)
-    return;
+    return false;
 
   ExceptionSpecificationType EST = Proto->getExceptionSpecType();
   if (EST == EST_Dynamic ||
@@ -589,14 +612,14 @@
     // TODO: Revisit exception specifications for the MS ABI.  There is a way to
     // encode these in an object file but MSVC doesn't do anything with it.
     if (getTarget().getCXXABI().isMicrosoft())
-      return;
+      return false;
     // In wasm we currently treat 'throw()' in the same way as 'noexcept'. In
     // case of throw with types, we ignore it and print a warning for now.
     // TODO Correctly handle exception specification in wasm
     if (CGM.getLangOpts().hasWasmExceptions()) {
       if (EST == EST_DynamicNone)
-        EHStack.popTerminate();
-      return;
+        return true;
+      return false;
     }
     EHFilterScope &filterScope = cast<EHFilterScope>(*EHStack.begin());
     emitFilterDispatchBlock(*this, filterScope);
@@ -604,8 +627,32 @@
   } else if (Proto->canThrow() == CT_Cannot &&
               /* possible empty when under async exceptions */
              !EHStack.empty()) {
-    EHStack.popTerminate();
+    return true;
   }
+
+  return false;
+}
+
+void CodeGenFunction::EmitEndEHSpec(const Decl *D) {
+  if (!CGM.getLangOpts().CXXExceptions)
+    return;
+
+  // Does the EH Specification for the current function mandate that the
+  // exception escaping out of the current function is required to cause program
+  // termination?
+  if (EndEHSpec_ExceptionEscapeIsProgramTermination(D)) {
+    assert(CurFn->hasFnAttribute(llvm::Attribute::NoUnwind) &&
+           "Forgot to manifest nounwind function attribute in LLVM IR.");
+    EHStack.popTerminate();
+    return;
+  }
+
+  // If it does not mandate function termination, yet the LLVM IR function is
+  // marked as non-unwinding, then exception escape is UB.
+  // Let UBSan handle it from here.
+  if (CurFn->hasFnAttribute(llvm::Attribute::NoUnwind) &&
+      SanOpts.has(SanitizerKind::ExceptionEscape))
+    EHStack.popUB();
 }
 
 void CodeGenFunction::EmitCXXTryStmt(const CXXTryStmt &S) {
@@ -692,6 +739,10 @@
     case EHScope::Terminate:
       dispatchBlock = getTerminateHandler();
       break;
+
+    case EHScope::UB:
+      dispatchBlock = getExceptionEscapeUBHandler();
+      break;
     }
     scope.setCachedEHDispatchBlock(dispatchBlock);
   }
@@ -733,6 +784,10 @@
   case EHScope::Terminate:
     DispatchBlock->setName("terminate");
     break;
+
+  case EHScope::UB:
+    DispatchBlock->setName("ub");
+    break;
   }
   EHS.setCachedEHDispatchBlock(DispatchBlock);
   return DispatchBlock;
@@ -748,6 +803,7 @@
   case EHScope::Filter:
   case EHScope::Catch:
   case EHScope::Terminate:
+  case EHScope::UB:
     return false;
   }
 
@@ -813,6 +869,8 @@
   switch (innermostEHScope.getKind()) {
   case EHScope::Terminate:
     return getTerminateLandingPad();
+  case EHScope::UB:
+    return getExceptionEscapeUBLandingPad();
 
   case EHScope::Catch:
   case EHScope::Cleanup:
@@ -872,7 +930,8 @@
     }
 
     case EHScope::Terminate:
-      // Terminate scopes are basically catch-alls.
+    case EHScope::UB:
+      // Terminate/UB scopes are basically catch-alls.
       assert(!hasCatchAll);
       hasCatchAll = true;
       goto done;
@@ -943,7 +1002,7 @@
   Builder.restoreIP(savedIP);
 
   return lpad;
-}
+ }
 
 static void emitCatchPadBlock(CodeGenFunction &CGF, EHCatchScope &CatchScope) {
   llvm::BasicBlock *DispatchBlock = CatchScope.getCachedEHDispatchBlock();
@@ -1601,6 +1660,120 @@
   return TerminateFunclet;
 }
 
+llvm::BasicBlock *CodeGenFunction::getExceptionEscapeUBSanitizerBB() {
+  assert(SanOpts.has(SanitizerKind::ExceptionEscape) &&
+         "Should only get here if the Exception Escape Sanitizer is enabled.");
+
+  if (ExceptionEscapeUBSanitizerBB)
+    return ExceptionEscapeUBSanitizerBB;
+
+  SanitizerScope SanScope(this);
+
+  CGBuilderTy::InsertPoint SavedIP = Builder.saveAndClearIP();
+
+  // This will get inserted at the end of the function.
+  ExceptionEscapeUBSanitizerBB =
+      createBasicBlock("exception-escape.sanitization");
+  Builder.SetInsertPoint(ExceptionEscapeUBSanitizerBB);
+
+  llvm::Value *Exn = getExceptionFromSlot();
+
+  llvm::Type *SourceLocationTy =
+      EmitCheckSourceLocation(SourceLocation())->getType();
+  ExceptionEscapeUBLastInvokeSrcLoc =
+      CreateTempAlloca(SourceLocationTy, "invoke.srcloc");
+  // The callers are responsible with populating this with relevant information.
+
+  llvm::Value *InvokeSrcLoc = Builder.CreateLoad(
+      Address(ExceptionEscapeUBLastInvokeSrcLoc, SourceLocationTy,
+              CharUnits::fromQuantity(
+                  ExceptionEscapeUBLastInvokeSrcLoc->getAlign().value())),
+      "curr.invoke.srcloc");
+
+  llvm::Value *DynamicData[] = {InvokeSrcLoc, Exn};
+  // FIXME: can we do anything interesting with `Exn`?
+  EmitCheck({std::make_pair(llvm::ConstantInt::getFalse(getLLVMContext()),
+                            SanitizerKind::ExceptionEscape)},
+            SanitizerHandler::ExceptionEscape, /*StaticData=*/{}, DynamicData);
+
+  Builder.CreateUnreachable();
+
+  // Restore the saved insertion state.
+  Builder.restoreIP(SavedIP);
+
+  return ExceptionEscapeUBSanitizerBB;
+}
+
+llvm::BasicBlock *CodeGenFunction::getExceptionEscapeUBLandingPad() {
+  assert(SanOpts.has(SanitizerKind::ExceptionEscape) &&
+         "Should only get here if the Exception Escape Sanitizer is enabled.");
+
+  if (ExceptionEscapeUBLandingPad)
+    return ExceptionEscapeUBLandingPad;
+
+  llvm::BasicBlock *SanBB = getExceptionEscapeUBSanitizerBB();
+
+  SanitizerScope SanScope(this);
+
+  CGBuilderTy::InsertPoint SavedIP = Builder.saveAndClearIP();
+
+  // This will get inserted at the end of the function.
+  ExceptionEscapeUBLandingPad = createBasicBlock("exception-escape.lpad");
+  Builder.SetInsertPoint(ExceptionEscapeUBLandingPad);
+
+  // Tell the backend that this is a landing pad.
+  const EHPersonality &Personality = EHPersonality::get(*this);
+
+  if (!CurFn->hasPersonalityFn())
+    CurFn->setPersonalityFn(getOpaquePersonalityFn(CGM, Personality));
+
+  llvm::LandingPadInst *LPadInst =
+      Builder.CreateLandingPad(llvm::StructType::get(Int8PtrTy, Int32Ty), 0);
+  LPadInst->addClause(getCatchAllValue(*this));
+
+  llvm::Value *Exn = nullptr;
+  if (getLangOpts().CPlusPlus)
+    Exn = Builder.CreateExtractValue(LPadInst, 0);
+  Builder.CreateStore(Exn, getExceptionSlot());
+
+  // And just fall through into the actual sanitizer block.
+  Builder.CreateBr(SanBB);
+
+  // Restore the saved insertion state.
+  Builder.restoreIP(SavedIP);
+
+  return ExceptionEscapeUBLandingPad;
+}
+
+llvm::BasicBlock *CodeGenFunction::getExceptionEscapeUBHandler() {
+  assert(SanOpts.has(SanitizerKind::ExceptionEscape) &&
+         "Should only get here if the Exception Escape Sanitizer is enabled.");
+
+  if (ExceptionEscapeUBHandler)
+    return ExceptionEscapeUBHandler;
+
+  llvm::BasicBlock *SanBB = getExceptionEscapeUBSanitizerBB();
+
+  // Set up the UB handler.  This block is inserted at the very
+  // end of the function by FinishFunction.
+  ExceptionEscapeUBHandler = createBasicBlock("exception-escape.handler");
+  CGBuilderTy::InsertPoint SavedIP = Builder.saveAndClearIP();
+  Builder.SetInsertPoint(ExceptionEscapeUBHandler);
+
+  // For C++, the exception is already in the slot, otherwise we need to store
+  // null.
+  if (!getLangOpts().CPlusPlus)
+    Builder.CreateStore(/*Exn=*/nullptr, getExceptionSlot());
+
+  // And just fall through into the actual sanitizer block.
+  Builder.CreateBr(SanBB);
+
+  // Restore the saved insertion state.
+  Builder.restoreIP(SavedIP);
+
+  return ExceptionEscapeUBHandler;
+}
+
 llvm::BasicBlock *CodeGenFunction::getEHResumeBlock(bool isCleanup) {
   if (EHResumeBlock) return EHResumeBlock;
 
Index: clang/lib/CodeGen/CGCleanup.h
===================================================================
--- clang/lib/CodeGen/CGCleanup.h
+++ clang/lib/CodeGen/CGCleanup.h
@@ -101,7 +101,7 @@
   };
 
 public:
-  enum Kind { Cleanup, Catch, Terminate, Filter };
+  enum Kind { Cleanup, Catch, Terminate, UB, Filter };
 
   EHScope(Kind kind, EHScopeStack::stable_iterator enclosingEHScope)
     : CachedLandingPad(nullptr), CachedEHDispatchBlock(nullptr),
@@ -487,6 +487,16 @@
   }
 };
 
+/// An exceptions scope which causes UB if any exception reaches it.
+class EHUBScope : public EHScope {
+public:
+  EHUBScope(EHScopeStack::stable_iterator enclosingEHScope)
+      : EHScope(UB, enclosingEHScope) {}
+  static size_t getSize() { return sizeof(EHUBScope); }
+
+  static bool classof(const EHScope *scope) { return scope->getKind() == UB; }
+};
+
 /// A non-stable pointer into the scope stack.
 class EHScopeStack::iterator {
   char *Ptr;
@@ -524,6 +534,10 @@
     case EHScope::Terminate:
       Size = EHTerminateScope::getSize();
       break;
+
+    case EHScope::UB:
+      Size = EHUBScope::getSize();
+      break;
     }
     Ptr += llvm::alignTo(Size, ScopeStackAlignment);
     return *this;
@@ -572,6 +586,14 @@
   deallocate(EHTerminateScope::getSize());
 }
 
+inline void EHScopeStack::popUB() {
+  assert(!empty() && "popping exception stack when not empty");
+
+  EHUBScope &scope = cast<EHUBScope>(*begin());
+  InnermostEHScope = scope.getEnclosingEHScope();
+  deallocate(EHUBScope::getSize());
+}
+
 inline EHScopeStack::iterator EHScopeStack::find(stable_iterator sp) const {
   assert(sp.isValid() && "finding invalid savepoint");
   assert(sp.Size <= stable_begin().Size && "finding savepoint after pop");
Index: clang/lib/CodeGen/CGCleanup.cpp
===================================================================
--- clang/lib/CodeGen/CGCleanup.cpp
+++ clang/lib/CodeGen/CGCleanup.cpp
@@ -270,6 +270,12 @@
   InnermostEHScope = stable_begin();
 }
 
+void EHScopeStack::pushUB() {
+  char *Buffer = allocate(EHUBScope::getSize());
+  new (Buffer) EHUBScope(InnermostEHScope);
+  InnermostEHScope = stable_begin();
+}
+
 /// Remove any 'null' fixups on the stack.  However, we can't pop more
 /// fixups than the fixup depth on the innermost normal cleanup, or
 /// else fixups that we try to add to that cleanup will end up in the
Index: clang/lib/CodeGen/CGCall.cpp
===================================================================
--- clang/lib/CodeGen/CGCall.cpp
+++ clang/lib/CodeGen/CGCall.cpp
@@ -5399,6 +5399,16 @@
   if (!InvokeDest) {
     CI = Builder.CreateCall(IRFuncTy, CalleePtr, IRCallArgs, BundleList);
   } else {
+    if (SanOpts.has(SanitizerKind::ExceptionEscape)) {
+      assert(ExceptionEscapeUBLastInvokeSrcLoc && "");
+      llvm::Constant *CheckSourceLocation = EmitCheckSourceLocation(Loc);
+      Builder.CreateStore(
+          CheckSourceLocation,
+          Address(ExceptionEscapeUBLastInvokeSrcLoc,
+                  CheckSourceLocation->getType(),
+                  CharUnits::fromQuantity(
+                      ExceptionEscapeUBLastInvokeSrcLoc->getAlign().value())));
+    }
     llvm::BasicBlock *Cont = createBasicBlock("invoke.cont");
     CI = Builder.CreateInvoke(IRFuncTy, CalleePtr, Cont, InvokeDest, IRCallArgs,
                               BundleList);
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
@@ -795,8 +795,8 @@
 - Introduced the new function ``clang_CXXMethod_isMoveAssignmentOperator``,
   which identifies whether a method cursor is a move-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.
 
@@ -816,6 +816,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