Skip to content

Fix caught foreign native exception handling take 2 #116693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

janvorli
Copy link
Member

The previous fix was reverted, as it didn't take into account hardware
exceptions stemming from managed code. This has broken debugger tests.

When a native exception occurs on a foreign thread, it is propagated
through managed frames and then flows into the foreign native frames
and is handled there, the runtime still reports the exception as unhandled
via AppDomain.CurrentDomain.UnhandledException event and also prints
out the unhandled exception message. Even though the process then continues
running just fine.

This change fixes the behavior so that only managed exceptions
thrown by the .NET runtime are reported as unhandled in such case.
Foreign exceptions, like e.g. C++ exceptions, are not reported as
unhandled and they are just propagated into the foreign native
frames. If that exception is not handled by the native frames,
then C++ reports them as unhandled.

I have also added a test that fails without this fix.

Close #115654

janvorli added 2 commits June 16, 2025 15:36
The previous fix was reverted, as it didn't take into account hardware
exceptions stemming from managed code. This has broken debugger tests.

When a native exception occurs on a foreign thread, it is propagated
through managed frames and then flows into the foreign native frames
and is handled there, the runtime still reports the exception as unhandled
via AppDomain.CurrentDomain.UnhandledException event and also prints
out the unhandled exception message. Even though the process then continues
running just fine.

This change fixes the behavior so that only managed exceptions
thrown by the .NET runtime are reported as unhandled in such case.
Foreign exceptions, like e.g. C++ exceptions, are not reported as
unhandled and they are just propagated into the foreign native
frames. If that exception is not handled by the native frames,
then C++ reports them as unhandled.

I have also added a test that fails without this fix.

Close dotnet#115654
Also add hardware unhandled exception test
@janvorli janvorli added this to the 10.0.0 milestone Jun 16, 2025
@janvorli janvorli requested a review from jkotas June 16, 2025 14:52
@janvorli janvorli self-assigned this Jun 16, 2025
@Copilot Copilot AI review requested due to automatic review settings June 16, 2025 14:52
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refines unhandled-exception reporting so that only managed exceptions thrown by the .NET runtime trigger the AppDomain unhandled-event, while foreign/native exceptions are left to native handlers. It also adds test cases to verify this behavior on both the main and secondary threads, and adjusts core exception-logging logic accordingly.

  • Update expected exit codes in unhandledTester.cs to account for hardware (NullReference) exceptions.
  • Add C# branches for mainhardware, secondaryhardware, and secondaryunhandled in unhandled.cs and corresponding native/thread helpers in ExceptionInterop.
  • Modify exceptionhandling.cpp to skip logging for exceptions not originating from managed code.

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/tests/baseservices/exceptions/unhandled/unhandledTester.cs Adjusted expectedExitCode logic and added hardware test invocations
src/tests/baseservices/exceptions/unhandled/unhandled.cs Added branches to throw NullReferenceException on main and secondary threads, plus secondaryunhandled
src/tests/baseservices/exceptions/exceptioninterop/ExceptionInteropNative.cpp Introduced InvokeCallbackOnNewThread and native catch/rethrow helper
src/tests/baseservices/exceptions/exceptioninterop/ExceptionInterop.cs Imported InvokeCallbackOnNewThread and added PropagateAndCatchCppException test
src/coreclr/vm/exceptionhandling.cpp Added isNotHandledByRuntime and gated logging to managed-code exceptions only
Comments suppressed due to low confidence (3)

src/tests/baseservices/exceptions/unhandled/unhandledTester.cs:141

  • The new "secondaryunhandled" branch in unhandled.cs isn’t invoked by TestEntryPoint, leaving that code path untested. Add a RunExternalProcess("secondaryunhandled", "unhandled.dll"); call immediately after the secondaryhardware invocation.
RunExternalProcess("secondaryhardware", "unhandled.dll");

src/tests/baseservices/exceptions/exceptioninterop/ExceptionInterop.cs:198

  • [nitpick] For consistency with C# naming conventions, rename the parameter from callBack to callback.
public static extern void InvokeCallbackOnNewThread(delegate*unmanaged<void> callBack);

src/coreclr/vm/exceptionhandling.cpp:4018

  • [nitpick] The isNotHandledByRuntime initialization mixes preprocessor directives and logical operators, which can be hard to follow. Consider restructuring the expression or aligning the #ifdef block so the OR-conditions remain visually coherent.
bool isNotHandledByRuntime =

Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@@ -4015,13 +4015,16 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
// Check if there are any further managed frames on the stack or a catch for all exceptions in native code (marked by
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a bit confused by invalidRevPInvoke local variable name. I am not sure what's invalid about it.

Would isReversePInvoke be a better name for it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a historical name that was created when the Interop::ManagedToNativeExceptionCallback was introduced. It is confusing to me too. I am planning to restructure this code a bit as part of a fix to the #116676, so I will address it as part of that.

@janvorli
Copy link
Member Author

cc: @tommcdon - I have verified that this change fixes the failure in the debugger tests.

@janvorli
Copy link
Member Author

/ba-g the wasm build failure is unrelated to this change, it happens in onstackreplacement.cpp, but the log doesn't show any specific failure.

@janvorli janvorli merged commit 08758d8 into dotnet:main Jun 16, 2025
100 of 102 checks passed
@janvorli janvorli deleted the fix-caught-foreign-native-exception-handling-2 branch June 16, 2025 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UnhandledException fires when unmanaged exception thrown from managed catch block C++/CLI .NET 9
2 participants