llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.3k stars 12.11k forks source link

Destructors are not called during unwinding of SEH exceptions for Windows x86 binaries with `/EHa` #90946

Open momo5502 opened 6 months ago

momo5502 commented 6 months ago

Building sample below with cl.exe (both x64 and x86) using cl.exe /EHa a.cpp yields the following expected output:

Destructor called
Exception thrown

Building it with clang-cl.exe for x86 using clang-cl.exe -m32 /EHa a.cpp yields the following output:

Destructor NOT called
Exception thrown

As one can see, the destructor is not being called when unwinding. Note that compiling that sample using clang-cl for x64 works completely fine. This is only an issue with x86.

Another thing worth mentioning is that taking the code from HandleDestructorCallWithException and putting it directly within CatchNativeExceptions causes the x86 binary to crash, while x64 works fine.

The sample:

#include <cstdio>

struct Destructor {
  Destructor(bool* destructorCalled) : mDestructorCalled(destructorCalled) {}
  ~Destructor() { *mDestructorCalled = true; }
  bool* mDestructorCalled;
};

void HandleDestructorCallWithException(bool* destructorCalled)
{
  Destructor x(destructorCalled);
  *reinterpret_cast<int*>(1) = 1;
}

void CatchNativeExceptions(bool* destructorCalled, bool* exceptionThrown)
{
  try {
    HandleDestructorCallWithException(destructorCalled);
  }
  catch(...) {
    *exceptionThrown = true;
  }
}

int main()
{
  bool destructorCalled = false;
  bool exceptionThrown = false;

  CatchNativeExceptions(&destructorCalled, &exceptionThrown);

  puts(destructorCalled ? "Destructor called" : "Destructor NOT called");
  puts(exceptionThrown ? "Exception thrown" : "Exception NOT thrown");

  return destructorCalled && exceptionThrown ? 0 : 1;
}
llvmbot commented 3 months ago

@llvm/issue-subscribers-clang-driver

Author: Maurice Heumann (momo5502)

Building sample below with cl.exe (both x64 and x86) using `cl.exe /EHa a.cpp` yields the following expected output: ``` Destructor called Exception thrown ``` Building it with clang-cl.exe for x86 using `clang-cl.exe -m32 /EHa a.cpp` yields the following output: ``` Destructor NOT called Exception thrown ``` As one can see, the destructor is not being called when unwinding. Note that compiling that sample using clang-cl for x64 works completely fine. This is only an issue with x86. Another thing worth mentioning is that taking the code from `HandleDestructorCallWithException` and putting it directly within `CatchNativeExceptions` causes the x86 binary to crash, while x64 works fine. The sample: ```C++ #include <cstdio> struct Destructor { Destructor(bool* destructorCalled) : mDestructorCalled(destructorCalled) {} ~Destructor() { *mDestructorCalled = true; } bool* mDestructorCalled; }; void HandleDestructorCallWithException(bool* destructorCalled) { Destructor x(destructorCalled); *reinterpret_cast<int*>(1) = 1; } void CatchNativeExceptions(bool* destructorCalled, bool* exceptionThrown) { try { HandleDestructorCallWithException(destructorCalled); } catch(...) { *exceptionThrown = true; } } int main() { bool destructorCalled = false; bool exceptionThrown = false; CatchNativeExceptions(&destructorCalled, &exceptionThrown); puts(destructorCalled ? "Destructor called" : "Destructor NOT called"); puts(exceptionThrown ? "Exception thrown" : "Exception NOT thrown"); return destructorCalled && exceptionThrown ? 0 : 1; } ```
llvmbot commented 3 months ago

@llvm/issue-subscribers-c-1

Author: Maurice Heumann (momo5502)

Building sample below with cl.exe (both x64 and x86) using `cl.exe /EHa a.cpp` yields the following expected output: ``` Destructor called Exception thrown ``` Building it with clang-cl.exe for x86 using `clang-cl.exe -m32 /EHa a.cpp` yields the following output: ``` Destructor NOT called Exception thrown ``` As one can see, the destructor is not being called when unwinding. Note that compiling that sample using clang-cl for x64 works completely fine. This is only an issue with x86. Another thing worth mentioning is that taking the code from `HandleDestructorCallWithException` and putting it directly within `CatchNativeExceptions` causes the x86 binary to crash, while x64 works fine. The sample: ```C++ #include <cstdio> struct Destructor { Destructor(bool* destructorCalled) : mDestructorCalled(destructorCalled) {} ~Destructor() { *mDestructorCalled = true; } bool* mDestructorCalled; }; void HandleDestructorCallWithException(bool* destructorCalled) { Destructor x(destructorCalled); *reinterpret_cast<int*>(1) = 1; } void CatchNativeExceptions(bool* destructorCalled, bool* exceptionThrown) { try { HandleDestructorCallWithException(destructorCalled); } catch(...) { *exceptionThrown = true; } } int main() { bool destructorCalled = false; bool exceptionThrown = false; CatchNativeExceptions(&destructorCalled, &exceptionThrown); puts(destructorCalled ? "Destructor called" : "Destructor NOT called"); puts(exceptionThrown ? "Exception thrown" : "Exception NOT thrown"); return destructorCalled && exceptionThrown ? 0 : 1; } ```
momo5502 commented 1 week ago

Seems like when compiling with MS cl, it enters a try block:

image

With clang-cl, it doesn't:

image

even though it splits the block at that position.

momo5502 commented 1 week ago

Seems like on IR level, the scope is being generated:

image

maybe it's just missing from the func info entry

momo5502 commented 1 week ago

setting the try state to 0 is necessary:

image

not sure why it's not done with clang

momo5502 commented 1 week ago

clang correctly emits it for the parent function (just like cl):

image