[clang] [Clang][CodeGen] Fix crash and generate UnreachableInst upon branching out of SEH handlers (PR #220462)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 2 17:59:34 PDT 2026
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 at 0@finally_with_func@@"({{[^)]*}})
// CHECK: call {{.*}}void @cleanup_with_func(ptr noundef @"??_C at _0BC@COAGBPGM at 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 at 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 at 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 at 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 at 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 at 0@break_out_of_finally@@"({{.*}})
+
+// CHECK-LABEL: define internal {{.*}}void @"?fin$0 at 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 at 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 at 0@continue_out_of_finally@@"({{.*}})
+
+// CHECK-LABEL: define internal {{.*}}void @"?fin$0 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 0@nested_finally_break@@"({{.*}})
+// CHECK-SAME: [[finally_attrs]]
+// CHECK: br label %[[whilecond:[^ ]*]]
+// CHECK: call {{.*}}void @"?fin$1 at 0@nested_finally_break@@"({{.*}})
+// CHECK: br label %[[whilecond]]
+// CHECK-NOT: unreachable
+// CHECK: ret void
+
+// CHECK-LABEL: define internal {{.*}}void @"?fin$1 at 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
More information about the cfe-commits
mailing list