Skip to content

Conversation

@GkvJwa
Copy link
Contributor

@GkvJwa GkvJwa commented Dec 15, 2025

Consider the following code:

class CheckError {
 public:
  static CheckError Check();

  ~CheckError();
};

void Foo(const int* p) {
  int d = 0;
  __try {
    d = *p;
  } __except (EXCEPTION_EXECUTE_HANDLER) {
    ::CheckError::Check();
  }
}

The following error will occur in mscv.

error C2712: Cannot use __try in functions that require object unwinding

However, using LLVM /EHa will cause it to crash.

llvm::SmallVectorTemplateCommon<llvm::SEHUnwindMapEntry,void>::operator[](unsigned __int64 idx) Line 295	C++
llvm::calculateSEHStateForAsynchEH(const llvm::BasicBlock * BB, int State, llvm::WinEHFuncInfo & EHInfo) Line 346	C++
llvm::calculateSEHStateNumbers(const llvm::Function * Fn, llvm::WinEHFuncInfo & FuncInfo) Line 612	C++
llvm::FunctionLoweringInfo::set(const llvm::Function & fn, llvm::MachineFunction & mf, llvm::SelectionDAG * DAG) Line 114	C++
llvm::SelectionDAGISel::runOnMachineFunction(llvm::MachineFunction & mf) Line 588	C++

This patch is compatible with the crash(Unexpected state == -1)

@GkvJwa
Copy link
Contributor Author

GkvJwa commented Dec 15, 2025

Could someone please take a look at this patch when you have some free time? I understand it only needs to fix the crash issue.

@efriedma-quic
Copy link
Collaborator

CC @MuellerMP

@GkvJwa GkvJwa requested a review from tentzen December 16, 2025 06:30
@MuellerMP
Copy link
Contributor

MuellerMP commented Dec 16, 2025

My general take on this issue: since msvc rejects this it would not be unreasonable to reject this in clang aswell for compatibility reasons and to avoid overall complexity in the state handling.

Regarding this patch:
The C++ state numbering condition aswell as the Filter state numbering fix are not required for this issue and are not covered by tests.
As far as I can tell the only fix covered by the test is the SEH state numbering State > 0 condition for the seh_try_end invoke right?

Now what actually causes the issue:
We choose the calculateSEHStateNumbers function instead of the calculateWinCXXEHStateNumbers Function in FunctionLoweringInfo due to this function having the __C_specific_handler personality.
The calculateSEHStateNumbers function currently does NOT know about C++ EH constructs like the emitted seh_scope_begin. We simply skip over it in the state numbering which causes the issue.
We could "fix" this by either supporting seh_scope_begin in calculateSEHStateNumbers or choosing the C++ unwinder in the frontend (latter one would be preferable and more sound IMO).

=>But like I said: I would rather choose to reject this in the frontend.

@GkvJwa
Copy link
Contributor Author

GkvJwa commented Dec 16, 2025

My general take on this issue: since msvc rejects this it would not be unreasonable to reject this in clang aswell for compatibility reasons and to avoid overall complexity in the state handling.

Regarding this patch: The C++ state numbering condition aswell as the Filter state numbering fix are not required for this issue and are not covered by tests. As far as I can tell the only fix covered by the test is the SEH state numbering State > 0 condition for the seh_try_end invoke right?

Now what actually causes the issue: We choose the calculateSEHStateNumbers function instead of the calculateWinCXXEHStateNumbers Function in FunctionLoweringInfo due to this function having the __C_specific_handler personality. The calculateSEHStateNumbers function currently does NOT know about C++ EH constructs like the emitted seh_scope_begin. We simply skip over it in the state numbering which causes the issue. We could "fix" this by either supporting seh_scope_begin in calculateSEHStateNumbers or choosing the C++ unwinder in the frontend (latter one would be preferable and more sound IMO).

=>But like I said: I would rather choose to reject this in the frontend.

I checked the compiled code, and it didn't reject anything.
The front-end can generate ll files; the problem only arises when generating obj files.

You can run this case and then check it.

@MuellerMP
Copy link
Contributor

@GkvJwa My point is that the ll could already be considered ill formed - as explained in Now what actually causes the issue.
But I agree that not only the clang frontend but also LLC should reject this IMO.
Besides that your test does actually include the clang frontend here.

@GkvJwa
Copy link
Contributor Author

GkvJwa commented Dec 16, 2025

@GkvJwa My point is that the ll could already be considered ill formed - as explained in Now what actually causes the issue. But I agree that not only the clang frontend but also LLC should reject this IMO. Besides that your test does actually include the clang frontend here.

Okay, so if we find a class object in llvm.eh.exceptioncode, is it feasible to directly refuse compilation?

// llvm.eh.exceptioncode has callinst

