Skip to content

Commit dd9b0e6

Browse files
authored
Fix TryInvokeICustomQueryInterface exception handling (#117518)
* Fix TryInvokeICustomQueryInterface exception handling When the InteropLibImports::TryInvokeICustomQueryInterface is called from a native thread and a managed exception occurs in the managed ICustomQueryInterface interface implementation, the exception handling reports that exception as unhandled because it doesn't know tha the InteropLibImports::TryInvokeICustomQueryInterface catches managed exceptions and converts them to HRESULTs. This change fixes it by adding DebuggerU2MCatchHandlerFrame to the InteropLibImports::TryInvokeICustomQueryInterface. That allows EH to understand that the exception that flows to native code will be caught and so it should not report it as unhandled. Close #117393 * Add regression test * Fix unix test build * Fix unix test build again
1 parent 1f862c8 commit dd9b0e6

File tree

5 files changed

+176
-4
lines changed

5 files changed

+176
-4
lines changed

src/coreclr/vm/interoplibinterface_comwrappers.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,14 +355,18 @@ namespace InteropLibImports
355355
return TryInvokeICustomQueryInterfaceResult::FailedToInvoke;
356356
}
357357

358+
// Switch to Cooperative mode since object references
359+
// are being manipulated and the catchFrame needs that so that it can push
360+
// itself to the explicit frame stack.
361+
GCX_COOP();
362+
// Indicate to the debugger and exception handling that managed exceptions are being caught
363+
// here.
364+
DebuggerU2MCatchHandlerFrame catchFrame(true /* catchesAllExceptions */);
365+
358366
HRESULT hr;
359367
auto result = TryInvokeICustomQueryInterfaceResult::FailedToInvoke;
360368
EX_TRY_THREAD(CURRENT_THREAD)
361369
{
362-
// Switch to Cooperative mode since object references
363-
// are being manipulated.
364-
GCX_COOP();
365-
366370
struct
367371
{
368372
OBJECTREF objRef;
@@ -380,6 +384,8 @@ namespace InteropLibImports
380384
}
381385
EX_CATCH_HRESULT(hr);
382386

387+
catchFrame.Pop();
388+
383389
// Assert valid value.
384390
_ASSERTE(TryInvokeICustomQueryInterfaceResult::Min <= result
385391
&& result <= TryInvokeICustomQueryInterfaceResult::Max);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
project (nativetest117393)
2+
include_directories( ${INC_PLATFORM_DIR} )
3+
set(SOURCES nativetest117393.cpp)
4+
5+
# add the shared library
6+
add_library (nativetest117393 SHARED ${SOURCES})
7+
target_link_libraries(nativetest117393 PRIVATE ${LINK_LIBRARIES_ADDITIONAL})
8+
9+
# add the install targets
10+
install (TARGETS nativetest117393 DESTINATION bin)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#include <../../Interop/common/xplatform.h>
5+
#include <../../Interop/common/ComHelpers.h>
6+
#include "platformdefines.h"
7+
8+
#ifdef _WIN32
9+
#pragma warning(push)
10+
#pragma warning(disable:4265 4577)
11+
#include <thread>
12+
#pragma warning(pop)
13+
#else // _WIN32
14+
#include <pthread.h>
15+
#endif // _WIN32
16+
17+
// #include <platformdefines.h>
18+
19+
// Interface ID for ITest
20+
// {92BAA992-DB5A-4ADD-977B-B22838EE91FD}
21+
static const GUID IID_ITest =
22+
{ 0x92baa992, 0xdb5a, 0x4add, { 0x97, 0x7b, 0xb2, 0x28, 0x38, 0xee, 0x91, 0xfd } };
23+
24+
// Interface definition for ITest
25+
MIDL_INTERFACE("92BAA992-DB5A-4ADD-977B-B22838EE91FD")
26+
ITest : public IUnknown
27+
{
28+
virtual HRESULT STDMETHODCALLTYPE Test() = 0;
29+
};
30+
31+
void NativeTestThread(IUnknown* pUnknown)
32+
{
33+
ITest* pITest = nullptr;
34+
pUnknown->QueryInterface(IID_ITest, reinterpret_cast<void**>(&pITest));
35+
// This tests causes the QueryInterface to fail, so we don't release the pITest
36+
}
37+
38+
#ifndef _WIN32
39+
void* NativeTestThreadUnix(void* pUnknown)
40+
{
41+
NativeTestThread((IUnknown*)pUnknown);
42+
return NULL;
43+
}
44+
45+
#define AbortIfFail(st) if (st != 0) abort()
46+
47+
#endif // !_WIN32
48+
49+
extern "C" DLL_EXPORT void TestFromNativeThread(IUnknown* pUnknown)
50+
{
51+
#ifdef _WIN32
52+
std::thread t1(NativeTestThread, pUnknown);
53+
t1.join();
54+
#else // _WIN32
55+
// For Unix, we need to use pthreads to create the thread so that we can set its stack size.
56+
// We need to set the stack size due to the very small (80kB) default stack size on MUSL
57+
// based Linux distros.
58+
pthread_attr_t attr;
59+
int st = pthread_attr_init(&attr);
60+
AbortIfFail(st);
61+
62+
// set stack size to 1.5MB
63+
st = pthread_attr_setstacksize(&attr, 0x180000);
64+
AbortIfFail(st);
65+
66+
pthread_t t;
67+
st = pthread_create(&t, &attr, NativeTestThreadUnix, (void*)pUnknown);
68+
AbortIfFail(st);
69+
70+
st = pthread_join(t, NULL);
71+
AbortIfFail(st);
72+
#endif // _WIN32
73+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
8+
using Xunit;
9+
10+
namespace Test117393
11+
{
12+
class TestObject : ICustomQueryInterface
13+
{
14+
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
15+
{
16+
// Induce NullReferenceException
17+
string s = null;
18+
Console.WriteLine(s.Length);
19+
ppv = IntPtr.Zero;
20+
return CustomQueryInterfaceResult.Failed;
21+
}
22+
}
23+
24+
class TestWrappers : ComWrappers
25+
{
26+
protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
27+
{
28+
// Unknown type
29+
count = 0;
30+
return null;
31+
}
32+
33+
protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags)
34+
{
35+
return null;
36+
}
37+
38+
protected override void ReleaseObjects(IEnumerable objects)
39+
{
40+
}
41+
}
42+
43+
public class Program
44+
{
45+
[DllImport("nativetest117393")]
46+
private static extern void TestFromNativeThread(IntPtr pUnknown);
47+
48+
[Fact]
49+
public static void TestEntryPoint()
50+
{
51+
bool reportedUnhandledException = false;
52+
UnhandledExceptionEventHandler handler = (sender, e) =>
53+
{
54+
reportedUnhandledException = true;
55+
};
56+
57+
var cw = new TestWrappers();
58+
TestObject obj = new TestObject();
59+
// Create a managed object wrapper for the Demo object.
60+
// Note the returned COM interface will always be for IUnknown.
61+
IntPtr ccwUnknown = cw.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.None);
62+
AppDomain.CurrentDomain.UnhandledException += handler;
63+
TestFromNativeThread(ccwUnknown);
64+
AppDomain.CurrentDomain.UnhandledException -= handler;
65+
Marshal.Release(ccwUnknown);
66+
67+
Assert.False(reportedUnhandledException, "There should be no unhandled exception on the secondary thread");
68+
}
69+
}
70+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<!-- Needed for CMakeProjectReference -->
4+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
5+
<CLRTestPriority>1</CLRTestPriority>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<Compile Include="test117393.cs" />
9+
</ItemGroup>
10+
<ItemGroup>
11+
<CMakeProjectReference Include="CMakeLists.txt" />
12+
</ItemGroup>
13+
</Project>

0 commit comments

Comments
 (0)