[llvm] [WinEH] Emit unreachable on malformed catchpad (PR #222181)

Aaron Danen via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 12:54:36 PDT 2026


https://github.com/aadanen updated https://github.com/llvm/llvm-project/pull/222181

>From dfd06af01a72ee4c4f05833daa38cf4424d059b0 Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Tue, 8 Sep 2026 14:08:19 -0700
Subject: [PATCH 1/9] [WinEH] Emit unreachable on malformed catchpad

format
---
 llvm/lib/CodeGen/WinEHPrepare.cpp             | 149 +++++++++++--
 llvm/test/CodeGen/WinEH/wineh-cloning.ll      |  20 +-
 llvm/test/CodeGen/WinEH/wineh-demotion.ll     |  18 +-
 .../CodeGen/WinEH/wineh-malformed-catchpad.ll | 203 ++++++++++++++++++
 4 files changed, 349 insertions(+), 41 deletions(-)
 create mode 100644 llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index 40cb11cbeb0de..272291dd2f05c 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -19,6 +19,7 @@
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/CodeGen/MachineBasicBlock.h"
 #include "llvm/CodeGen/Passes.h"
 #include "llvm/CodeGen/WinEHFuncInfo.h"
@@ -58,6 +59,65 @@ static cl::opt<bool> DemoteCatchSwitchPHIOnlyOpt(
     "demote-catchswitch-only", cl::Hidden,
     cl::desc("Demote catchswitch BBs only (for wasm EH)"), cl::init(false));
 
+// assumes Personality is one relevant to WinEHPrepare
+static bool isMalformedCatchpad(const CatchPadInst *CPI,
+                                EHPersonality Personality) {
+  switch (Personality) {
+  case EHPersonality::MSVC_CXX: {
+    if (CPI->arg_size() != 3)
+      return true;
+
+    Constant *TypeInfo;
+    GlobalVariable *TypeDescriptor;
+    ConstantInt *Adjectives;
+
+    if (!(TypeInfo = dyn_cast<Constant>(CPI->getArgOperand(0))))
+      return true;
+    if (TypeInfo->isNullValue())
+      return false;
+
+    if (!(TypeDescriptor =
+              dyn_cast<GlobalVariable>(TypeInfo->stripPointerCasts())))
+      return true;
+
+    if (!(Adjectives = dyn_cast<ConstantInt>(CPI->getArgOperand(1))))
+      return true;
+
+    return false;
+  }
+  case EHPersonality::MSVC_X86SEH:
+  case EHPersonality::MSVC_TableSEH: {
+    // Argument 0 is the filter function, or null for a catch-all; only it
+    // is ever consulted, so extra trailing arguments are tolerated.
+    if (CPI->arg_size() == 0)
+      return true;
+
+    Constant *FilterOrNull;
+    if (!(FilterOrNull = dyn_cast<Constant>(CPI->getArgOperand(0))))
+      return true;
+
+    Constant *Filter = FilterOrNull->stripPointerCasts();
+    if (!isa<Function>(Filter) && !Filter->isNullValue())
+      return true;
+
+    return false;
+  }
+  case EHPersonality::CoreCLR: {
+    // Argument 0 is the integer type token; only it is ever consulted, so
+    // extra trailing arguments are tolerated.
+    if (CPI->arg_size() == 0)
+      return true;
+
+    if (!dyn_cast<ConstantInt>(CPI->getArgOperand(0)))
+      return true;
+
+    return false;
+  }
+  default:
+    return false;
+  }
+}
+
 namespace {
 
 class WinEHPrepareImpl {
@@ -80,6 +140,7 @@ class WinEHPrepareImpl {
 
   bool demotePHIsOnFunclets(Function &F, bool DemoteCatchSwitchPHIOnly);
   bool cloneCommonBlocks(Function &F);
+  bool removeMalformedCatchswitch(Function &F);
   bool removeImplausibleInstructions(Function &F);
   bool cleanupPreparedFunclets(Function &F);
   void verifyPreparedFunclets(Function &F);
@@ -162,18 +223,19 @@ static void addTryBlockMapEntry(WinEHFuncInfo &FuncInfo, int TryLow,
   assert(TBME.TryLow <= TBME.TryHigh);
   for (const CatchPadInst *CPI : Handlers) {
     WinEHHandlerType HT;
-    Constant *TypeInfo = cast<Constant>(CPI->getArgOperand(0));
-    if (TypeInfo->isNullValue())
-      HT.TypeDescriptor = nullptr;
-    else
-      HT.TypeDescriptor = cast<GlobalVariable>(TypeInfo->stripPointerCasts());
-    HT.Adjectives = cast<ConstantInt>(CPI->getArgOperand(1))->getZExtValue();
-    HT.Handler = CPI->getParent();
-    if (auto *AI =
-            dyn_cast<AllocaInst>(CPI->getArgOperand(2)->stripPointerCasts()))
-      HT.CatchObj.Alloca = AI;
-    else
-      HT.CatchObj.Alloca = nullptr;
+    HT.TypeDescriptor = nullptr;
+    HT.Adjectives = 0;
+    HT.CatchObj.Alloca = nullptr;
+    if (!isMalformedCatchpad(CPI, EHPersonality::MSVC_CXX)) {
+      Constant *TypeInfo = cast<Constant>(CPI->getArgOperand(0));
+      if (!TypeInfo->isNullValue())
+        HT.TypeDescriptor = cast<GlobalVariable>(TypeInfo->stripPointerCasts());
+      HT.Adjectives = cast<ConstantInt>(CPI->getArgOperand(1))->getZExtValue();
+      HT.Handler = CPI->getParent();
+      if (auto *AI =
+              dyn_cast<AllocaInst>(CPI->getArgOperand(2)->stripPointerCasts()))
+        HT.CatchObj.Alloca = AI;
+    }
     TBME.HandlerArray.push_back(HT);
   }
   FuncInfo.TryBlockMap.push_back(TBME);
@@ -324,9 +386,13 @@ void llvm::calculateSEHStateForAsynchEH(const BasicBlock *BB, int State,
     EHInfo.BlockToStateMap[BB] = State; // Record state
 
     if (isa<CatchPadInst>(It) && isa<CatchReturnInst>(TI)) {
-      const Constant *FilterOrNull = cast<Constant>(
-          cast<CatchPadInst>(It)->getArgOperand(0)->stripPointerCasts());
-      const Function *Filter = dyn_cast<Function>(FilterOrNull);
+      const auto *CPI = cast<CatchPadInst>(It);
+      const Function *Filter = nullptr;
+      if (!isMalformedCatchpad(CPI, EHPersonality::MSVC_X86SEH)) {
+        const Constant *FilterOrNull =
+            cast<Constant>(CPI->getArgOperand(0)->stripPointerCasts());
+        Filter = dyn_cast<Function>(FilterOrNull);
+      }
       if (!Filter || !Filter->getName().starts_with("__IsLocalUnwind"))
         State = EHInfo.SEHUnwindMap[State].ToState; // Retrive next State
     } else if ((isa<CleanupReturnInst>(TI) || isa<CatchReturnInst>(TI)) &&
@@ -514,11 +580,14 @@ static void calculateSEHStateNumbers(WinEHFuncInfo &FuncInfo,
     const auto *CatchPad =
         cast<CatchPadInst>((*CatchSwitch->handler_begin())->getFirstNonPHIIt());
     const BasicBlock *CatchPadBB = CatchPad->getParent();
-    const Constant *FilterOrNull =
-        cast<Constant>(CatchPad->getArgOperand(0)->stripPointerCasts());
-    const Function *Filter = dyn_cast<Function>(FilterOrNull);
-    assert((Filter || FilterOrNull->isNullValue()) &&
-           "unexpected filter value");
+    const Function *Filter = nullptr;
+    if (!isMalformedCatchpad(CatchPad, EHPersonality::MSVC_X86SEH)) {
+      const Constant *FilterOrNull =
+          cast<Constant>(CatchPad->getArgOperand(0)->stripPointerCasts());
+      Filter = dyn_cast<Function>(FilterOrNull);
+      assert((Filter || FilterOrNull->isNullValue()) &&
+             "unexpected filter value");
+    }
     int TryState = addSEHExcept(FuncInfo, ParentState, Filter, CatchPadBB);
 
     // Everything in the __try block uses TryState as its parent state.
@@ -730,8 +799,10 @@ void llvm::calculateClrEHStateNumbers(const Function *Fn,
         // Create the entry for this catch with the appropriate handler
         // properties.
         const auto *Catch = cast<CatchPadInst>(CatchBlock->getFirstNonPHIIt());
-        uint32_t TypeToken = static_cast<uint32_t>(
-            cast<ConstantInt>(Catch->getArgOperand(0))->getZExtValue());
+        uint32_t TypeToken = 0;
+        if (!isMalformedCatchpad(Catch, EHPersonality::CoreCLR))
+          TypeToken = static_cast<uint32_t>(
+              cast<ConstantInt>(Catch->getArgOperand(0))->getZExtValue());
         CatchState =
             addClrEHHandler(FuncInfo, HandlerParentState, FollowerState,
                             ClrHandlerType::Catch, TypeToken, CatchBlock);
@@ -1118,6 +1189,37 @@ bool WinEHPrepareImpl::cloneCommonBlocks(Function &F) {
   return Changed;
 }
 
+bool WinEHPrepareImpl::removeMalformedCatchswitch(Function &F) {
+  bool Changed = false;
+
+  // If a catchpad is malformed, the whole catchswitch is invalidated
+  // therefore, make all child catchpads unreachable
+  SmallPtrSet<CatchSwitchInst *, 4> Invalidated;
+  for (auto &Funclet : FuncletBlocks) {
+    BasicBlock *FuncletPadBB = Funclet.first;
+    Instruction *FirstNonPHI = &*FuncletPadBB->getFirstNonPHIIt();
+    auto *FuncletPad = dyn_cast<FuncletPadInst>(FirstNonPHI);
+    auto *CatchPad = dyn_cast_or_null<CatchPadInst>(FuncletPad);
+
+    if (!CatchPad)
+      continue;
+
+    if (!isMalformedCatchpad(CatchPad, Personality))
+      continue;
+
+    CatchSwitchInst *CatchSwitch = CatchPad->getCatchSwitch();
+    if (!Invalidated.insert(CatchSwitch).second)
+      continue;
+
+    for (BasicBlock *Handler : CatchSwitch->handlers())
+      changeToUnreachable(Handler->getFirstNonPHIIt()->getNextNode());
+
+    Changed = true;
+  }
+
+  return Changed;
+}
+
 bool WinEHPrepareImpl::removeImplausibleInstructions(Function &F) {
   bool Changed = false;
 
@@ -1249,6 +1351,9 @@ bool WinEHPrepareImpl::prepareExplicitEH(Function &F) {
                                            DemoteCatchSwitchPHIOnlyOpt);
 
   if (!DisableCleanups) {
+    assert(!verifyFunction(F, &dbgs()));
+    Changed |= removeMalformedCatchswitch(F);
+
     assert(!verifyFunction(F, &dbgs()));
     Changed |= removeImplausibleInstructions(F);
 
diff --git a/llvm/test/CodeGen/WinEH/wineh-cloning.ll b/llvm/test/CodeGen/WinEH/wineh-cloning.ll
index 2b9e3bd06407b..889009939dff8 100644
--- a/llvm/test/CodeGen/WinEH/wineh-cloning.ll
+++ b/llvm/test/CodeGen/WinEH/wineh-cloning.ll
@@ -23,7 +23,7 @@ entry:
 catch.switch:
   %cs = catchswitch within none [label %catch] unwind to caller
 catch:
-  %cp = catchpad within %cs []
+  %cp = catchpad within %cs [ptr null, i32 0, ptr null]
   br label %noreturn
 noreturn:
   ; %x use colors: {entry, cleanup}
@@ -41,7 +41,7 @@ noreturn:
 ; CHECK: catch.switch:
 ; CHECK:   %cs = catchswitch within none [label %catch] unwind to caller
 ; CHECK: catch:
-; CHECK:   catchpad within %cs []
+; CHECK:   catchpad within %cs [ptr null, i32 0, ptr null]
 ; CHECK-NEXT: call void @llvm.foo(i32 %x)
 ; CHECK: [[EntryCopy]]:
 ; CHECK:   call void @llvm.foo(i32 %x)
@@ -84,7 +84,7 @@ invoke.cont:
 catch.switch:
   %cs = catchswitch within none [label %catch] unwind to caller
 catch:
-  catchpad within %cs []
+  catchpad within %cs [ptr null, i32 0, ptr null]
   br label %shared
 cleanup:
   cleanuppad within none []
@@ -102,7 +102,7 @@ exit:
 ; CHECK:   invoke void @f()
 ; CHECK:     to label %[[exit:[^ ]+]] unwind
 ; CHECK: catch:
-; CHECK:   catchpad within %cs []
+; CHECK:   catchpad within %cs [ptr null, i32 0, ptr null]
 ; CHECK-NEXT: call void @llvm.bar()
 ; CHECK-NEXT: unreachable
 ; CHECK: cleanup:
@@ -120,7 +120,7 @@ entry:
 catch.switch:
   %cs = catchswitch within none [label %catch] unwind to caller
 catch:
-  catchpad within %cs []
+  catchpad within %cs [ptr null, i32 0, ptr null]
   br label %shared
 shared:
   %x = call i32 @llvm.qux()
@@ -152,7 +152,7 @@ exit:
 ; CHECK:  entry:
 ; CHECK:    to label %[[shared_E:[^ ]+]] unwind label %catch.switch
 ; CHECK:  catch:
-; CHECK:    catchpad within %cs []
+; CHECK:    catchpad within %cs [ptr null, i32 0, ptr null]
 ; CHECK:    [[x_C:%[^ ]+]] = call i32 @llvm.qux()
 ; CHECK:    [[i_C:%[^ ]+]] = call i32 @llvm.qux()
 ; CHECK:    [[zt_C:%[^ ]+]] = icmp eq i32 [[i_C]], 0
@@ -210,7 +210,7 @@ outer:
 catch.switch:
   %cs = catchswitch within %o [label %inner] unwind to caller
 inner:
-  %i = catchpad within %cs []
+  %i = catchpad within %cs [ptr null]
   catchret from %i to label %outer.post-inner
 outer.post-inner:
   call void @llvm.foo(i32 %x)
@@ -229,7 +229,7 @@ exit:
 ; CHECK-NEXT:   invoke void @f()
 ; CHECK-NEXT:     to label %outer.ret unwind label %catch.switch
 ; CHECK:      inner:
-; CHECK-NEXT:   %i = catchpad within %cs []
+; CHECK-NEXT:   %i = catchpad within %cs [ptr null]
 ; CHECK-NEXT:   catchret from %i to label %outer.post-inner
 ; CHECK:      outer.post-inner:
 ; CHECK-NEXT:   call void @llvm.foo(i32 %x)
@@ -249,7 +249,7 @@ outer:
   %cs = catchswitch within none [label %catch.body] unwind to caller
 
 catch.body:
-  %catch = catchpad within %cs []
+  %catch = catchpad within %cs [ptr null, i32 0, ptr null]
   catchret from %catch to label %exit
 exit:
   ret void
@@ -266,7 +266,7 @@ unreachable:
 ; CHECK:      outer:
 ; CHECK-NEXT:   %cs = catchswitch within none [label %catch.body] unwind to caller
 ; CHECK:      catch.body:
-; CHECK-NEXT:   %catch = catchpad within %cs []
+; CHECK-NEXT:   %catch = catchpad within %cs [ptr null, i32 0, ptr null]
 ; CHECK-NEXT:   catchret from %catch to label %exit
 ; CHECK:      exit:
 ; CHECK-NEXT:   ret void
diff --git a/llvm/test/CodeGen/WinEH/wineh-demotion.ll b/llvm/test/CodeGen/WinEH/wineh-demotion.ll
index 4e44c081ef586..39ac51adeef23 100644
--- a/llvm/test/CodeGen/WinEH/wineh-demotion.ll
+++ b/llvm/test/CodeGen/WinEH/wineh-demotion.ll
@@ -44,7 +44,7 @@ merge:
   %cs1 = catchswitch within none [label %catch] unwind to caller
 
 catch:
-  %cp = catchpad within %cs1 []
+  %cp = catchpad within %cs1 [ptr null, i32 0, ptr null]
   ; CHECK: catch:
   ; CHECK: [[Reload:%[^ ]+]] = load i32, ptr [[Slot]]
   ; CHECK-NEXT: call void @h(i32 [[Reload]])
@@ -83,7 +83,7 @@ merge.inner:
   %cs1 = catchswitch within none [label %catch.inner] unwind label %merge.outer
 
 catch.inner:
-  %cpinner = catchpad within %cs1 []
+  %cpinner = catchpad within %cs1 [ptr null, i32 0, ptr null]
   ; Need just one store here because only %y is affected
   ; CHECK: catch.inner:
   %z = call i32 @g() [ "funclet"(token %cpinner) ]
@@ -103,9 +103,9 @@ merge.outer:
   %cs2 = catchswitch within none [label %catch.outer] unwind to caller
 
 catch.outer:
-  %cpouter = catchpad within %cs2 []
+  %cpouter = catchpad within %cs2 [ptr null, i32 0, ptr null]
   ; CHECK: catch.outer:
-  ; CHECK: [[CatchPad:%[^ ]+]] = catchpad within %cs2 []
+  ; CHECK: [[CatchPad:%[^ ]+]] = catchpad within %cs2 [ptr null, i32 0, ptr null]
   ; Need to load x and y from two different slots since they're both live
   ; and can have different values (if we came from catch.inner)
   ; CHECK-DAG: load i32, ptr [[Slot1]]
@@ -150,7 +150,7 @@ catchpad.inner:
    %phi.inner = phi i32 [ %l, %left ], [ %r, %right ]
    %cs1 = catchswitch within none [label %catch.inner] unwind label %catchpad.outer
 catch.inner:
-   %cp1 = catchpad within %cs1 []
+   %cp1 = catchpad within %cs1 [ptr null, i32 0, ptr null]
    catchret from %cp1 to label %join
 join:
   ; CHECK: join:
@@ -170,7 +170,7 @@ catch.outer:
    ; CHECK: catch.outer:
    ; CHECK:   [[Reload:%[^ ]+]] = load i32, ptr [[Slot]]
    ; CHECK:   call void @h(i32 [[Reload]])
-   %cp2 = catchpad within %cs2 []
+   %cp2 = catchpad within %cs2 [ptr null, i32 0, ptr null]
    call void @h(i32 %phi.outer) [ "funclet"(token %cp2) ]
    catchret from %cp2 to label %exit
 exit:
@@ -243,7 +243,7 @@ catch:
   ; CHECK:   catchpad within %cs1
   ; CHECK:   [[CatchReload:%[^ ]+]] = load i32, ptr [[CatchSlot]]
   ; CHECK:   call void @h(i32 [[CatchReload]]
-  %cp2 = catchpad within %cs1 []
+  %cp2 = catchpad within %cs1 [ptr null, i32 0, ptr null]
   call void @h(i32 %phi.catch) [ "funclet"(token %cp2) ]
   catchret from %cp2 to label %exit
 
@@ -298,8 +298,8 @@ catchpad:
   %cs1 = catchswitch within none [label %catch] unwind to caller
 catch:
   ; CHECK: catch:
-  ; CHECK-NEXT: %[[CatchPad:[^ ]+]] = catchpad within %cs1 []
-  %cp = catchpad within %cs1 []
+  ; CHECK-NEXT: %[[CatchPad:[^ ]+]] = catchpad within %cs1 [ptr null, i32 0, ptr null]
+  %cp = catchpad within %cs1 [ptr null, i32 0, ptr null]
   %b = call i1 @i() [ "funclet"(token %cp) ]
   br i1 %b, label %left, label %right
 left:
diff --git a/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll b/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll
new file mode 100644
index 0000000000000..6d769e285baad
--- /dev/null
+++ b/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll
@@ -0,0 +1,203 @@
+; RUN: split-file %s %t
+; RUN: llc -o - %t/issue219223_cxx.ll | FileCheck %t/issue219223_cxx.ll
+; RUN: llc -o - %t/issue219223_seh.ll | FileCheck %t/issue219223_seh.ll
+; RUN: llc -o - %t/issue219223_clr.ll | FileCheck %t/issue219223_clr.ll
+; RUN: opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/single.ll | FileCheck %t/single.ll
+; RUN: opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_cxx.ll | FileCheck %t/sibling_cxx.ll
+; RUN: opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_seh.ll | FileCheck %t/sibling_seh.ll
+; RUN: opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_clr.ll | FileCheck %t/sibling_clr.ll
+
+;--- issue219223_cxx.ll
+target triple = "x86_64-pc-linux-gnu"
+
+declare i32 @__CxxFrameHandler3(...)
+declare void @f()
+
+define void @empty_catchpad_cxx() personality ptr @__CxxFrameHandler3 {
+entry:
+  invoke void @f() to label %cont unwind label %dispatch
+
+dispatch:
+  %cs = catchswitch within none [label %catch] unwind to caller
+
+catch:
+  %pad = catchpad within %cs []
+  catchret from %pad to label %cont
+
+cont:
+  ret void
+}
+
+; CHECK-LABEL: empty_catchpad_cxx:
+
+;--- issue219223_seh.ll
+declare i32 @__C_specific_handler(...)
+declare void @f()
+
+define void @empty_catchpad_seh() personality ptr @__C_specific_handler {
+entry:
+  invoke void @f() to label %cont unwind label %dispatch
+
+dispatch:
+  %cs = catchswitch within none [label %catch] unwind to caller
+
+catch:
+  %pad = catchpad within %cs []
+  catchret from %pad to label %cont
+
+cont:
+  ret void
+}
+
+; CHECK-LABEL: empty_catchpad_seh:
+
+;--- issue219223_clr.ll
+declare void @ProcessCLRException(...)
+declare void @f()
+
+define void @empty_catchpad_clr() personality ptr @ProcessCLRException {
+entry:
+  invoke void @f() to label %cont unwind label %dispatch
+
+dispatch:
+  %cs = catchswitch within none [label %catch] unwind to caller
+
+catch:
+  %pad = catchpad within %cs []
+  catchret from %pad to label %cont
+
+cont:
+  ret void
+}
+
+; CHECK-LABEL: empty_catchpad_clr:
+
+;--- single.ll
+declare i32 @__CxxFrameHandler3(...)
+declare void @f()
+
+define void @malformed_single() personality ptr @__CxxFrameHandler3 {
+entry:
+  invoke void @f()
+          to label %cont unwind label %dispatch
+
+dispatch:
+  %cs = catchswitch within none [label %catch] unwind to caller
+
+catch:
+  %pad = catchpad within %cs []
+  catchret from %pad to label %cont
+
+cont:
+  ret void
+}
+
+; CHECK-LABEL: define void @malformed_single()
+; CHECK:      %cs = catchswitch within none [label %catch] unwind to caller
+; CHECK:      catch:
+; CHECK-NEXT:   %pad = catchpad within %cs []
+; CHECK-NEXT:   unreachable
+; CHECK: cont: ; preds = %entry
+; CHECK-NEXT: ret void
+
+;--- sibling_cxx.ll
+declare i32 @__CxxFrameHandler3(...)
+declare void @f()
+
+define void @sibling_cxx() personality ptr @__CxxFrameHandler3 {
+entry:
+  invoke void @f()
+          to label %cont unwind label %dispatch
+
+dispatch:
+  %cs = catchswitch within none [label %catch.bad, label %catch.good] unwind to caller
+
+catch.bad:
+  %pad.bad = catchpad within %cs [ptr null]
+  catchret from %pad.bad to label %cont
+
+catch.good:
+  %pad.good = catchpad within %cs [ptr null, i32 0, ptr null]
+  catchret from %pad.good to label %cont
+
+cont:
+  ret void
+}
+
+; CHECK-LABEL: define void @sibling_cxx()
+; CHECK:      catch.bad:
+; CHECK-NEXT:   %pad.bad = catchpad within %cs [ptr null]
+; CHECK-NEXT:   unreachable
+; CHECK:      catch.good:
+; CHECK-NEXT:   %pad.good = catchpad within %cs [ptr null, i32 0, ptr null]
+; CHECK-NEXT:   unreachable
+; CHECK: cont: ; preds = %entry
+; CHECK-NEXT: ret void
+
+;--- sibling_seh.ll
+declare i32 @__C_specific_handler(...)
+declare void @f()
+
+define void @sibling_seh() personality ptr @__C_specific_handler {
+entry:
+  invoke void @f()
+          to label %cont unwind label %dispatch
+
+dispatch:
+  %cs = catchswitch within none [label %catch.bad, label %catch.good] unwind to caller
+
+catch.bad:
+  %pad.bad = catchpad within %cs []
+  catchret from %pad.bad to label %cont
+
+catch.good:
+  %pad.good = catchpad within %cs [ptr null]
+  catchret from %pad.good to label %cont
+
+cont:
+  ret void
+}
+
+; CHECK-LABEL: define void @sibling_seh()
+; CHECK:      catch.bad:
+; CHECK-NEXT:   %pad.bad = catchpad within %cs []
+; CHECK-NEXT:   unreachable
+; CHECK:      catch.good:
+; CHECK-NEXT:   %pad.good = catchpad within %cs [ptr null]
+; CHECK-NEXT:   unreachable
+; CHECK: cont: ; preds = %entry
+; CHECK-NEXT: ret void
+
+;--- sibling_clr.ll
+declare void @ProcessCLRException(...)
+declare void @f()
+
+define void @sibling_clr() personality ptr @ProcessCLRException {
+entry:
+  invoke void @f()
+          to label %cont unwind label %dispatch
+
+dispatch:
+  %cs = catchswitch within none [label %catch.bad, label %catch.good] unwind to caller
+
+catch.bad:
+  %pad.bad = catchpad within %cs []
+  catchret from %pad.bad to label %cont
+
+catch.good:
+  %pad.good = catchpad within %cs [i32 1]
+  catchret from %pad.good to label %cont
+
+cont:
+  ret void
+}
+
+; CHECK-LABEL: define void @sibling_clr()
+; CHECK:      catch.bad:
+; CHECK-NEXT:   %pad.bad = catchpad within %cs []
+; CHECK-NEXT:   unreachable
+; CHECK:      catch.good:
+; CHECK-NEXT:   %pad.good = catchpad within %cs [i32 1]
+; CHECK-NEXT:   unreachable
+; CHECK: cont: ; preds = %entry
+; CHECK-NEXT: ret void

>From 3a0b372d0fd98d3420b5ef12588d3e02eaeb2a07 Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Wed, 9 Sep 2026 14:06:48 -0700
Subject: [PATCH 2/9] added a scan and diagnostic

---
 llvm/lib/CodeGen/WinEHPrepare.cpp             | 18 +++++++++++++++
 .../CodeGen/WinEH/wineh-malformed-catchpad.ll | 23 ++++++++++++-------
 2 files changed, 33 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index 272291dd2f05c..0d81cecd81168 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -24,6 +24,7 @@
 #include "llvm/CodeGen/Passes.h"
 #include "llvm/CodeGen/WinEHFuncInfo.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/EHPersonalities.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/Module.h"
@@ -137,6 +138,7 @@ class WinEHPrepareImpl {
                           DenseMap<BasicBlock *, Value *> &Loads, Function &F);
   bool prepareExplicitEH(Function &F);
   void colorFunclets(Function &F);
+  void diagnoseMalformedCatchpads(Function &F);
 
   bool demotePHIsOnFunclets(Function &F, bool DemoteCatchSwitchPHIOnly);
   bool cloneCommonBlocks(Function &F);
@@ -200,6 +202,9 @@ bool WinEHPrepareImpl::runOnFunction(Function &Fn) {
   if (!isScopedEHPersonality(Personality))
     return false;
 
+  // Report a diagnostic for each malformed catchpad
+  diagnoseMalformedCatchpads(Fn);
+
   DL = &Fn.getDataLayout();
   return prepareExplicitEH(Fn);
 }
@@ -1335,6 +1340,19 @@ void WinEHPrepareImpl::verifyPreparedFunclets(Function &F) {
 }
 #endif
 
+void WinEHPrepareImpl::diagnoseMalformedCatchpads(Function &F) {
+  for (BasicBlock &BB : F) {
+    for (Instruction &I : BB) {
+      if (CatchPadInst *CPI = dyn_cast<CatchPadInst>(&I)) {
+        if (isMalformedCatchpad(CPI, Personality)) {
+          F.getContext().diagnose(DiagnosticInfoGenericWithLoc(
+              "catchpad with unexpected arguments", F, CPI->getDebugLoc()));
+        }
+      }
+    }
+  }
+}
+
 bool WinEHPrepareImpl::prepareExplicitEH(Function &F) {
   // Remove unreachable blocks.  It is not valuable to assign them a color and
   // their existence can trick us into thinking values are alive when they are
diff --git a/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll b/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll
index 6d769e285baad..9baef294cfc80 100644
--- a/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll
+++ b/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll
@@ -1,14 +1,15 @@
 ; RUN: split-file %s %t
-; RUN: llc -o - %t/issue219223_cxx.ll | FileCheck %t/issue219223_cxx.ll
-; RUN: llc -o - %t/issue219223_seh.ll | FileCheck %t/issue219223_seh.ll
-; RUN: llc -o - %t/issue219223_clr.ll | FileCheck %t/issue219223_clr.ll
-; RUN: opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/single.ll | FileCheck %t/single.ll
-; RUN: opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_cxx.ll | FileCheck %t/sibling_cxx.ll
-; RUN: opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_seh.ll | FileCheck %t/sibling_seh.ll
-; RUN: opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_clr.ll | FileCheck %t/sibling_clr.ll
+; RUN: not llc -o - %t/issue219223_cxx.ll 2>/dev/null | FileCheck %t/issue219223_cxx.ll
+; RUN: not llc -o - %t/issue219223_seh.ll 2>/dev/null | FileCheck %t/issue219223_seh.ll
+; RUN: not llc -o - %t/issue219223_clr.ll 2>/dev/null | FileCheck %t/issue219223_clr.ll
+; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/single.ll 2>/dev/null | FileCheck %t/single.ll
+; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/single.ll 2>&1 >/dev/null | FileCheck %t/single.ll --check-prefix=DIAG
+; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_cxx.ll 2>/dev/null | FileCheck %t/sibling_cxx.ll
+; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_seh.ll 2>/dev/null | FileCheck %t/sibling_seh.ll
+; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_clr.ll 2>/dev/null | FileCheck %t/sibling_clr.ll
 
 ;--- issue219223_cxx.ll
-target triple = "x86_64-pc-linux-gnu"
+target triple = "x86_64-pc-windows-msvc"
 
 declare i32 @__CxxFrameHandler3(...)
 declare void @f()
@@ -31,6 +32,8 @@ cont:
 ; CHECK-LABEL: empty_catchpad_cxx:
 
 ;--- issue219223_seh.ll
+target triple = "x86_64-pc-windows-msvc"
+
 declare i32 @__C_specific_handler(...)
 declare void @f()
 
@@ -52,6 +55,8 @@ cont:
 ; CHECK-LABEL: empty_catchpad_seh:
 
 ;--- issue219223_clr.ll
+target triple = "x86_64-pc-windows-msvc"
+
 declare void @ProcessCLRException(...)
 declare void @f()
 
@@ -100,6 +105,8 @@ cont:
 ; CHECK: cont: ; preds = %entry
 ; CHECK-NEXT: ret void
 
+; DIAG: catchpad with unexpected arguments
+
 ;--- sibling_cxx.ll
 declare i32 @__CxxFrameHandler3(...)
 declare void @f()

>From 72fdc061e3d347c538f4693c604d9f491d65d058 Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Wed, 9 Sep 2026 14:48:21 -0700
Subject: [PATCH 3/9] moved diagnostic to main catchpad walk

---
 llvm/lib/CodeGen/WinEHPrepare.cpp | 20 +++-----------------
 1 file changed, 3 insertions(+), 17 deletions(-)

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index 0d81cecd81168..0a3a7af342f56 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -138,7 +138,6 @@ class WinEHPrepareImpl {
                           DenseMap<BasicBlock *, Value *> &Loads, Function &F);
   bool prepareExplicitEH(Function &F);
   void colorFunclets(Function &F);
-  void diagnoseMalformedCatchpads(Function &F);
 
   bool demotePHIsOnFunclets(Function &F, bool DemoteCatchSwitchPHIOnly);
   bool cloneCommonBlocks(Function &F);
@@ -202,9 +201,6 @@ bool WinEHPrepareImpl::runOnFunction(Function &Fn) {
   if (!isScopedEHPersonality(Personality))
     return false;
 
-  // Report a diagnostic for each malformed catchpad
-  diagnoseMalformedCatchpads(Fn);
-
   DL = &Fn.getDataLayout();
   return prepareExplicitEH(Fn);
 }
@@ -1212,6 +1208,9 @@ bool WinEHPrepareImpl::removeMalformedCatchswitch(Function &F) {
     if (!isMalformedCatchpad(CatchPad, Personality))
       continue;
 
+    F.getContext().diagnose(DiagnosticInfoGenericWithLoc(
+        "catchpad with unexpected arguments", F, CatchPad->getDebugLoc()));
+
     CatchSwitchInst *CatchSwitch = CatchPad->getCatchSwitch();
     if (!Invalidated.insert(CatchSwitch).second)
       continue;
@@ -1340,19 +1339,6 @@ void WinEHPrepareImpl::verifyPreparedFunclets(Function &F) {
 }
 #endif
 
-void WinEHPrepareImpl::diagnoseMalformedCatchpads(Function &F) {
-  for (BasicBlock &BB : F) {
-    for (Instruction &I : BB) {
-      if (CatchPadInst *CPI = dyn_cast<CatchPadInst>(&I)) {
-        if (isMalformedCatchpad(CPI, Personality)) {
-          F.getContext().diagnose(DiagnosticInfoGenericWithLoc(
-              "catchpad with unexpected arguments", F, CPI->getDebugLoc()));
-        }
-      }
-    }
-  }
-}
-
 bool WinEHPrepareImpl::prepareExplicitEH(Function &F) {
   // Remove unreachable blocks.  It is not valuable to assign them a color and
   // their existence can trick us into thinking values are alive when they are

>From f8787ace55ca0fb56273fb3e4d7c0df63c378fca Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Tue, 22 Sep 2026 09:38:45 -0700
Subject: [PATCH 4/9] moved logic fully to win-eh-prepare

---
 llvm/lib/CodeGen/WinEHPrepare.cpp             | 161 +++++++++---------
 .../CodeGen/WinEH/wineh-malformed-catchpad.ll | 151 +++-------------
 2 files changed, 104 insertions(+), 208 deletions(-)

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index 0a3a7af342f56..acbe2687cea19 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -23,6 +23,7 @@
 #include "llvm/CodeGen/MachineBasicBlock.h"
 #include "llvm/CodeGen/Passes.h"
 #include "llvm/CodeGen/WinEHFuncInfo.h"
+#include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/EHPersonalities.h"
@@ -60,7 +61,6 @@ static cl::opt<bool> DemoteCatchSwitchPHIOnlyOpt(
     "demote-catchswitch-only", cl::Hidden,
     cl::desc("Demote catchswitch BBs only (for wasm EH)"), cl::init(false));
 
-// assumes Personality is one relevant to WinEHPrepare
 static bool isMalformedCatchpad(const CatchPadInst *CPI,
                                 EHPersonality Personality) {
   switch (Personality) {
@@ -88,8 +88,6 @@ static bool isMalformedCatchpad(const CatchPadInst *CPI,
   }
   case EHPersonality::MSVC_X86SEH:
   case EHPersonality::MSVC_TableSEH: {
-    // Argument 0 is the filter function, or null for a catch-all; only it
-    // is ever consulted, so extra trailing arguments are tolerated.
     if (CPI->arg_size() == 0)
       return true;
 
@@ -104,8 +102,6 @@ static bool isMalformedCatchpad(const CatchPadInst *CPI,
     return false;
   }
   case EHPersonality::CoreCLR: {
-    // Argument 0 is the integer type token; only it is ever consulted, so
-    // extra trailing arguments are tolerated.
     if (CPI->arg_size() == 0)
       return true;
 
@@ -115,7 +111,7 @@ static bool isMalformedCatchpad(const CatchPadInst *CPI,
     return false;
   }
   default:
-    return false;
+    llvm_unreachable("Unsupported Personality for WinEH");
   }
 }
 
@@ -141,7 +137,7 @@ class WinEHPrepareImpl {
 
   bool demotePHIsOnFunclets(Function &F, bool DemoteCatchSwitchPHIOnly);
   bool cloneCommonBlocks(Function &F);
-  bool removeMalformedCatchswitch(Function &F);
+  bool removeMalformedCatchswitch(BasicBlock *FuncletPadBB);
   bool removeImplausibleInstructions(Function &F);
   bool cleanupPreparedFunclets(Function &F);
   void verifyPreparedFunclets(Function &F);
@@ -224,19 +220,20 @@ static void addTryBlockMapEntry(WinEHFuncInfo &FuncInfo, int TryLow,
   assert(TBME.TryLow <= TBME.TryHigh);
   for (const CatchPadInst *CPI : Handlers) {
     WinEHHandlerType HT;
-    HT.TypeDescriptor = nullptr;
-    HT.Adjectives = 0;
-    HT.CatchObj.Alloca = nullptr;
-    if (!isMalformedCatchpad(CPI, EHPersonality::MSVC_CXX)) {
-      Constant *TypeInfo = cast<Constant>(CPI->getArgOperand(0));
-      if (!TypeInfo->isNullValue())
-        HT.TypeDescriptor = cast<GlobalVariable>(TypeInfo->stripPointerCasts());
-      HT.Adjectives = cast<ConstantInt>(CPI->getArgOperand(1))->getZExtValue();
-      HT.Handler = CPI->getParent();
-      if (auto *AI =
-              dyn_cast<AllocaInst>(CPI->getArgOperand(2)->stripPointerCasts()))
-        HT.CatchObj.Alloca = AI;
-    }
+    assert(!isMalformedCatchpad(CPI, EHPersonality::MSVC_CXX) &&
+           "Malformed CatchPadInst not caught by win-eh-prepare");
+    Constant *TypeInfo = cast<Constant>(CPI->getArgOperand(0));
+    if (TypeInfo->isNullValue())
+      HT.TypeDescriptor = nullptr;
+    else
+      HT.TypeDescriptor = cast<GlobalVariable>(TypeInfo->stripPointerCasts());
+    HT.Adjectives = cast<ConstantInt>(CPI->getArgOperand(1))->getZExtValue();
+    HT.Handler = CPI->getParent();
+    if (auto *AI =
+            dyn_cast<AllocaInst>(CPI->getArgOperand(2)->stripPointerCasts()))
+      HT.CatchObj.Alloca = AI;
+    else
+      HT.CatchObj.Alloca = nullptr;
     TBME.HandlerArray.push_back(HT);
   }
   FuncInfo.TryBlockMap.push_back(TBME);
@@ -387,13 +384,11 @@ void llvm::calculateSEHStateForAsynchEH(const BasicBlock *BB, int State,
     EHInfo.BlockToStateMap[BB] = State; // Record state
 
     if (isa<CatchPadInst>(It) && isa<CatchReturnInst>(TI)) {
-      const auto *CPI = cast<CatchPadInst>(It);
-      const Function *Filter = nullptr;
-      if (!isMalformedCatchpad(CPI, EHPersonality::MSVC_X86SEH)) {
-        const Constant *FilterOrNull =
-            cast<Constant>(CPI->getArgOperand(0)->stripPointerCasts());
-        Filter = dyn_cast<Function>(FilterOrNull);
-      }
+      assert(!isMalformedCatchpad(cast<CatchPadInst>(It), EHPersonality::MSVC_X86SEH) &&
+             "Malformed CatchPadInst not caught by win-eh-prepare");
+      const Constant *FilterOrNull = cast<Constant>(
+          cast<CatchPadInst>(It)->getArgOperand(0)->stripPointerCasts());
+      const Function *Filter = dyn_cast<Function>(FilterOrNull);
       if (!Filter || !Filter->getName().starts_with("__IsLocalUnwind"))
         State = EHInfo.SEHUnwindMap[State].ToState; // Retrive next State
     } else if ((isa<CleanupReturnInst>(TI) || isa<CatchReturnInst>(TI)) &&
@@ -581,14 +576,13 @@ static void calculateSEHStateNumbers(WinEHFuncInfo &FuncInfo,
     const auto *CatchPad =
         cast<CatchPadInst>((*CatchSwitch->handler_begin())->getFirstNonPHIIt());
     const BasicBlock *CatchPadBB = CatchPad->getParent();
-    const Function *Filter = nullptr;
-    if (!isMalformedCatchpad(CatchPad, EHPersonality::MSVC_X86SEH)) {
-      const Constant *FilterOrNull =
-          cast<Constant>(CatchPad->getArgOperand(0)->stripPointerCasts());
-      Filter = dyn_cast<Function>(FilterOrNull);
-      assert((Filter || FilterOrNull->isNullValue()) &&
-             "unexpected filter value");
-    }
+    assert(!isMalformedCatchpad(CatchPad, EHPersonality::MSVC_X86SEH) &&
+           "Malformed CatchPadInst not caught by win-eh-prepare");
+    const Constant *FilterOrNull =
+        cast<Constant>(CatchPad->getArgOperand(0)->stripPointerCasts());
+    const Function *Filter = dyn_cast<Function>(FilterOrNull);
+    assert((Filter || FilterOrNull->isNullValue()) &&
+           "unexpected filter value");
     int TryState = addSEHExcept(FuncInfo, ParentState, Filter, CatchPadBB);
 
     // Everything in the __try block uses TryState as its parent state.
@@ -800,10 +794,10 @@ void llvm::calculateClrEHStateNumbers(const Function *Fn,
         // Create the entry for this catch with the appropriate handler
         // properties.
         const auto *Catch = cast<CatchPadInst>(CatchBlock->getFirstNonPHIIt());
-        uint32_t TypeToken = 0;
-        if (!isMalformedCatchpad(Catch, EHPersonality::CoreCLR))
-          TypeToken = static_cast<uint32_t>(
-              cast<ConstantInt>(Catch->getArgOperand(0))->getZExtValue());
+        assert(!isMalformedCatchpad(Catch, EHPersonality::CoreCLR) &&
+               "Malformed CatchPadInst not caught by win-eh-prepare");
+        uint32_t TypeToken = static_cast<uint32_t>(
+            cast<ConstantInt>(Catch->getArgOperand(0))->getZExtValue());
         CatchState =
             addClrEHHandler(FuncInfo, HandlerParentState, FollowerState,
                             ClrHandlerType::Catch, TypeToken, CatchBlock);
@@ -985,6 +979,57 @@ bool WinEHPrepareImpl::demotePHIsOnFunclets(Function &F,
   return Changed;
 }
 
+
+bool WinEHPrepareImpl::removeMalformedCatchswitch(BasicBlock *FuncletPadBB) {
+  // If a catchpad is malformed, the whole catchswitch is invalidated
+  // therefore, make all of its catchpads unreachable
+  CatchPadInst *CatchPad =
+      dyn_cast<CatchPadInst>(FuncletPadBB->getFirstNonPHIIt());
+
+  if (!CatchPad)
+    return false;
+
+  if (!isMalformedCatchpad(CatchPad, Personality))
+    return false;
+
+  CatchPad->getContext().diagnose(DiagnosticInfoGenericWithLoc(
+      "catchpad with unexpected arguments", *FuncletPadBB->getParent(),
+      CatchPad->getDebugLoc()));
+
+  CatchSwitchInst *CatchSwitch = CatchPad->getCatchSwitch();
+  for (BasicBlock *Handler : CatchSwitch->handlers()) {
+    if (CatchPadInst *CPI =
+            dyn_cast<CatchPadInst>(Handler->getFirstNonPHIIt())) {
+      LLVMContext &CTX = CPI->getContext();
+      IRBuilder<> Builder(CPI);
+
+      // default values for the CatchPad's args
+      SmallVector<Value *, 3> args;
+      Value *nullPtr = ConstantPointerNull::get(PointerType::getUnqual(CTX));
+      Value *constantZero = ConstantInt::get(Type::getInt32Ty(CTX), 0);
+      switch (Personality) {
+      case EHPersonality::MSVC_CXX:
+        args = {nullPtr, constantZero, nullPtr};
+        break;
+      case EHPersonality::MSVC_X86SEH:
+      case EHPersonality::MSVC_TableSEH:
+        args = {nullPtr};
+        break;
+      case EHPersonality::CoreCLR:
+        args = {constantZero};
+        break;
+      default:
+        llvm_unreachable("Unsupported Personality for WinEH");
+      };
+      Value *NewCatchPad = Builder.CreateCatchPad(CPI->getParentPad(), args, CPI->getName());
+      CPI->replaceAllUsesWith(NewCatchPad);
+      changeToUnreachable(CPI);
+    }
+  }
+
+  return true;
+}
+
 bool WinEHPrepareImpl::cloneCommonBlocks(Function &F) {
   bool Changed = false;
 
@@ -993,6 +1038,7 @@ bool WinEHPrepareImpl::cloneCommonBlocks(Function &F) {
   // *and* the new basic blocks themselves.
   for (auto &Funclets : FuncletBlocks) {
     BasicBlock *FuncletPadBB = Funclets.first;
+    Changed |= removeMalformedCatchswitch(FuncletPadBB);
     std::vector<BasicBlock *> &BlocksInFunclet = Funclets.second;
     Value *FuncletToken;
     if (FuncletPadBB == &F.getEntryBlock())
@@ -1190,40 +1236,6 @@ bool WinEHPrepareImpl::cloneCommonBlocks(Function &F) {
   return Changed;
 }
 
-bool WinEHPrepareImpl::removeMalformedCatchswitch(Function &F) {
-  bool Changed = false;
-
-  // If a catchpad is malformed, the whole catchswitch is invalidated
-  // therefore, make all child catchpads unreachable
-  SmallPtrSet<CatchSwitchInst *, 4> Invalidated;
-  for (auto &Funclet : FuncletBlocks) {
-    BasicBlock *FuncletPadBB = Funclet.first;
-    Instruction *FirstNonPHI = &*FuncletPadBB->getFirstNonPHIIt();
-    auto *FuncletPad = dyn_cast<FuncletPadInst>(FirstNonPHI);
-    auto *CatchPad = dyn_cast_or_null<CatchPadInst>(FuncletPad);
-
-    if (!CatchPad)
-      continue;
-
-    if (!isMalformedCatchpad(CatchPad, Personality))
-      continue;
-
-    F.getContext().diagnose(DiagnosticInfoGenericWithLoc(
-        "catchpad with unexpected arguments", F, CatchPad->getDebugLoc()));
-
-    CatchSwitchInst *CatchSwitch = CatchPad->getCatchSwitch();
-    if (!Invalidated.insert(CatchSwitch).second)
-      continue;
-
-    for (BasicBlock *Handler : CatchSwitch->handlers())
-      changeToUnreachable(Handler->getFirstNonPHIIt()->getNextNode());
-
-    Changed = true;
-  }
-
-  return Changed;
-}
-
 bool WinEHPrepareImpl::removeImplausibleInstructions(Function &F) {
   bool Changed = false;
 
@@ -1355,9 +1367,6 @@ bool WinEHPrepareImpl::prepareExplicitEH(Function &F) {
                                            DemoteCatchSwitchPHIOnlyOpt);
 
   if (!DisableCleanups) {
-    assert(!verifyFunction(F, &dbgs()));
-    Changed |= removeMalformedCatchswitch(F);
-
     assert(!verifyFunction(F, &dbgs()));
     Changed |= removeImplausibleInstructions(F);
 
diff --git a/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll b/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll
index 9baef294cfc80..c9a2df44a14b3 100644
--- a/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll
+++ b/llvm/test/CodeGen/WinEH/wineh-malformed-catchpad.ll
@@ -1,120 +1,16 @@
 ; RUN: split-file %s %t
-; RUN: not llc -o - %t/issue219223_cxx.ll 2>/dev/null | FileCheck %t/issue219223_cxx.ll
-; RUN: not llc -o - %t/issue219223_seh.ll 2>/dev/null | FileCheck %t/issue219223_seh.ll
-; RUN: not llc -o - %t/issue219223_clr.ll 2>/dev/null | FileCheck %t/issue219223_clr.ll
-; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/single.ll 2>/dev/null | FileCheck %t/single.ll
-; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/single.ll 2>&1 >/dev/null | FileCheck %t/single.ll --check-prefix=DIAG
-; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_cxx.ll 2>/dev/null | FileCheck %t/sibling_cxx.ll
-; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_seh.ll 2>/dev/null | FileCheck %t/sibling_seh.ll
-; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/sibling_clr.ll 2>/dev/null | FileCheck %t/sibling_clr.ll
-
-;--- issue219223_cxx.ll
-target triple = "x86_64-pc-windows-msvc"
+; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/cxx.ll 2>/dev/null | FileCheck %t/cxx.ll
+; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/seh.ll 2>/dev/null | FileCheck %t/seh.ll
+; RUN: not opt -mtriple=x86_64-pc-windows-msvc -S -passes=win-eh-prepare < %t/clr.ll 2>/dev/null | FileCheck %t/clr.ll
 
+;--- cxx.ll
 declare i32 @__CxxFrameHandler3(...)
 declare void @f()
 
-define void @empty_catchpad_cxx() personality ptr @__CxxFrameHandler3 {
-entry:
-  invoke void @f() to label %cont unwind label %dispatch
-
-dispatch:
-  %cs = catchswitch within none [label %catch] unwind to caller
-
-catch:
-  %pad = catchpad within %cs []
-  catchret from %pad to label %cont
-
-cont:
-  ret void
-}
-
-; CHECK-LABEL: empty_catchpad_cxx:
-
-;--- issue219223_seh.ll
-target triple = "x86_64-pc-windows-msvc"
-
-declare i32 @__C_specific_handler(...)
-declare void @f()
-
-define void @empty_catchpad_seh() personality ptr @__C_specific_handler {
-entry:
-  invoke void @f() to label %cont unwind label %dispatch
-
-dispatch:
-  %cs = catchswitch within none [label %catch] unwind to caller
-
-catch:
-  %pad = catchpad within %cs []
-  catchret from %pad to label %cont
-
-cont:
-  ret void
-}
-
-; CHECK-LABEL: empty_catchpad_seh:
-
-;--- issue219223_clr.ll
-target triple = "x86_64-pc-windows-msvc"
-
-declare void @ProcessCLRException(...)
-declare void @f()
-
-define void @empty_catchpad_clr() personality ptr @ProcessCLRException {
-entry:
-  invoke void @f() to label %cont unwind label %dispatch
-
-dispatch:
-  %cs = catchswitch within none [label %catch] unwind to caller
-
-catch:
-  %pad = catchpad within %cs []
-  catchret from %pad to label %cont
-
-cont:
-  ret void
-}
-
-; CHECK-LABEL: empty_catchpad_clr:
-
-;--- single.ll
-declare i32 @__CxxFrameHandler3(...)
-declare void @f()
-
-define void @malformed_single() personality ptr @__CxxFrameHandler3 {
+define void @cxx() personality ptr @__CxxFrameHandler3 {
 entry:
   invoke void @f()
-          to label %cont unwind label %dispatch
-
-dispatch:
-  %cs = catchswitch within none [label %catch] unwind to caller
-
-catch:
-  %pad = catchpad within %cs []
-  catchret from %pad to label %cont
-
-cont:
-  ret void
-}
-
-; CHECK-LABEL: define void @malformed_single()
-; CHECK:      %cs = catchswitch within none [label %catch] unwind to caller
-; CHECK:      catch:
-; CHECK-NEXT:   %pad = catchpad within %cs []
-; CHECK-NEXT:   unreachable
-; CHECK: cont: ; preds = %entry
-; CHECK-NEXT: ret void
-
-; DIAG: catchpad with unexpected arguments
-
-;--- sibling_cxx.ll
-declare i32 @__CxxFrameHandler3(...)
-declare void @f()
-
-define void @sibling_cxx() personality ptr @__CxxFrameHandler3 {
-entry:
-  invoke void @f()
-          to label %cont unwind label %dispatch
+  to label %cont unwind label %dispatch
 
 dispatch:
   %cs = catchswitch within none [label %catch.bad, label %catch.good] unwind to caller
@@ -124,31 +20,28 @@ catch.bad:
   catchret from %pad.bad to label %cont
 
 catch.good:
-  %pad.good = catchpad within %cs [ptr null, i32 0, ptr null]
+  %pad.good = catchpad within %cs [ptr null, i32 1, ptr null]
   catchret from %pad.good to label %cont
 
 cont:
   ret void
 }
 
-; CHECK-LABEL: define void @sibling_cxx()
+; CHECK-LABEL: define void @cxx()
 ; CHECK:      catch.bad:
-; CHECK-NEXT:   %pad.bad = catchpad within %cs [ptr null]
-; CHECK-NEXT:   unreachable
-; CHECK:      catch.good:
-; CHECK-NEXT:   %pad.good = catchpad within %cs [ptr null, i32 0, ptr null]
+; CHECK-NEXT:   %pad.bad1 = catchpad within %cs [ptr null, i32 0, ptr null]
 ; CHECK-NEXT:   unreachable
 ; CHECK: cont: ; preds = %entry
 ; CHECK-NEXT: ret void
 
-;--- sibling_seh.ll
+;--- seh.ll
 declare i32 @__C_specific_handler(...)
 declare void @f()
 
-define void @sibling_seh() personality ptr @__C_specific_handler {
+define void @seh() personality ptr @__C_specific_handler {
 entry:
   invoke void @f()
-          to label %cont unwind label %dispatch
+  to label %cont unwind label %dispatch
 
 dispatch:
   %cs = catchswitch within none [label %catch.bad, label %catch.good] unwind to caller
@@ -165,24 +58,21 @@ cont:
   ret void
 }
 
-; CHECK-LABEL: define void @sibling_seh()
+; CHECK-LABEL: define void @seh()
 ; CHECK:      catch.bad:
-; CHECK-NEXT:   %pad.bad = catchpad within %cs []
-; CHECK-NEXT:   unreachable
-; CHECK:      catch.good:
-; CHECK-NEXT:   %pad.good = catchpad within %cs [ptr null]
+; CHECK-NEXT:   %pad.bad1 = catchpad within %cs [ptr null]
 ; CHECK-NEXT:   unreachable
 ; CHECK: cont: ; preds = %entry
 ; CHECK-NEXT: ret void
 
-;--- sibling_clr.ll
+;--- clr.ll
 declare void @ProcessCLRException(...)
 declare void @f()
 
-define void @sibling_clr() personality ptr @ProcessCLRException {
+define void @clr() personality ptr @ProcessCLRException {
 entry:
   invoke void @f()
-          to label %cont unwind label %dispatch
+  to label %cont unwind label %dispatch
 
 dispatch:
   %cs = catchswitch within none [label %catch.bad, label %catch.good] unwind to caller
@@ -199,12 +89,9 @@ cont:
   ret void
 }
 
-; CHECK-LABEL: define void @sibling_clr()
+; CHECK-LABEL: define void @clr()
 ; CHECK:      catch.bad:
-; CHECK-NEXT:   %pad.bad = catchpad within %cs []
-; CHECK-NEXT:   unreachable
-; CHECK:      catch.good:
-; CHECK-NEXT:   %pad.good = catchpad within %cs [i32 1]
+; CHECK-NEXT:   %pad.bad1 = catchpad within %cs [i32 0]
 ; CHECK-NEXT:   unreachable
 ; CHECK: cont: ; preds = %entry
 ; CHECK-NEXT: ret void

>From 4d4d82822f37237663ecd08370f078121b2275ca Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Tue, 22 Sep 2026 09:51:10 -0700
Subject: [PATCH 5/9] format

---
 llvm/lib/CodeGen/WinEHPrepare.cpp | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index acbe2687cea19..1ecbc4205b0dc 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -23,10 +23,10 @@
 #include "llvm/CodeGen/MachineBasicBlock.h"
 #include "llvm/CodeGen/Passes.h"
 #include "llvm/CodeGen/WinEHFuncInfo.h"
-#include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/EHPersonalities.h"
+#include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/Module.h"
 #include "llvm/IR/Verifier.h"
@@ -384,7 +384,8 @@ void llvm::calculateSEHStateForAsynchEH(const BasicBlock *BB, int State,
     EHInfo.BlockToStateMap[BB] = State; // Record state
 
     if (isa<CatchPadInst>(It) && isa<CatchReturnInst>(TI)) {
-      assert(!isMalformedCatchpad(cast<CatchPadInst>(It), EHPersonality::MSVC_X86SEH) &&
+      assert(!isMalformedCatchpad(cast<CatchPadInst>(It),
+                                  EHPersonality::MSVC_X86SEH) &&
              "Malformed CatchPadInst not caught by win-eh-prepare");
       const Constant *FilterOrNull = cast<Constant>(
           cast<CatchPadInst>(It)->getArgOperand(0)->stripPointerCasts());
@@ -979,7 +980,6 @@ bool WinEHPrepareImpl::demotePHIsOnFunclets(Function &F,
   return Changed;
 }
 
-
 bool WinEHPrepareImpl::removeMalformedCatchswitch(BasicBlock *FuncletPadBB) {
   // If a catchpad is malformed, the whole catchswitch is invalidated
   // therefore, make all of its catchpads unreachable
@@ -1021,7 +1021,8 @@ bool WinEHPrepareImpl::removeMalformedCatchswitch(BasicBlock *FuncletPadBB) {
       default:
         llvm_unreachable("Unsupported Personality for WinEH");
       };
-      Value *NewCatchPad = Builder.CreateCatchPad(CPI->getParentPad(), args, CPI->getName());
+      Value *NewCatchPad =
+          Builder.CreateCatchPad(CPI->getParentPad(), args, CPI->getName());
       CPI->replaceAllUsesWith(NewCatchPad);
       changeToUnreachable(CPI);
     }

>From b58ca32b35806dca73d14c5d3b2fce5ec792d38d Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Tue, 22 Sep 2026 12:11:59 -0700
Subject: [PATCH 6/9] clean up code

---
 llvm/lib/CodeGen/WinEHPrepare.cpp | 25 ++++++++++---------------
 1 file changed, 10 insertions(+), 15 deletions(-)

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index 1ecbc4205b0dc..80ee46b5c586e 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -69,19 +69,13 @@ static bool isMalformedCatchpad(const CatchPadInst *CPI,
       return true;
 
     Constant *TypeInfo;
-    GlobalVariable *TypeDescriptor;
-    ConstantInt *Adjectives;
-
     if (!(TypeInfo = dyn_cast<Constant>(CPI->getArgOperand(0))))
       return true;
-    if (TypeInfo->isNullValue())
-      return false;
-
-    if (!(TypeDescriptor =
-              dyn_cast<GlobalVariable>(TypeInfo->stripPointerCasts())))
+    if (!TypeInfo->isNullValue() &&
+        !isa<GlobalVariable>(TypeInfo->stripPointerCasts()))
       return true;
 
-    if (!(Adjectives = dyn_cast<ConstantInt>(CPI->getArgOperand(1))))
+    if (!isa<ConstantInt>(CPI->getArgOperand(1)))
       return true;
 
     return false;
@@ -96,7 +90,7 @@ static bool isMalformedCatchpad(const CatchPadInst *CPI,
       return true;
 
     Constant *Filter = FilterOrNull->stripPointerCasts();
-    if (!isa<Function>(Filter) && !Filter->isNullValue())
+    if (!Filter->isNullValue() && !isa<Function>(Filter))
       return true;
 
     return false;
@@ -137,7 +131,7 @@ class WinEHPrepareImpl {
 
   bool demotePHIsOnFunclets(Function &F, bool DemoteCatchSwitchPHIOnly);
   bool cloneCommonBlocks(Function &F);
-  bool removeMalformedCatchswitch(BasicBlock *FuncletPadBB);
+  bool removeMalformedCatchswitch(Value *FuncletToken);
   bool removeImplausibleInstructions(Function &F);
   bool cleanupPreparedFunclets(Function &F);
   void verifyPreparedFunclets(Function &F);
@@ -980,11 +974,11 @@ bool WinEHPrepareImpl::demotePHIsOnFunclets(Function &F,
   return Changed;
 }
 
-bool WinEHPrepareImpl::removeMalformedCatchswitch(BasicBlock *FuncletPadBB) {
+bool WinEHPrepareImpl::removeMalformedCatchswitch(Value *FuncletToken) {
   // If a catchpad is malformed, the whole catchswitch is invalidated
   // therefore, make all of its catchpads unreachable
   CatchPadInst *CatchPad =
-      dyn_cast<CatchPadInst>(FuncletPadBB->getFirstNonPHIIt());
+      dyn_cast<CatchPadInst>(FuncletToken);
 
   if (!CatchPad)
     return false;
@@ -993,7 +987,7 @@ bool WinEHPrepareImpl::removeMalformedCatchswitch(BasicBlock *FuncletPadBB) {
     return false;
 
   CatchPad->getContext().diagnose(DiagnosticInfoGenericWithLoc(
-      "catchpad with unexpected arguments", *FuncletPadBB->getParent(),
+      "catchpad with unexpected arguments", *CatchPad->getParent()->getParent(),
       CatchPad->getDebugLoc()));
 
   CatchSwitchInst *CatchSwitch = CatchPad->getCatchSwitch();
@@ -1039,7 +1033,6 @@ bool WinEHPrepareImpl::cloneCommonBlocks(Function &F) {
   // *and* the new basic blocks themselves.
   for (auto &Funclets : FuncletBlocks) {
     BasicBlock *FuncletPadBB = Funclets.first;
-    Changed |= removeMalformedCatchswitch(FuncletPadBB);
     std::vector<BasicBlock *> &BlocksInFunclet = Funclets.second;
     Value *FuncletToken;
     if (FuncletPadBB == &F.getEntryBlock())
@@ -1047,6 +1040,8 @@ bool WinEHPrepareImpl::cloneCommonBlocks(Function &F) {
     else
       FuncletToken = &*FuncletPadBB->getFirstNonPHIIt();
 
+    Changed |= removeMalformedCatchswitch(FuncletToken);
+
     std::vector<std::pair<BasicBlock *, BasicBlock *>> Orig2Clone;
     ValueToValueMapTy VMap;
     for (BasicBlock *BB : BlocksInFunclet) {

>From 8f7a4697578661cc1da5a790f3b361a4b9fae85e Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Tue, 22 Sep 2026 12:45:19 -0700
Subject: [PATCH 7/9] handle Wasm_CXX

---
 llvm/lib/CodeGen/WinEHPrepare.cpp | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index 80ee46b5c586e..6185ff627c7d5 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -99,7 +99,13 @@ static bool isMalformedCatchpad(const CatchPadInst *CPI,
     if (CPI->arg_size() == 0)
       return true;
 
-    if (!dyn_cast<ConstantInt>(CPI->getArgOperand(0)))
+    if (!isa<ConstantInt>(CPI->getArgOperand(0)))
+      return true;
+
+    return false;
+  }
+  case EHPersonality::Wasm_CXX: {
+    if (CPI->arg_size() == 1 && !isa<Constant>(CPI->getArgOperand(0)))
       return true;
 
     return false;
@@ -1007,6 +1013,7 @@ bool WinEHPrepareImpl::removeMalformedCatchswitch(Value *FuncletToken) {
         break;
       case EHPersonality::MSVC_X86SEH:
       case EHPersonality::MSVC_TableSEH:
+      case EHPersonality::Wasm_CXX:
         args = {nullPtr};
         break;
       case EHPersonality::CoreCLR:
@@ -1037,10 +1044,11 @@ bool WinEHPrepareImpl::cloneCommonBlocks(Function &F) {
     Value *FuncletToken;
     if (FuncletPadBB == &F.getEntryBlock())
       FuncletToken = ConstantTokenNone::get(F.getContext());
-    else
+    else {
       FuncletToken = &*FuncletPadBB->getFirstNonPHIIt();
+      Changed |= removeMalformedCatchswitch(FuncletToken);
+    }
 
-    Changed |= removeMalformedCatchswitch(FuncletToken);
 
     std::vector<std::pair<BasicBlock *, BasicBlock *>> Orig2Clone;
     ValueToValueMapTy VMap;

>From 4b4bcf642e64a1362b0500e968016a48c6ae1481 Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Tue, 22 Sep 2026 12:46:07 -0700
Subject: [PATCH 8/9] format

---
 llvm/lib/CodeGen/WinEHPrepare.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index 6185ff627c7d5..1cb2c0c64c158 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -1049,7 +1049,6 @@ bool WinEHPrepareImpl::cloneCommonBlocks(Function &F) {
       Changed |= removeMalformedCatchswitch(FuncletToken);
     }
 
-
     std::vector<std::pair<BasicBlock *, BasicBlock *>> Orig2Clone;
     ValueToValueMapTy VMap;
     for (BasicBlock *BB : BlocksInFunclet) {

>From d2a667d9756ade495b0cae507cc93f6a817d2c0f Mon Sep 17 00:00:00 2001
From: Aaron Danen <aaron.danen at gmail.com>
Date: Tue, 22 Sep 2026 12:52:43 -0700
Subject: [PATCH 9/9] format? doesn't reproduce on my machine...

---
 llvm/lib/CodeGen/WinEHPrepare.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp
index 1cb2c0c64c158..624acfe22c2d6 100644
--- a/llvm/lib/CodeGen/WinEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WinEHPrepare.cpp
@@ -983,8 +983,7 @@ bool WinEHPrepareImpl::demotePHIsOnFunclets(Function &F,
 bool WinEHPrepareImpl::removeMalformedCatchswitch(Value *FuncletToken) {
   // If a catchpad is malformed, the whole catchswitch is invalidated
   // therefore, make all of its catchpads unreachable
-  CatchPadInst *CatchPad =
-      dyn_cast<CatchPadInst>(FuncletToken);
+  CatchPadInst *CatchPad = dyn_cast<CatchPadInst>(FuncletToken);
 
   if (!CatchPad)
     return false;



More information about the llvm-commits mailing list