llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-codegen Author: Ian Li (ianayl) <details> <summary>Changes</summary> This PR fixes codegen for statements that branch out of SEH handlers causing compiler crashes in LLVM, i.e. see current behavior as of 23.1.0: https://godbolt.org/z/Gsde8b1cE ```c int main() { __try { } __finally { goto illegal_jmp; // Compiler crash } illegal_jmp: for (int i = 0; i < 4; i++) { __try { } __finally { // Also compiler crash: break; continue; } } } ``` Instead, this PR tries to adhere to [MSVC C4532](https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4532) and emits `unreachable` when a statement leads to a `br` out of the immediate SEH handler statement / outlined function. As visible in aforementioned Godbolt link, Sema already contains code emitting warnings on these jumps out of SEH handlers, it's moreso a matter of preventing the compiler crash and adhereing to what's specified in C4532. Assisted by: I used Claude to help me with the test cases, although the code was handwritten. --- Patch is 22.39 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/220462.diff 6 Files Affected: - (modified) clang/lib/CodeGen/CGException.cpp (+4-3) - (modified) clang/lib/CodeGen/CGStmt.cpp (+53-8) - (modified) clang/lib/CodeGen/CodeGenFunction.h (+12-3) - (modified) clang/test/CodeGen/exceptions-seh-finally.c (+389-4) - (modified) clang/test/CodeGen/exceptions-seh.c (+87) - (modified) clang/test/Sema/__try.c (+13) ``````````diff diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index b0fb3b4d85d15..109a02c3b48fe 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -1739,8 +1739,8 @@ struct PerformSEHFinally final : EHScopeStack::Cleanup { // Compute the two argument values. QualType ArgTys[2] = {Context.UnsignedCharTy, Context.VoidPtrTy}; llvm::Value *FP = nullptr; - // If CFG.IsOutlinedSEHHelper is true, then we are within a finally block. - if (CGF.IsOutlinedSEHHelper) { + // If CFG.IsOutlinedSEHHelper() is true, then we are within a finally block. + if (CGF.IsOutlinedSEHHelper()) { FP = &CGF.CurFn->arg_begin()[1]; } else { llvm::Function *LocalAddrFn = @@ -2083,7 +2083,8 @@ void CodeGenFunction::startOutlinedSEHHelper(CodeGenFunction &ParentCGF, llvm::Function *Fn = llvm::Function::Create( FnTy, llvm::GlobalValue::InternalLinkage, Name.str(), &CGM.getModule()); - IsOutlinedSEHHelper = true; + // Indicate the current function is an outlined SEH helper. + OutlinedSEHStmt = OutlinedStmt; StartFunction(GlobalDecl(), RetTy, Fn, FnInfo, Args, OutlinedStmt->getBeginLoc(), OutlinedStmt->getBeginLoc()); diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index bf6e6eb50f555..90291d17caa48 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -848,6 +848,23 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) { EmitStmt(S.getSubStmt(), S.getAttrs()); } +static bool StmtContainsLabelDecl(const Stmt *S, const LabelDecl *Target) { + if (!S) + return false; + if (const auto *LS = dyn_cast<LabelStmt>(S)) + if (LS->getDecl() == Target) + return true; + for (const Stmt *Child : S->children()) + if (StmtContainsLabelDecl(Child, Target)) + return true; + return false; +} + +bool CodeGenFunction::IsLabelWithinSEHHelper(const LabelDecl *Label) const { + assert(IsOutlinedSEHHelper() && "Not in an outlined SEH helper"); + return StmtContainsLabelDecl(OutlinedSEHStmt, Label); +} + void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) { // If this code is reachable then emit a stop point (if generating // debug info). We have to do this ourselves because we are on the @@ -855,6 +872,16 @@ void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) { if (HaveInsertPoint()) EmitStopPoint(&S); + // MSVC C4532: jumping out of an outlined SEH helper is UB, but jumping + // internally should be fine. Sema has a warn for this already. + // `br` should not jump out of the current function anyway, so a jump out of + // the outlined SEH helper will cause a compiler crash. + if (IsOutlinedSEHHelper() && !IsLabelWithinSEHHelper(S.getLabel())) { + Builder.CreateUnreachable(); + Builder.ClearInsertionPoint(); + return; + } + ApplyAtomGroup Grp(getDebugInfo()); EmitBranchThroughCleanup(getJumpDestForLabel(S.getLabel())); } @@ -1627,7 +1654,7 @@ void CodeGenFunction::EmitReturnStmt(const ReturnStmt &S) { } // Returning from an outlined SEH helper is UB, and we already warn on it. - if (IsOutlinedSEHHelper) { + if (IsOutlinedSEHHelper()) { Builder.CreateUnreachable(); Builder.ClearInsertionPoint(); } @@ -1740,6 +1767,8 @@ void CodeGenFunction::EmitDeclStmt(const DeclStmt &S) { auto CodeGenFunction::GetDestForLoopControlStmt(const LoopControlStmt &S) -> const BreakContinue * { + if (BreakContinueStack.empty()) + return nullptr; if (!S.hasLabelTarget()) return &BreakContinueStack.back(); @@ -1749,33 +1778,49 @@ auto CodeGenFunction::GetDestForLoopControlStmt(const LoopControlStmt &S) if (BC.LoopOrSwitch == LoopOrSwitch) return &BC; - llvm_unreachable("break/continue target not found"); + return nullptr; } void CodeGenFunction::EmitBreakStmt(const BreakStmt &S) { - assert(!BreakContinueStack.empty() && "break stmt not in a loop or switch!"); - // If this code is reachable then emit a stop point (if generating // debug info). We have to do this ourselves because we are on the // "simple" statement path. if (HaveInsertPoint()) EmitStopPoint(&S); + const BreakContinue *BC = GetDestForLoopControlStmt(S); + if (!BC) { + assert(IsOutlinedSEHHelper() && + "break stmt destination not found in current function!"); + // S was meant to break out of a loop or switch outside of the outlined SEH + // helper. This is UB as per MSVC C4532, so generate unreachable. + Builder.CreateUnreachable(); + Builder.ClearInsertionPoint(); + return; + } ApplyAtomGroup Grp(getDebugInfo()); - EmitBranchThroughCleanup(GetDestForLoopControlStmt(S)->BreakBlock); + EmitBranchThroughCleanup(BC->BreakBlock); } void CodeGenFunction::EmitContinueStmt(const ContinueStmt &S) { - assert(!BreakContinueStack.empty() && "continue stmt not in a loop!"); - // If this code is reachable then emit a stop point (if generating // debug info). We have to do this ourselves because we are on the // "simple" statement path. if (HaveInsertPoint()) EmitStopPoint(&S); + const BreakContinue *BC = GetDestForLoopControlStmt(S); + if (!BC || !BC->ContinueBlock.isValid()) { + assert(IsOutlinedSEHHelper() && + "continue stmt destination not found in current function!"); + // S was meant to continue out of a loop outside of the outlined SEH helper. + // This is UB as per MSVC C4532, so generate unreachable. + Builder.CreateUnreachable(); + Builder.ClearInsertionPoint(); + return; + } ApplyAtomGroup Grp(getDebugInfo()); - EmitBranchThroughCleanup(GetDestForLoopControlStmt(S)->ContinueBlock); + EmitBranchThroughCleanup(BC->ContinueBlock); } /// EmitCaseStmtRange - If case statement range is not too big then diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index dfb6f2ff65a7d..9759953a1fb81 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -605,9 +605,16 @@ class CodeGenFunction : public CodeGenTypeCache { GlobalDecl CurSEHParent; - /// True if the current function is an outlined SEH helper. This can be a - /// finally block or filter expression. - bool IsOutlinedSEHHelper = false; + /// Pointer to the outlined SEH helper statement if current function is an + /// outlined SEH helper, nullptr otherwise. This can be a finally block or + /// a filter expression. + const Stmt *OutlinedSEHStmt = nullptr; + + /// True if the current function is an outlined SEH helper. + bool IsOutlinedSEHHelper() const { return OutlinedSEHStmt != nullptr; } + + /// True if Label is found within OutlinedSEHStmt. + bool IsLabelWithinSEHHelper(const LabelDecl *Label) const; /// True if CodeGen currently emits code inside presereved access index /// region. @@ -3687,6 +3694,8 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitDeferStmt(const DeferStmt &S); void EmitAsmStmt(const AsmStmt &S); + /// Look up the destination for a break/continue statement, nullptr if not + /// found. const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt &S); void EmitObjCForCollectionStmt(const ObjCForCollectionStmt &S); diff --git a/clang/test/CodeGen/exceptions-seh-finally.c b/clang/test/CodeGen/exceptions-seh-finally.c index 8b6f6b124bd21..4e686e3269336 100644 --- a/clang/test/CodeGen/exceptions-seh-finally.c +++ b/clang/test/CodeGen/exceptions-seh-finally.c @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 %s -triple x86_64-pc-win32 -fms-extensions -emit-llvm -O1 -disable-llvm-passes -o - | FileCheck %s -// RUN: %clang_cc1 %s -triple i686-pc-win32 -fms-extensions -emit-llvm -O1 -disable-llvm-passes -o - | FileCheck %s -// RUN: %clang_cc1 %s -triple aarch64-windows -fms-extensions -emit-llvm -O1 -disable-llvm-passes -o - | FileCheck %s -// RUN: %clang_cc1 %s -triple thumbv7-windows -fms-extensions -emit-llvm -O1 -disable-llvm-passes -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple x86_64-pc-win32 -fms-extensions -fnamed-loops -emit-llvm -O1 -disable-llvm-passes -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple i686-pc-win32 -fms-extensions -fnamed-loops -emit-llvm -O1 -disable-llvm-passes -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple aarch64-windows -fms-extensions -fnamed-loops -emit-llvm -O1 -disable-llvm-passes -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple thumbv7-windows -fms-extensions -fnamed-loops -emit-llvm -O1 -disable-llvm-passes -o - | FileCheck %s // NOTE: we're passing "-O1 -disable-llvm-passes" to avoid adding optnone and noinline everywhere. void abort(void) __attribute__((noreturn)); @@ -286,6 +286,391 @@ void finally_with_func(void) { // CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@finally_with_func@@"({{[^)]*}}) // CHECK: call {{.*}}void @cleanup_with_func(ptr noundef @"??_C@_0BC@COAGBPGM@finally_with_func?$AA@") +// Jumping out of a __finally is UB, check that unreachable is emitted rather +// than a br out of __finally (which is illegal). +void goto_out_of_finally(void) { + __try { + might_crash(); + } __finally { + goto out; + } +out: + cleanup(); +} + +// CHECK-LABEL: define dso_local {{.*}}void @goto_out_of_finally() +// CHECK: call {{.*}}void @"?fin$0@0@goto_out_of_finally@@"({{.*}}) +// CHECK: br label %[[out:[^ ]*]] +// +// CHECK: [[out]] +// CHECK: call {{.*}}void @cleanup() +// CHECK-NEXT: ret void + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@goto_out_of_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK-NOT: br +// CHECK-NOT: ret void +// CHECK: unreachable + +// Jumping inside a __finally should still be legal, so check that br is still +// emitted for jumps inside the SEH handler. +void goto_in_and_out_of_finally(void) { +before: + __try { + might_crash(); + } __finally { + if (check_condition()) + goto inside; + if (check_condition()) + goto before; + inside: + if (check_condition()) + goto after; + cleanup(); + } +after: + cleanup(); +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@goto_in_and_out_of_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: call {{.*}}i32 @check_condition() +// CHECK: br {{.*}}, label %[[ifthen1:[^ ]*]], label %[[ifend1:[^ ]*]] +// +// CHECK: [[ifthen1]] +// Internal jump should still generate. +// CHECK: br label %[[inside:[^ ]*]] +// +// CHECK: [[ifend1]] +// CHECK: call {{.*}}i32 @check_condition() +// CHECK: br {{.*}}, label %[[ifthen2:[^ ]*]], label %[[ifend2:[^ ]*]] +// +// CHECK: [[ifthen2]] +// Backwards jumps still leave the helper. +// CHECK: unreachable +// +// CHECK: [[ifend2]] +// CHECK: br label %[[inside]] +// +// CHECK: [[inside]] +// CHECK: call {{.*}}i32 @check_condition() +// CHECK: br {{.*}}, label %[[ifthen3:[^ ]*]], label %[[ifend3:[^ ]*]] +// +// CHECK: [[ifthen3]] +// Forward jumps still leave the helper. +// CHECK: unreachable +// +// CHECK: [[ifend3]] +// CHECK: call {{.*}}void @cleanup() +// CHECK-NEXT: ret void + +// The label can be nested arbitrarily deeply inside the __finally, but alas +// still legal and a br should still be generated. +void deep_label_in_finally(void) { + __try { + might_crash(); + } __finally { + while (check_condition()) { + switch (check_condition()) { + case 1: + deep: + cleanup(); + break; + default: + goto deep; + } + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@deep_label_in_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: switch i32 %{{[^,]*}}, label %[[swdefault:[^ ]*]] [ +// CHECK: br label %[[deep:[^ ]*]] +// +// CHECK: [[deep]] +// CHECK: call {{.*}}void @cleanup() +// +// CHECK: [[swdefault]] +// CHECK-NEXT: br label %[[deep]] +// CHECK-NOT: unreachable +// CHECK: ret void + +void break_out_of_finally(void) { + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + break; + } + } +} + +// CHECK-LABEL: define dso_local {{.*}}void @break_out_of_finally() +// CHECK: call {{.*}}void @"?fin$0@0@break_out_of_finally@@"({{.*}}) + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@break_out_of_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// break out of an outlined SEH handler is UB, do not generate br. +// CHECK-NOT: br +// CHECK-NOT: ret void +// CHECK: unreachable + +void break_out_of_finally_to_switch(void) { + switch (check_condition()) { + case 1: + __try { + might_crash(); + } __finally { + break; + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@break_out_of_finally_to_switch@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK-NOT: br +// CHECK-NOT: ret void +// CHECK: unreachable + +void continue_out_of_finally(void) { + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + continue; + } + } +} + +// CHECK-LABEL: define dso_local {{.*}}void @continue_out_of_finally() +// CHECK: call {{.*}}void @"?fin$0@0@continue_out_of_finally@@"({{.*}}) + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@continue_out_of_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// continue out of an outlined SEH handler is UB, do not generate br. +// CHECK-NOT: br +// CHECK-NOT: ret void +// CHECK: unreachable + +// break/continue bound to a loop within an outlined SEH handler should still +// generate br's as normal. +void loop_inside_finally(void) { + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + while (check_condition()) { + if (check_condition()) + continue; + break; + } + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@loop_inside_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: br label %[[whilecond:[^ ]*]] +// +// CHECK: [[whilecond]] +// CHECK: br i1 {{.*}}, label %{{[^ ]*}}, label %[[whileend:[^ ]*]] +// +// CHECK: br label %[[whilecond]] +// CHECK: br label %[[whileend]] +// +// CHECK: [[whileend]] +// CHECK-NOT: unreachable +// CHECK: ret void + +// Ditto. +void break_in_switch_in_finally(void) { + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + switch (check_condition()) { + case 1: + break; + } + cleanup(); + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@break_in_switch_in_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: switch i32 %{{[^,]*}}, label %[[epilog:[^ ]*]] [ +// CHECK-NEXT: i32 1, label %[[epilog]] +// +// CHECK: [[epilog]] +// CHECK-NOT: unreachable +// CHECK: call {{.*}}void @cleanup() +// CHECK-NEXT: ret void + +void switch_in_loop_in_finally(void) { + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + while (check_condition()) { + switch (check_condition()) { + case 1: + continue; + } + } + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@switch_in_loop_in_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: br label %[[whilecond:[^ ]*]] +// CHECK: switch i32 %{{[^,]*}}, label %{{[^ ]*}} [ +// CHECK-NEXT: i32 1, label %[[swbb:[^ ]*]] +// +// CHECK: [[swbb]] +// CHECK-NEXT: br label %[[whilecond]] +// CHECK-NOT: unreachable +// CHECK: ret void + +void continue_in_switch_in_finally(void) { + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + switch (check_condition()) { + case 1: + continue; + } + cleanup(); + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@continue_in_switch_in_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: switch i32 %{{[^,]*}}, label %[[epilog:[^ ]*]] [ +// CHECK-NEXT: i32 1, label %[[swbb:[^ ]*]] +// +// CHECK: [[swbb]] +// continue corresponds to the loop instead, so generate unreachable. +// CHECK-NEXT: unreachable +// +// CHECK: [[epilog]] +// CHECK: call {{.*}}void @cleanup() +// CHECK-NEXT: ret void + +// C2Y named loops: This is still UB. +void named_break_out_of_finally(void) { +outer: + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + while (check_condition()) + break outer; + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@named_break_out_of_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: br label %[[whilecond:[^ ]*]] +// +// CHECK: [[whilecond]] +// CHECK: br i1 {{.*}}, label %[[whilebody:[^ ]*]], label %[[whileend:[^ ]*]] +// +// CHECK: [[whilebody]] +// CHECK-NEXT: unreachable +// +// CHECK: [[whileend]] +// CHECK-NEXT: ret void + +// C2Y named loops: This is still UB. +void named_continue_out_of_finally(void) { +outer: + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + while (check_condition()) + continue outer; + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@named_continue_out_of_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: br label %[[whilecond:[^ ]*]] +// +// CHECK: [[whilecond]] +// CHECK: br i1 {{.*}}, label %[[whilebody:[^ ]*]], label %[[whileend:[^ ]*]] +// +// CHECK: [[whilebody]] +// CHECK-NEXT: unreachable +// +// CHECK: [[whileend]] +// CHECK-NEXT: ret void + +// C2Y named loops: Named loops within an outlined SEH handler should not +// generate unreachable's. +void named_break_inside_finally(void) { + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + inner: + while (check_condition()) + while (check_condition()) + break inner; + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@named_break_inside_finally@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: br i1 {{.*}}, label %{{[^ ]*}}, label %[[outerend:[^ ]*]] +// CHECK: br i1 {{.*}}, label %[[innerbody:[^ ]*]], label %{{[^ ]*}} +// +// CHECK: [[innerbody]] +// CHECK-NEXT: br label %[[outerend]] +// CHECK-NOT: unreachable +// CHECK: [[outerend]] +// CHECK: ret void + +// The break's loop dest is in the outer __finally, generate unreachable in the +// inner loop while leaving outer loop intact. +void nested_finally_break(void) { + for (int i = 0; i < 4; i++) { + __try { + might_crash(); + } __finally { + while (check_condition()) { + __try { + might_crash(); + } __finally { + break; + } + } + } + } +} + +// CHECK-LABEL: define internal {{.*}}void @"?fin$0@0@nested_finally_break@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK: br label %[[whilecond:[^ ]*]] +// CHECK: call {{.*}}void @"?fin$1@0@nested_finally_break@@"({{.*}}) +// CHECK: br label %[[whilecond]] +// CHECK-NOT: unreachable +// CHECK: ret void + +// CHECK-LABEL: define internal {{.*}}void @"?fin$1@0@nested_finally_break@@"({{.*}}) +// CHECK-SAME: [[finally_attrs]] +// CHECK-NOT: br +// CHECK-NOT: ret void +// CHECK: unreachable + // Look for the absence of noinline. nounwind is expected; any further // attributes should be string attributes. // CHECK: attributes [[finally_attrs]] = { nounwind "{{.*}}" } diff --git a/clang/test/CodeGen/exceptions-seh.c b/clang/test/CodeGen/exceptions-seh.c index a406076d5c5a4..cdeb8a405b748 100644 --- a/clang/test/CodeGen/exceptions-seh.c +++ b/clang/test/CodeGen/exceptions-seh.c @@ -306,4 +306,91 @@ int exception_code_in_except(void) { // CHECK: %[[ret2:[^ ]*]] = load i32, ptr %[[ret_slot]] // CHECK: ret i32 %[[ret2]] +// __except handlers run within the enclosing function, so goto's within the +// function are still technically legal and should produce a br. +// __finally handlers run in a separate function, so goto's out of them are UB +// and should produce unreachable instead (tested in exceptions-seh-finally.c). +int goto_out_of_except_body(void) { + __try { + try_body(0, 0, 0); + } __except(1) { + goto out; + } +out: + return 0; +} + +// CHECK-LABEL: define dso_local {{.*}}i32 @goto_out_of_except_body() +// CHECK: %[[pad:[^ ]*]] = catchpad +// CHECK: catchret from %[[pad]] +// CHECK: br label %[[out:[^ ]*]] +// CHECK-NOT: unreachable +// CHECK: [[out]] +// CHECK: ret i32 0 + +// Filter expressions get outlined into a helper function, so a jump that leaves +// it is UB and should produce unreachable, while a jump to a label inside it +// should still produce a br. +int goto_in_and_out_of_filter(void) { + __try { + try_body(... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/220462 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
