Implement instantiating and unboxing through portable stublinker codeā€¦ (#106)
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / src / System / MulticastDelegate.cs
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 // See the LICENSE file in the project root for more information.
4
5 using System.Diagnostics;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Reflection;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.InteropServices;
10 using System.Runtime.Serialization;
11
12 using Internal.Runtime.CompilerServices;
13
14 namespace System
15 {
16     [ClassInterface(ClassInterfaceType.None)]
17     [ComVisible(true)]
18     public abstract class MulticastDelegate : Delegate
19     {
20         // This is set under 2 circumstances
21         // 1. Multicast delegate
22         // 2. Wrapper delegate
23         private object? _invocationList; // Initialized by VM as needed
24         private IntPtr _invocationCount;
25
26         // This constructor is called from the class generated by the
27         //    compiler generated code (This must match the constructor
28         //    in Delegate
29         protected MulticastDelegate(object target, string method) : base(target, method)
30         {
31         }
32
33         // This constructor is called from a class to generate a 
34         // delegate based upon a static method name and the Type object
35         // for the class defining the method.
36         protected MulticastDelegate(Type target, string method) : base(target, method)
37         {
38         }
39
40         internal bool IsUnmanagedFunctionPtr()
41         {
42             return (_invocationCount == (IntPtr)(-1));
43         }
44
45         internal bool InvocationListLogicallyNull()
46         {
47             return (_invocationList == null) || (_invocationList is LoaderAllocator) || (_invocationList is System.Reflection.Emit.DynamicResolver);
48         }
49
50         public override void GetObjectData(SerializationInfo info, StreamingContext context)
51         {
52             throw new SerializationException(SR.Serialization_DelegatesNotSupported);
53         }
54
55         // equals returns true IIF the delegate is not null and has the
56         //    same target, method and invocation list as this object
57         public override sealed bool Equals(object? obj)
58         {
59             if (obj == null)
60                 return false;
61             if (object.ReferenceEquals(this, obj))
62                 return true;
63             if (!InternalEqualTypes(this, obj))
64                 return false;
65
66             // Since this is a MulticastDelegate and we know
67             // the types are the same, obj should also be a
68             // MulticastDelegate
69             Debug.Assert(obj is MulticastDelegate, "Shouldn't have failed here since we already checked the types are the same!");
70             var d = Unsafe.As<MulticastDelegate>(obj);
71
72             if (_invocationCount != (IntPtr)0)
73             {
74                 // there are 4 kind of delegate kinds that fall into this bucket
75                 // 1- Multicast (_invocationList is Object[])
76                 // 2- Wrapper (_invocationList is Delegate)
77                 // 3- Unmanaged FntPtr (_invocationList == null)
78                 // 4- Open virtual (_invocationCount == MethodDesc of target, _invocationList == null, LoaderAllocator, or DynamicResolver)
79
80                 if (InvocationListLogicallyNull())
81                 {
82                     if (IsUnmanagedFunctionPtr())
83                     {
84                         if (!d.IsUnmanagedFunctionPtr())
85                             return false;
86
87                         return CompareUnmanagedFunctionPtrs(this, d);
88                     }
89
90                     // now we know 'this' is not a special one, so we can work out what the other is
91                     if ((d._invocationList as Delegate) != null)
92                         // this is a wrapper delegate so we need to unwrap and check the inner one
93                         return Equals(d._invocationList);
94
95                     return base.Equals(obj);
96                 }
97                 else
98                 {
99                     if (_invocationList is Delegate invocationListDelegate)
100                     {
101                         // this is a wrapper delegate so we need to unwrap and check the inner one
102                         return invocationListDelegate.Equals(obj);
103                     }
104                     else
105                     {
106                         Debug.Assert((_invocationList as object[]) != null, "empty invocation list on multicast delegate");
107                         return InvocationListEquals(d);
108                     }
109                 }
110             }
111             else
112             {
113                 // among the several kind of delegates falling into this bucket one has got a non
114                 // empty _invocationList (open static with special sig)
115                 // to be equals we need to check that _invocationList matches (both null is fine)
116                 // and call the base.Equals()
117                 if (!InvocationListLogicallyNull())
118                 {
119                     if (!_invocationList!.Equals(d._invocationList))
120                         return false;
121                     return base.Equals(d);
122                 }
123
124                 // now we know 'this' is not a special one, so we can work out what the other is
125                 if ((d._invocationList as Delegate) != null)
126                     // this is a wrapper delegate so we need to unwrap and check the inner one
127                     return Equals(d._invocationList);
128
129                 // now we can call on the base
130                 return base.Equals(d);
131             }
132         }
133
134         // Recursive function which will check for equality of the invocation list.
135         private bool InvocationListEquals(MulticastDelegate d)
136         {
137             Debug.Assert(d != null);
138             Debug.Assert(_invocationList is object[]);
139             object[] invocationList = (object[])_invocationList;
140
141             if (d._invocationCount != _invocationCount)
142                 return false;
143
144             int invocationCount = (int)_invocationCount;
145             for (int i = 0; i < invocationCount; i++)
146             {
147                 Debug.Assert(invocationList[i] is Delegate);
148                 Delegate dd = (Delegate)invocationList[i]; // If invocationList is an object[], it always contains Delegate (or MulticastDelegate) objects
149
150                 object[] dInvocationList = (d._invocationList as object[])!;
151                 if (!dd.Equals(dInvocationList[i]))
152                     return false;
153             }
154             return true;
155         }
156
157         private bool TrySetSlot(object?[] a, int index, object o)
158         {
159             if (a[index] == null && System.Threading.Interlocked.CompareExchange<object?>(ref a[index], o, null) == null)
160                 return true;
161
162             // The slot may be already set because we have added and removed the same method before.
163             // Optimize this case, because it's cheaper than copying the array.
164             if (a[index] != null)
165             {
166                 MulticastDelegate d = (MulticastDelegate)o;
167                 MulticastDelegate dd = (MulticastDelegate)a[index]!; // TODO-NULLABLE: Indexer nullability tracked (https://github.com/dotnet/roslyn/issues/34644)
168
169                 if (dd._methodPtr == d._methodPtr &&
170                     dd._target == d._target &&
171                     dd._methodPtrAux == d._methodPtrAux)
172                 {
173                     return true;
174                 }
175             }
176             return false;
177         }
178
179         private MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready)
180         {
181             // First, allocate a new multicast delegate just like this one, i.e. same type as the this object
182             MulticastDelegate result = (MulticastDelegate)InternalAllocLike(this);
183
184             // Performance optimization - if this already points to a true multicast delegate,
185             // copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them
186             if (thisIsMultiCastAlready)
187             {
188                 result._methodPtr = this._methodPtr;
189                 result._methodPtrAux = this._methodPtrAux;
190             }
191             else
192             {
193                 result._methodPtr = GetMulticastInvoke();
194                 result._methodPtrAux = GetInvokeMethod();
195             }
196             result._target = result;
197             result._invocationList = invocationList;
198             result._invocationCount = (IntPtr)invocationCount;
199
200             return result;
201         }
202
203         internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount)
204         {
205             return NewMulticastDelegate(invocationList, invocationCount, false);
206         }
207
208         internal void StoreDynamicMethod(MethodInfo dynamicMethod)
209         {
210             if (_invocationCount != (IntPtr)0)
211             {
212                 Debug.Assert(!IsUnmanagedFunctionPtr(), "dynamic method and unmanaged fntptr delegate combined");
213                 // must be a secure/wrapper one, unwrap and save
214                 MulticastDelegate d = ((MulticastDelegate?)_invocationList)!;
215                 d._methodBase = dynamicMethod;
216             }
217             else
218                 _methodBase = dynamicMethod;
219         }
220
221         // This method will combine this delegate with the passed delegate
222         //    to form a new delegate.
223         protected override sealed Delegate CombineImpl(Delegate? follow)
224         {
225             if ((object?)follow == null) // cast to object for a more efficient test
226                 return this;
227
228             // Verify that the types are the same...
229             if (!InternalEqualTypes(this, follow))
230                 throw new ArgumentException(SR.Arg_DlgtTypeMis);
231
232             MulticastDelegate dFollow = (MulticastDelegate)follow;
233             object[]? resultList;
234             int followCount = 1;
235             object[]? followList = dFollow._invocationList as object[];
236             if (followList != null)
237                 followCount = (int)dFollow._invocationCount;
238
239             int resultCount;
240             if (!(_invocationList is object[] invocationList))
241             {
242                 resultCount = 1 + followCount;
243                 resultList = new object[resultCount];
244                 resultList[0] = this;
245                 if (followList == null)
246                 {
247                     resultList[1] = dFollow;
248                 }
249                 else
250                 {
251                     for (int i = 0; i < followCount; i++)
252                         resultList[1 + i] = followList[i];
253                 }
254                 return NewMulticastDelegate(resultList, resultCount);
255             }
256             else
257             {
258                 int invocationCount = (int)_invocationCount;
259                 resultCount = invocationCount + followCount;
260                 resultList = null;
261                 if (resultCount <= invocationList.Length)
262                 {
263                     resultList = invocationList;
264                     if (followList == null)
265                     {
266                         if (!TrySetSlot(resultList, invocationCount, dFollow))
267                             resultList = null;
268                     }
269                     else
270                     {
271                         for (int i = 0; i < followCount; i++)
272                         {
273                             if (!TrySetSlot(resultList, invocationCount + i, followList[i]))
274                             {
275                                 resultList = null;
276                                 break;
277                             }
278                         }
279                     }
280                 }
281
282                 if (resultList == null)
283                 {
284                     int allocCount = invocationList.Length;
285                     while (allocCount < resultCount)
286                         allocCount *= 2;
287
288                     resultList = new object[allocCount];
289
290                     for (int i = 0; i < invocationCount; i++)
291                         resultList[i] = invocationList[i];
292
293                     if (followList == null)
294                     {
295                         resultList[invocationCount] = dFollow;
296                     }
297                     else
298                     {
299                         for (int i = 0; i < followCount; i++)
300                             resultList[invocationCount + i] = followList[i];
301                     }
302                 }
303                 return NewMulticastDelegate(resultList, resultCount, true);
304             }
305         }
306
307         private object[] DeleteFromInvocationList(object[] invocationList, int invocationCount, int deleteIndex, int deleteCount)
308         {
309             Debug.Assert(_invocationList is object[]);
310             object[] thisInvocationList = (object[])_invocationList;
311             int allocCount = thisInvocationList.Length;
312             while (allocCount / 2 >= invocationCount - deleteCount)
313                 allocCount /= 2;
314
315             object[] newInvocationList = new object[allocCount];
316
317             for (int i = 0; i < deleteIndex; i++)
318                 newInvocationList[i] = invocationList[i];
319
320             for (int i = deleteIndex + deleteCount; i < invocationCount; i++)
321                 newInvocationList[i - deleteCount] = invocationList[i];
322
323             return newInvocationList;
324         }
325
326         private bool EqualInvocationLists(object[] a, object[] b, int start, int count)
327         {
328             for (int i = 0; i < count; i++)
329             {
330                 if (!(a[start + i].Equals(b[i])))
331                     return false;
332             }
333             return true;
334         }
335
336         // This method currently looks backward on the invocation list
337         //    for an element that has Delegate based equality with value.  (Doesn't
338         //    look at the invocation list.)  If this is found we remove it from
339         //    this list and return a new delegate.  If its not found a copy of the
340         //    current list is returned.
341         protected override sealed Delegate? RemoveImpl(Delegate value)
342         {
343             // There is a special case were we are removing using a delegate as
344             //    the value we need to check for this case
345             //
346             MulticastDelegate? v = value as MulticastDelegate;
347
348             if (v == null)
349                 return this;
350             if (!(v._invocationList is object[]))
351             {
352                 if (!(_invocationList is object[] invocationList))
353                 {
354                     // they are both not real Multicast
355                     if (this.Equals(value))
356                         return null;
357                 }
358                 else
359                 {
360                     int invocationCount = (int)_invocationCount;
361                     for (int i = invocationCount; --i >= 0;)
362                     {
363                         if (value.Equals(invocationList[i]))
364                         {
365                             if (invocationCount == 2)
366                             {
367                                 // Special case - only one value left, either at the beginning or the end
368                                 return (Delegate)invocationList[1 - i];
369                             }
370                             else
371                             {
372                                 object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1);
373                                 return NewMulticastDelegate(list, invocationCount - 1, true);
374                             }
375                         }
376                     }
377                 }
378             }
379             else
380             {
381                 if (_invocationList is object[] invocationList)
382                 {
383                     int invocationCount = (int)_invocationCount;
384                     int vInvocationCount = (int)v._invocationCount;
385                     for (int i = invocationCount - vInvocationCount; i >= 0; i--)
386                     {
387                         if (EqualInvocationLists(invocationList, (v._invocationList as object[])!, i, vInvocationCount))
388                         {
389                             if (invocationCount - vInvocationCount == 0)
390                             {
391                                 // Special case - no values left
392                                 return null;
393                             }
394                             else if (invocationCount - vInvocationCount == 1)
395                             {
396                                 // Special case - only one value left, either at the beginning or the end
397                                 return (Delegate)invocationList[i != 0 ? 0 : invocationCount - 1];
398                             }
399                             else
400                             {
401                                 object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, vInvocationCount);
402                                 return NewMulticastDelegate(list, invocationCount - vInvocationCount, true);
403                             }
404                         }
405                     }
406                 }
407             }
408
409             return this;
410         }
411
412         // This method returns the Invocation list of this multicast delegate.
413         public override sealed Delegate[] GetInvocationList()
414         {
415             Delegate[] del;
416             if (!(_invocationList is object[] invocationList))
417             {
418                 del = new Delegate[1];
419                 del[0] = this;
420             }
421             else
422             {
423                 // Create an array of delegate copies and each
424                 //    element into the array
425                 del = new Delegate[(int)_invocationCount];
426
427                 for (int i = 0; i < del.Length; i++)
428                     del[i] = (Delegate)invocationList[i];
429             }
430             return del;
431         }
432
433         // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
434         [MethodImpl(MethodImplOptions.AggressiveInlining)]
435         public static bool operator ==(MulticastDelegate? d1, MulticastDelegate? d2)
436         {
437             // Test d2 first to allow branch elimination when inlined for null checks (== null)
438             // so it can become a simple test
439             if (d2 is null)
440             {
441                 // return true/false not the test result https://github.com/dotnet/coreclr/issues/914
442                 return (d1 is null) ? true : false;
443             }
444
445             return ReferenceEquals(d2, d1) ? true : d2.Equals((object?)d1);
446         }
447
448         // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
449         [MethodImpl(MethodImplOptions.AggressiveInlining)]
450         public static bool operator !=(MulticastDelegate? d1, MulticastDelegate? d2)
451         {
452             // Can't call the == operator as it will call object==
453
454             // Test d2 first to allow branch elimination when inlined for not null checks (!= null)
455             // so it can become a simple test
456             if (d2 is null)
457             {
458                 // return true/false not the test result https://github.com/dotnet/coreclr/issues/914
459                 return (d1 is null) ? false : true;
460             }
461
462             return ReferenceEquals(d2, d1) ? false : !d2.Equals(d1);
463         }
464
465         public override sealed int GetHashCode()
466         {
467             if (IsUnmanagedFunctionPtr())
468                 return ValueType.GetHashCodeOfPtr(_methodPtr) ^ ValueType.GetHashCodeOfPtr(_methodPtrAux);
469
470             if (_invocationCount != (IntPtr)0)
471             {
472                 if (_invocationList is Delegate t)
473                 {
474                     // this is a wrapper delegate so we need to unwrap and check the inner one
475                     return t.GetHashCode();
476                 }
477             }
478
479             if (!(_invocationList is object[] invocationList))
480             {
481                 return base.GetHashCode();
482             }
483             else
484             {
485                 int hash = 0;
486                 for (int i = 0; i < (int)_invocationCount; i++)
487                 {
488                     hash = hash * 33 + invocationList[i].GetHashCode();
489                 }
490
491                 return hash;
492             }
493         }
494
495         internal override object? GetTarget()
496         {
497             if (_invocationCount != (IntPtr)0)
498             {
499                 // _invocationCount != 0 we are in one of these cases:
500                 // - Multicast -> return the target of the last delegate in the list
501                 // - wrapper delegate -> return the target of the inner delegate
502                 // - unmanaged function pointer - return null
503                 // - virtual open delegate - return null
504                 if (InvocationListLogicallyNull())
505                 {
506                     // both open virtual and ftn pointer return null for the target
507                     return null;
508                 }
509                 else
510                 {
511                     if (_invocationList is object[] invocationList)
512                     {
513                         int invocationCount = (int)_invocationCount;
514                         return ((Delegate)invocationList[invocationCount - 1]).GetTarget();
515                     }
516                     else
517                     {
518                         if (_invocationList is Delegate receiver)
519                             return receiver.GetTarget();
520                     }
521                 }
522             }
523             return base.GetTarget();
524         }
525
526         protected override MethodInfo GetMethodImpl()
527         {
528             if (_invocationCount != (IntPtr)0 && _invocationList != null)
529             {
530                 // multicast case
531                 if (_invocationList is object[] invocationList)
532                 {
533                     int index = (int)_invocationCount - 1;
534                     return ((Delegate)invocationList[index]).Method;
535                 }
536
537                 if (_invocationList is MulticastDelegate innerDelegate)
538                 {
539                     // must be a wrapper delegate
540                     return innerDelegate.GetMethodImpl();
541                 }
542             }
543             else if (IsUnmanagedFunctionPtr())
544             {
545                 // we handle unmanaged function pointers here because the generic ones (used for WinRT) would otherwise
546                 // be treated as open delegates by the base implementation, resulting in failure to get the MethodInfo
547                 if ((_methodBase == null) || !(_methodBase is MethodInfo))
548                 {
549                     IRuntimeMethodInfo method = FindMethodHandle();
550                     RuntimeType declaringType = RuntimeMethodHandle.GetDeclaringType(method);
551
552                     // need a proper declaring type instance method on a generic type
553                     if (RuntimeTypeHandle.IsGenericTypeDefinition(declaringType) || RuntimeTypeHandle.HasInstantiation(declaringType))
554                     {
555                         // we are returning the 'Invoke' method of this delegate so use this.GetType() for the exact type
556                         RuntimeType reflectedType = (RuntimeType)GetType();
557                         declaringType = reflectedType;
558                     }
559                     _methodBase = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method)!;
560                 }
561                 return (MethodInfo)_methodBase;
562             }
563
564             // Otherwise, must be an inner delegate of a Wrapper of an open virtual method. In that case, call base implementation
565             return base.GetMethodImpl();
566         }
567
568         // this should help inlining
569         [DoesNotReturn]
570         [System.Diagnostics.DebuggerNonUserCode]
571         private void ThrowNullThisInDelegateToInstance()
572         {
573             throw new ArgumentException(SR.Arg_DlgtNullInst);
574         }
575
576         [System.Diagnostics.DebuggerNonUserCode]
577         private void CtorClosed(object target, IntPtr methodPtr)
578         {
579             if (target == null)
580                 ThrowNullThisInDelegateToInstance();
581             this._target = target;
582             this._methodPtr = methodPtr;
583         }
584
585         [System.Diagnostics.DebuggerNonUserCode]
586         private void CtorClosedStatic(object target, IntPtr methodPtr)
587         {
588             this._target = target;
589             this._methodPtr = methodPtr;
590         }
591
592         [System.Diagnostics.DebuggerNonUserCode]
593         private void CtorRTClosed(object target, IntPtr methodPtr)
594         {
595             this._target = target;
596             this._methodPtr = AdjustTarget(target, methodPtr);
597         }
598
599         [System.Diagnostics.DebuggerNonUserCode]
600         private void CtorOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk)
601         {
602             this._target = this;
603             this._methodPtr = shuffleThunk;
604             this._methodPtrAux = methodPtr;
605         }
606
607         [System.Diagnostics.DebuggerNonUserCode]
608         private void CtorVirtualDispatch(object target, IntPtr methodPtr, IntPtr shuffleThunk)
609         {
610             this._target = this;
611             this._methodPtr = shuffleThunk;
612             this._methodPtrAux = GetCallStub(methodPtr);
613         }
614
615         [System.Diagnostics.DebuggerNonUserCode]
616         private void CtorCollectibleClosedStatic(object target, IntPtr methodPtr, IntPtr gchandle)
617         {
618             this._target = target;
619             this._methodPtr = methodPtr;
620             this._methodBase = System.Runtime.InteropServices.GCHandle.InternalGet(gchandle);
621         }
622
623         [System.Diagnostics.DebuggerNonUserCode]
624         private void CtorCollectibleOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk, IntPtr gchandle)
625         {
626             this._target = this;
627             this._methodPtr = shuffleThunk;
628             this._methodPtrAux = methodPtr;
629             this._methodBase = System.Runtime.InteropServices.GCHandle.InternalGet(gchandle);
630         }
631
632         [System.Diagnostics.DebuggerNonUserCode]
633         private void CtorCollectibleVirtualDispatch(object target, IntPtr methodPtr, IntPtr shuffleThunk, IntPtr gchandle)
634         {
635             this._target = this;
636             this._methodPtr = shuffleThunk;
637             this._methodPtrAux = GetCallStub(methodPtr);
638             this._methodBase = System.Runtime.InteropServices.GCHandle.InternalGet(gchandle);
639         }
640     }
641 }