Skip to content

Commit 5c6534d

Browse files
committed
Fixed undefined behavior with delegate reinterpret cast
1 parent 89a9aa4 commit 5c6534d

File tree

2 files changed

+43
-11
lines changed

2 files changed

+43
-11
lines changed

CommunityToolkit.WinUI/Extensions/Interop/DispatcherQueueProxyHandler1.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ internal unsafe struct DispatcherQueueProxyHandler1 : IDispatcherQueueHandler
6262
/// </summary>
6363
private GCHandle stateHandle;
6464

65+
/// <summary>
66+
/// The generic stub to invoke the current callback with the right generic context.
67+
/// </summary>
68+
private delegate*<DispatcherQueueProxyHandler1*, int> stub;
69+
6570
/// <summary>
6671
/// The current reference count for the object (from <c>IUnknown</c>).
6772
/// </summary>
@@ -70,16 +75,19 @@ internal unsafe struct DispatcherQueueProxyHandler1 : IDispatcherQueueHandler
7075
/// <summary>
7176
/// Creates a new <see cref="DispatcherQueueProxyHandler1"/> instance for the input callback and state.
7277
/// </summary>
78+
/// <typeparam name="T">The type of state currently being used.</typeparam>
7379
/// <param name="handler">The input <see cref="DispatcherQueueHandler{TState}"/> callback to enqueue.</param>
7480
/// <param name="state">The input state to capture and pass to the callback.</param>
7581
/// <returns>A pointer to the newly initialized <see cref="DispatcherQueueProxyHandler1"/> instance.</returns>
76-
public static DispatcherQueueProxyHandler1* Create(object handler, object state)
82+
public static DispatcherQueueProxyHandler1* Create<T>(DispatcherQueueHandler<T> handler, T state)
83+
where T : class
7784
{
7885
DispatcherQueueProxyHandler1* @this = (DispatcherQueueProxyHandler1*)Marshal.AllocHGlobal(sizeof(DispatcherQueueProxyHandler1));
7986

8087
@this->lpVtbl = Vtbl;
8188
@this->callbackHandle = GCHandle.Alloc(handler);
8289
@this->stateHandle = GCHandle.Alloc(state);
90+
@this->stub = &Impl.Invoke<T>;
8391
@this->referenceCount = 1;
8492

8593
return @this;
@@ -174,19 +182,22 @@ public static uint Release(DispatcherQueueProxyHandler1* @this)
174182
/// </summary>
175183
[UnmanagedCallersOnly]
176184
public static int Invoke(DispatcherQueueProxyHandler1* @this)
185+
{
186+
return @this->stub(@this);
187+
}
188+
189+
/// <summary>
190+
/// Implements <c>IDispatcherQueueHandler.Invoke()</c> from within a generic context.
191+
/// </summary>
192+
public static int Invoke<T>(DispatcherQueueProxyHandler1* @this)
193+
where T : class
177194
{
178195
object callback = @this->callbackHandle.Target!;
179196
object state = @this->stateHandle.Target!;
180197

181198
try
182199
{
183-
// We do an unsafe cast here to treat the captured delegate as if the contravariant
184-
// input type was actually declared as covariant. This is valid because the type
185-
// parameter is constrained to be a reference type, and due to how the proxy handler
186-
// is constructed we know that the captured state will always match the actual type
187-
// of the captured handler at this point. This lets this whole method work without the
188-
// need to make the proxy type itself generic, so without knowing the actual type argument.
189-
Unsafe.As<DispatcherQueueHandler<object>>(callback)(state);
200+
Unsafe.As<DispatcherQueueHandler<T>>(callback)(Unsafe.As<T>(state));
190201
}
191202
catch (Exception e)
192203
{

CommunityToolkit.WinUI/Extensions/Interop/DispatcherQueueProxyHandler2.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ internal unsafe struct DispatcherQueueProxyHandler2 : IDispatcherQueueHandler
6767
/// </summary>
6868
private GCHandle state2Handle;
6969

70+
/// <summary>
71+
/// The generic stub to invoke the current callback with the right generic context.
72+
/// </summary>
73+
private delegate*<DispatcherQueueProxyHandler2*, int> stub;
74+
7075
/// <summary>
7176
/// The current reference count for the object (from <c>IUnknown</c>).
7277
/// </summary>
@@ -75,18 +80,23 @@ internal unsafe struct DispatcherQueueProxyHandler2 : IDispatcherQueueHandler
7580
/// <summary>
7681
/// Creates a new <see cref="DispatcherQueueProxyHandler2"/> instance for the input callback and state.
7782
/// </summary>
83+
/// <typeparam name="T1">The type of the first state to capture.</typeparam>
84+
/// <typeparam name="T2">The type of the second state to capture.</typeparam>
7885
/// <param name="handler">The input <see cref="DispatcherQueueHandler{T1,T2}"/> callback to enqueue.</param>
7986
/// <param name="state1">The first input state to capture and pass to the callback.</param>
8087
/// <param name="state2">The second input state to capture and pass to the callback.</param>
8188
/// <returns>A pointer to the newly initialized <see cref="DispatcherQueueProxyHandler2"/> instance.</returns>
82-
public static DispatcherQueueProxyHandler2* Create(object handler, object state1, object state2)
89+
public static DispatcherQueueProxyHandler2* Create<T1, T2>(DispatcherQueueHandler<T1, T2> handler, T1 state1, T2 state2)
90+
where T1 : class
91+
where T2 : class
8392
{
8493
DispatcherQueueProxyHandler2* @this = (DispatcherQueueProxyHandler2*)Marshal.AllocHGlobal(sizeof(DispatcherQueueProxyHandler2));
8594

8695
@this->lpVtbl = Vtbl;
8796
@this->callbackHandle = GCHandle.Alloc(handler);
8897
@this->state1Handle = GCHandle.Alloc(state1);
8998
@this->state2Handle = GCHandle.Alloc(state2);
99+
@this->stub = &Impl.Invoke<T1, T2>;
90100
@this->referenceCount = 1;
91101

92102
return @this;
@@ -183,15 +193,26 @@ public static uint Release(DispatcherQueueProxyHandler2* @this)
183193
/// </summary>
184194
[UnmanagedCallersOnly]
185195
public static int Invoke(DispatcherQueueProxyHandler2* @this)
196+
{
197+
return @this->stub(@this);
198+
}
199+
200+
/// <summary>
201+
/// Implements <c>IDispatcherQueueHandler.Invoke()</c> from within a generic context.
202+
/// </summary>
203+
public static int Invoke<T1, T2>(DispatcherQueueProxyHandler2* @this)
204+
where T1 : class
205+
where T2 : class
186206
{
187207
object callback = @this->callbackHandle.Target!;
188208
object state1 = @this->state1Handle.Target!;
189209
object state2 = @this->state2Handle.Target!;
190210

191211
try
192212
{
193-
// Same optimization as in DispatcherQueueProxyHandler1
194-
Unsafe.As<DispatcherQueueHandler<object, object>>(callback)(state1, state2);
213+
Unsafe.As<DispatcherQueueHandler<object, object>>(callback)(
214+
Unsafe.As<T1>(state1),
215+
Unsafe.As<T2>(state2));
195216
}
196217
catch (Exception e)
197218
{

0 commit comments

Comments
 (0)