__except:                                         ; preds = %catch.dispatch
  %3 = catchpad within %2 [ptr null]
  catchret from %3 to label %__except2

__except2:                                        ; preds = %__except
  %4 = call i32 @llvm.eh.exceptioncode(token %3)
  store i32 %4, ptr %__exception_code, align 4
  call void @"?bb@aa@@SA?AV1@XZ"(ptr dead_on_unwind writable sret(%class.aa) align 1 %agg.tmp.ensured)
  invoke void @llvm.seh.scope.begin()
          to label %invoke.cont3 unwind label %ehcleanup

@MuellerMP
Copy link
Contributor

MuellerMP commented Dec 16, 2025

@GkvJwa
The calculateSEHStateNumbers function currently does NOT know about C++ EH constructs like the emitted seh_scope_begin. We simply skip over it in the state numbering which causes the issue.
=> Thus the correct refusal pattern for LLC would be a function with a SEH (non CXX) personality like __C_specific_handler that contains seh_scope_begin. LLVM does not currently handle this circumstance correctly. And IMO it should not try to do so.

Clang should probably catch this in the frontend and generate a similar error message as the C2712 one IMO.

@GkvJwa
Copy link
Contributor Author

GkvJwa commented Dec 16, 2025

=> Thus the correct refusal pattern for LLC would be a function with a SEH (non CXX) personality like __C_specific_handler that

It's okay, we can handle this simply first, and then improve SEH support later (I also encountered this problem during compilation. After reproducing it and creating a demo, I tried it with MSVC and found that it wasn't supported.)

@MuellerMP
Copy link
Contributor

I would also like to note that using try{}catch(...){} instead of __try{}__except(1){} is a basically semantically equivalent workaround for this issue.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Dec 16, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 16, 2025

@llvm/pr-subscribers-clang

Author: None (GkvJwa)

Changes

Consider the following code:


#include &lt;windows.h&gt;

class CheckError {
 public:
  static CheckError Check();

  ~CheckError();
};

void Foo(const int* p) {
  int d = 0;
  __try {
    d = *p;
  } __except (EXCEPTION_EXECUTE_HANDLER) {
    ::CheckError::Check();
  }
}

The following error will occur in mscv.

error C2712: Cannot use __try in functions that require object unwinding

However, using LLVM /EHa will cause it to crash.

llvm::SmallVectorTemplateCommon&lt;llvm::SEHUnwindMapEntry,void&gt;::operator[](unsigned __int64 idx) Line 295	C++
llvm::calculateSEHStateForAsynchEH(const llvm::BasicBlock * BB, int State, llvm::WinEHFuncInfo &amp; EHInfo) Line 346	C++
llvm::calculateSEHStateNumbers(const llvm::Function * Fn, llvm::WinEHFuncInfo &amp; FuncInfo) Line 612	C++
llvm::FunctionLoweringInfo::set(const llvm::Function &amp; fn, llvm::MachineFunction &amp; mf, llvm::SelectionDAG * DAG) Line 114	C++
llvm::SelectionDAGISel::runOnMachineFunction(llvm::MachineFunction &amp; mf) Line 588	C++

This patch is compatible with the crash.


Full diff: https://github.com/llvm/llvm-project/pull/172287.diff

1 Files Affected:

  • (modified) clang/lib/Sema/SemaStmt.cpp (+43)
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 1b1643250d05e..c64029f269c2b 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -4553,6 +4553,45 @@ StmtResult Sema::ActOnSEHTryBlock(bool IsCXXTry, SourceLocation TryLoc,
   return SEHTryStmt::Create(Context, IsCXXTry, TryLoc, TryBlock, Handler);
 }
 
+static bool containsNonTrivialObject(Sema &S, const Stmt *Node) {
+  (void)S;
+  if (!Node)
+    return false;
+
+  if (const DeclStmt *DS = dyn_cast<DeclStmt>(Node)) {
+    for (const Decl *D : DS->decls()) {
+      if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
+        QualType T = VD->getType();
+        if (const RecordType *RT = T->getAs<RecordType>()) {
+          if (const CXXRecordDecl *RD = RT->getAsCXXRecordDecl()) {
+            if (RD->hasDefinition() && !RD->hasTrivialDestructor())
+              return true;
+          }
+        }
+      }
+    }
+  }
+
+  if (const Expr *E = dyn_cast<Expr>(Node)) {
+    QualType T = E->getType();
+    if (T->isRecordType() && E->getValueKind() != VK_LValue) {
+      if (const RecordType *RT = T->getAs<RecordType>()) {
+        if (const CXXRecordDecl *RD = RT->getAsCXXRecordDecl()) {
+          if (RD->hasDefinition() && !RD->hasTrivialDestructor())
+            return true;
+        }
+      }
+    }
+  }
+
+  // children.
+  for (const Stmt *Child : Node->children())
+    if (containsNonTrivialObject(S, Child))
+      return true;
+
+  return false;
+}
+
 StmtResult Sema::ActOnSEHExceptBlock(SourceLocation Loc, Expr *FilterExpr,
                                      Stmt *Block) {
   assert(FilterExpr && Block);
@@ -4562,6 +4601,10 @@ StmtResult Sema::ActOnSEHExceptBlock(SourceLocation Loc, Expr *FilterExpr,
         Diag(FilterExpr->getExprLoc(), diag::err_filter_expression_integral)
         << FTy);
   }
