Nullable: System.Runtime.InteropServices.CustomMarshalers/WindowsRuntime (#23930)
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / src / System / Runtime / InteropServices / WindowsRuntime / ListToVectorAdapter.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 #nullable enable
6 using System.Collections.Generic;
7 using System.Collections.ObjectModel;
8 using System.Diagnostics;
9 using Internal.Runtime.CompilerServices;
10
11 namespace System.Runtime.InteropServices.WindowsRuntime
12 {
13     // This is a set of stub methods implementing the support for the IVector`1 interface on managed
14     // objects that implement IList`1. Used by the interop mashaling infrastructure.
15     //
16     // The methods on this class must be written VERY carefully to avoid introducing security holes.
17     // That's because they are invoked with special "this"! The "this" object
18     // for all of these methods are not ListToVectorAdapter objects. Rather, they are of type
19     // IList<T>. No actual ListToVectorAdapter object is ever instantiated. Thus, you will
20     // see a lot of expressions that cast "this" to "IList<T>". 
21     internal sealed class ListToVectorAdapter
22     {
23         private ListToVectorAdapter()
24         {
25             Debug.Fail("This class is never instantiated");
26         }
27
28         // T GetAt(uint index)
29         internal T GetAt<T>(uint index)
30         {
31             IList<T> _this = Unsafe.As<IList<T>>(this);
32             EnsureIndexInt32(index, _this.Count);
33
34             try
35             {
36                 return _this[(int)index];
37             }
38             catch (ArgumentOutOfRangeException ex)
39             {
40                 throw WindowsRuntimeMarshal.GetExceptionForHR(HResults.E_BOUNDS, ex, "ArgumentOutOfRange_IndexOutOfRange");
41             }
42         }
43
44         // uint Size { get }
45         internal uint Size<T>()
46         {
47             IList<T> _this = Unsafe.As<IList<T>>(this);
48             return (uint)_this.Count;
49         }
50
51         // IVectorView<T> GetView()
52         internal IReadOnlyList<T> GetView<T>()
53         {
54             IList<T> _this = Unsafe.As<IList<T>>(this);
55             Debug.Assert(_this != null);
56
57             // Note: This list is not really read-only - you could QI for a modifiable
58             // list.  We gain some perf by doing this.  We believe this is acceptable.
59             if (!(_this is IReadOnlyList<T> roList))
60             {
61                 roList = new ReadOnlyCollection<T>(_this);
62             }
63             return roList;
64         }
65
66         // bool IndexOf(T value, out uint index)
67         internal bool IndexOf<T>(T value, out uint index)
68         {
69             IList<T> _this = Unsafe.As<IList<T>>(this);
70             int ind = _this.IndexOf(value);
71
72             if (-1 == ind)
73             {
74                 index = 0;
75                 return false;
76             }
77
78             index = (uint)ind;
79             return true;
80         }
81
82         // void SetAt(uint index, T value)
83         internal void SetAt<T>(uint index, T value)
84         {
85             IList<T> _this = Unsafe.As<IList<T>>(this);
86             EnsureIndexInt32(index, _this.Count);
87
88             try
89             {
90                 _this[(int)index] = value;
91             }
92             catch (ArgumentOutOfRangeException ex)
93             {
94                 throw WindowsRuntimeMarshal.GetExceptionForHR(HResults.E_BOUNDS, ex, "ArgumentOutOfRange_IndexOutOfRange");
95             }
96         }
97
98         // void InsertAt(uint index, T value)
99         internal void InsertAt<T>(uint index, T value)
100         {
101             IList<T> _this = Unsafe.As<IList<T>>(this);
102
103             // Inserting at an index one past the end of the list is equivalent to appending
104             // so we need to ensure that we're within (0, count + 1).
105             EnsureIndexInt32(index, _this.Count + 1);
106
107             try
108             {
109                 _this.Insert((int)index, value);
110             }
111             catch (ArgumentOutOfRangeException ex)
112             {
113                 // Change error code to match what WinRT expects
114                 ex.HResult = HResults.E_BOUNDS;
115                 throw;
116             }
117         }
118
119         // void RemoveAt(uint index)
120         internal void RemoveAt<T>(uint index)
121         {
122             IList<T> _this = Unsafe.As<IList<T>>(this);
123             EnsureIndexInt32(index, _this.Count);
124
125             try
126             {
127                 _this.RemoveAt((int)index);
128             }
129             catch (ArgumentOutOfRangeException ex)
130             {
131                 // Change error code to match what WinRT expects
132                 ex.HResult = HResults.E_BOUNDS;
133                 throw;
134             }
135         }
136
137         // void Append(T value)
138         internal void Append<T>(T value)
139         {
140             IList<T> _this = Unsafe.As<IList<T>>(this);
141             _this.Add(value);
142         }
143
144         // void RemoveAtEnd()
145         internal void RemoveAtEnd<T>()
146         {
147             IList<T> _this = Unsafe.As<IList<T>>(this);
148             if (_this.Count == 0)
149             {
150                 Exception e = new InvalidOperationException(SR.InvalidOperation_CannotRemoveLastFromEmptyCollection);
151                 e.HResult = HResults.E_BOUNDS;
152                 throw e;
153             }
154
155             uint size = (uint)_this.Count;
156             RemoveAt<T>(size - 1);
157         }
158
159         // void Clear()
160         internal void Clear<T>()
161         {
162             IList<T> _this = Unsafe.As<IList<T>>(this);
163             _this.Clear();
164         }
165
166         // uint GetMany(uint startIndex, T[] items)
167         internal uint GetMany<T>(uint startIndex, T[] items)
168         {
169             IList<T> _this = Unsafe.As<IList<T>>(this);
170             return GetManyHelper<T>(_this, startIndex, items);
171         }
172
173         // void ReplaceAll(T[] items)
174         internal void ReplaceAll<T>(T[] items)
175         {
176             IList<T> _this = Unsafe.As<IList<T>>(this);
177             _this.Clear();
178
179             if (items != null)
180             {
181                 foreach (T item in items)
182                 {
183                     _this.Add(item);
184                 }
185             }
186         }
187
188         // Helpers:
189
190         private static void EnsureIndexInt32(uint index, int listCapacity)
191         {
192             // We use '<=' and not '<' becasue int.MaxValue == index would imply
193             // that Size > int.MaxValue:
194             if (((uint)int.MaxValue) <= index || index >= (uint)listCapacity)
195             {
196                 Exception e = new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexLargerThanMaxValue);
197                 e.HResult = HResults.E_BOUNDS;
198                 throw e;
199             }
200         }
201
202         private static uint GetManyHelper<T>(IList<T> sourceList, uint startIndex, T[] items)
203         {
204             // Calling GetMany with a start index equal to the size of the list should always
205             // return 0 elements, regardless of the input item size
206             if (startIndex == sourceList.Count)
207             {
208                 return 0;
209             }
210
211             EnsureIndexInt32(startIndex, sourceList.Count);
212
213             if (items == null)
214             {
215                 return 0;
216             }
217
218             uint itemCount = Math.Min((uint)items.Length, (uint)sourceList.Count - startIndex);
219             for (uint i = 0; i < itemCount; ++i)
220             {
221                 items[i] = sourceList[(int)(i + startIndex)];
222             }
223
224             if (typeof(T) == typeof(string))
225             {
226                 string[] stringItems = (items as string[])!;
227
228                 // Fill in rest of the array with string.Empty to avoid marshaling failure
229                 for (uint i = itemCount; i < items.Length; ++i)
230                     stringItems[i] = string.Empty;
231             }
232
233             return itemCount;
234         }
235     }
236 }