llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

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 &lt; 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

Reply via email to