lebedev.ri updated this revision to Diff 473529. lebedev.ri edited the summary of this revision. lebedev.ri added a comment. Herald added a subscriber: mstorsjo.
In D137381#3907799 <https://reviews.llvm.org/D137381#3907799>, @lebedev.ri wrote: > In D137381#3907104 <https://reviews.llvm.org/D137381#3907104>, @MaskRay wrote: > >> In your example, `clang++ a.cc; ./a.out` gives a libstdc++ error: >> >> terminate called after throwing an instance of 'int' >> >> libc++'s is similar. > > That's great, but just a symptom of misreduced testcase. > The whole problem is that in the original bug *no* abort happened at runtime, > the program terminated successfully, with a mysterious leak. > >> footgun is nounwind (due to the GNU pure attribute), so Clang uses `call` >> instead of `invoke` and the function call is described by a call site entry >> with a zero action_record_offset (i.e. not a handler) in `.gcc_except_table`. >> In `_Unwind_RaiseException` called by `__cxa_throw`, the missing exception >> handler causes `__terminate`. >> >> `g++ a.cc; ./a.out` succeeds, because its `footgun` call is caught by `catch >> (...)`. (Perhaps GCC doesn't have Clang's nounwind optimization.) FWIW, i now have a standalone reproducer (~3MiB filesize), and without sanitizers there's no abort regardless of optimization level, with sanitizers there's only the leak report, and no mention of the exception. I've already reduced it, but something gone wrong, and for some inexplicable reason, creduce succeeded with a repro that does not pass the interestingness test (this is not an error in said interestingness test.) So i will need to re-reduce. But as as i have expected, the problem will likely end up being more fundamental, because, not unexpectedly, this new diagnostic does not show up either. TLDR: thisisfine <https://reviews.llvm.org/file/data/fbgalfleh3huuxlldqul/PHID-FILE-5xsihirzrmdkq7wnmjfh/meme-on-fire.jpeg> >> The patch doesn't implement the runtime correctly (I get a linker error) so >> I cannot try it out. I believe this should now be fixed. I did not update `getRecoverableKind()`, which, conveniently, duplicates `Unrecoverable` from `SanitizerArgs.cpp`... >> How expensive is your instrumentation? >> Does it work with `-fno-exceptions` intermediate functions? (Unwinding >> through such functions should fail as well). Note that this check can be >> done today,without any instrumentation: just use >> `-fno-asynchronous-unwind-tables` (for some targets which default to async >> unwind tables (aarch64,x86,etc)). >> If the process installs a SIGABRT signal handler, the stack trace can be >> printed when `__terminate` calls `abort`. As you can see, at least right now, this quite literally relies on the existing `CodeGenFunction::getTerminateLandingPad()` handling infra, so it is as cheap as it will be. But has the same bugs as it already has. The obvious alternative implementation could be to, when emitting `nounwing` function body `F`, emit it as a thunk with an an invoke of an actual function, but without `nounwind`, but that is undesirable because we lose all source debug information. This is UB, and it should be handled by UBSan, not somewhere else, especially because that will result in best error messages. > I find this comment non-welcoming and discouraging. > I just wanted to get something posted when i had something to post already. > All of this needs a bit more time. 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/CGException.cpp clang/lib/CodeGen/CGExpr.cpp clang/lib/CodeGen/CodeGenFunction.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,36 @@ +// 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> + +void thrower() { throw 0; } + +void nonthrower() noexcept {} + +void footgun(int x) noexcept { +#line 100 + if (x == 2) + thrower(); +#line 200 + if (x == 3) + thrower(); + nonthrower(); +} + +// CHECK-ALL: TEST + +// CHECK-ONE-EMPTY: +// CHECK-TWO: exception-escape +// CHECK-THREE: exception-escape + +int main(int argc, char **argv) { + fprintf(stderr, "TEST\n"); + + try { + footgun(argc); + } catch (...) { + } + return 0; +} Index: compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp =================================================================== --- /dev/null +++ compiler-rt/test/ubsan/TestCases/Misc/exception-escape.cpp @@ -0,0 +1,36 @@ +// 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 nonthrower() noexcept {} + +void footgun(int x) noexcept { +#line 100 + if (x == 2) + thrower(); +#line 200 + if (x == 3) + thrower(); + nonthrower(); +} + +// 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/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,127 @@ +// 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-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() noexcept; + +// 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: call void @_Z2okv() #[[ATTR4:[0-9]+]] +// 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]]) #[[ATTR5:[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(i64 [[TMP1]], i64 [[TMP2]]) #[[ATTR5:[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]]) #[[ATTR5]] +// 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: call void @_Z2okv() #[[ATTR6:[0-9]+]] +// 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]] ] +// 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-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) #[[ATTR5:[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:%.*]]) #[[ATTR5]] +// 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: call void @_Z2okv() #[[ATTR6:[0-9]+]] +// 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]] ] +// 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, @@ -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,22 @@ return UnreachableBlock; } - llvm::BasicBlock *getInvokeDest() { + llvm::BasicBlock *getInvokeDest(SourceLocation Loc = SourceLocation()) { if (!EHStack.requiresLandingPad()) return nullptr; - return getInvokeDestImpl(); + + llvm::BasicBlock *InvokeDest = getInvokeDestImpl(); + + // This is not nice, but given caching, we might not be able to do better? + // FIXME: what about FuncletPads? + if (llvm::BasicBlock *InvokeBB = Builder.GetInsertBlock(); + SanOpts.has(SanitizerKind::ExceptionEscape) && + InvokeDest == TerminateLandingPad && TerminateLandingPadLocPHI && + TerminateLandingPadLocPHI->getBasicBlockIndex(InvokeBB) < 0) { + llvm::Constant *CheckSourceLocation = EmitCheckSourceLocation(Loc); + TerminateLandingPadLocPHI->addIncoming(CheckSourceLocation, InvokeBB); + } + + return InvokeDest; } bool currentFunctionUsesSEHTry() const { return CurSEHParent != nullptr; } Index: clang/lib/CodeGen/CGExpr.cpp =================================================================== --- clang/lib/CodeGen/CGExpr.cpp +++ clang/lib/CodeGen/CGExpr.cpp @@ -3185,7 +3185,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 @@ -943,7 +943,7 @@ Builder.restoreIP(savedIP); return lpad; -} + } static void emitCatchPadBlock(CodeGenFunction &CGF, EHCatchScope &CatchScope) { llvm::BasicBlock *DispatchBlock = CatchScope.getCachedEHDispatchBlock(); @@ -1517,6 +1517,14 @@ TerminateLandingPad = createBasicBlock("terminate.lpad"); Builder.SetInsertPoint(TerminateLandingPad); + if (SanOpts.has(SanitizerKind::ExceptionEscape)) { + llvm::Type *SourceLocationTy = + EmitCheckSourceLocation(SourceLocation())->getType(); + TerminateLandingPadLocPHI = + Builder.CreatePHI(SourceLocationTy, /*NumReservedValues=*/10); + // But *DON'T* add any incoming values, `getInvokeDest()` deals with that. + } + // Tell the backend that this is a landing pad. const EHPersonality &Personality = EHPersonality::get(*this); @@ -1530,6 +1538,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 @@ -5373,7 +5373,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 @@ -781,8 +781,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. @@ -802,6 +802,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