+  if (containsNonTrivialObject(*this, Block)) {
+    Diag(Loc, diag::err_seh_try_outside_functions);
+    return StmtError();
+  }
   return SEHExceptStmt::Create(Context, Loc, FilterExpr, Block);
 }
 

@GkvJwa
Copy link
Contributor Author

GkvJwa commented Dec 16, 2025

I would also like to note that using try{}catch(...){} instead of __try{}__except(1){} is a basically semantically equivalent workaround for this issue.

On Windows, the latter is the SEH. The 1 can be replaced with actual exceptions(GetExceptionCode() == ?), which the former cannot do. That's why we need to specifically use SEH.

Also, if you have time, review it again. If there are no issues, I will add test.

Of course, there shouldn't be any class objects in the try block. If this approach is acceptable, I will proceed with the impl.

@github-actions
Copy link

🐧 Linux x64 Test Results

  • 85151 tests passed
  • 1176 tests skipped
  • 1 test failed

Failed Tests

(click on a test name to see its output)

Clang

Clang.SemaCXX/__try.cpp
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/22/include -nostdsysteminc -triple x86_64-windows -fsyntax-only -verify -fborland-extensions -fcxx-exceptions /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/SemaCXX/__try.cpp
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/22/include -nostdsysteminc -triple x86_64-windows -fsyntax-only -verify -fborland-extensions -fcxx-exceptions /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/SemaCXX/__try.cpp
# .---command stderr------------
# | error: 'expected-error' diagnostics seen but not expected: 
# |   File /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/SemaCXX/__try.cpp Line 42: cannot use SEH '__try' in blocks, captured regions, or Obj-C method decls
# | 1 error generated.
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@github-actions
Copy link

🪟 Windows x64 Test Results

  • 51560 tests passed
  • 877 tests skipped
  • 1 test failed

Failed Tests

(click on a test name to see its output)

Clang

Clang.SemaCXX/__try.cpp
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\clang.exe -cc1 -internal-isystem C:\_work\llvm-project\llvm-project\build\lib\clang\22\include -nostdsysteminc -triple x86_64-windows -fsyntax-only -verify -fborland-extensions -fcxx-exceptions C:\_work\llvm-project\llvm-project\clang\test\SemaCXX\__try.cpp
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\clang.exe' -cc1 -internal-isystem 'C:\_work\llvm-project\llvm-project\build\lib\clang\22\include' -nostdsysteminc -triple x86_64-windows -fsyntax-only -verify -fborland-extensions -fcxx-exceptions 'C:\_work\llvm-project\llvm-project\clang\test\SemaCXX\__try.cpp'
# .---command stderr------------
# | error: 'expected-error' diagnostics seen but not expected: 
# |   File C:\_work\llvm-project\llvm-project\clang\test\SemaCXX\__try.cpp Line 42: cannot use SEH '__try' in blocks, captured regions, or Obj-C method decls
# | 1 error generated.
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@MuellerMP
Copy link
Contributor

MuellerMP commented Dec 17, 2025

@GkvJwa Regarding the failing test: I have to apologize. It seems there is some Borland C++ compatibility switch that I did not know about that allows these constructs to be parsed at least.
Regarding compilation this test does seem to fail very quickly though: https://godbolt.org/z/6eGfrr4Kn .
I assume the Borland C++ switch does not have any maintainer and just has some parser tests.

I would suggest emitting the diagnostic only for non Borland C++.
Regarding the Borland behavior: it might make even sense to deprecate Borland C++ support since its in such a broken state? But that really is not for me to decide.

@efriedma-quic Can you confirm that we should move ahead with this diagnostic for non Borland C++?
An alternative would be, like I said earlier, to support this SEH & C++ mixing in the SEH state numbering algo. In the latter case it might make sense to investigate what the Borland C++ compiler emits for the test code (especially which personality routine is chosen - since that is not an obvious decision IMO).

For reference we have a similar borland check here:
https://github.com/llvm/llvm-project/blob/release/21.x/clang/lib/Sema/SemaStmt.cpp#L4330

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category llvm:codegen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants