Skip to content

Commit 310b824

Browse files
Remove invalid Unsafe.As from array helpers (#99778)
* Remove UB from helpers * Cleanup more helpers * Fix Test.CoreLib * Cleanup --------- Co-authored-by: Jan Kotas <jkotas@microsoft.com>
1 parent 1c73fa7 commit 310b824

File tree

9 files changed

+140
-110
lines changed

9 files changed

+140
-110
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5-
using System.Numerics;
6-
using System.Runtime.CompilerServices;
75
using System.Runtime.InteropServices;
8-
using System.Threading;
96

107
namespace System.Runtime.CompilerServices
118
{
9+
[StackTraceHidden]
10+
[DebuggerStepThrough]
1211
internal static unsafe class CastHelpers
1312
{
1413
// In coreclr the table is allocated and written to on the native side.
@@ -24,14 +23,12 @@ internal static unsafe class CastHelpers
2423
private static extern ref byte Unbox_Helper(void* toTypeHnd, object obj);
2524

2625
[MethodImpl(MethodImplOptions.InternalCall)]
27-
private static extern void WriteBarrier(ref object? dst, object obj);
26+
private static extern void WriteBarrier(ref object? dst, object? obj);
2827

2928
// IsInstanceOf test used for unusual cases (naked type parameters, variant generic types)
3029
// Unlike the IsInstanceOfInterface and IsInstanceOfClass functions,
3130
// this test must deal with all kinds of type tests
3231
[DebuggerHidden]
33-
[StackTraceHidden]
34-
[DebuggerStepThrough]
3532
private static object? IsInstanceOfAny(void* toTypeHnd, object? obj)
3633
{
3734
if (obj != null)
@@ -63,8 +60,6 @@ internal static unsafe class CastHelpers
6360
}
6461

6562
[DebuggerHidden]
66-
[StackTraceHidden]
67-
[DebuggerStepThrough]
6863
private static object? IsInstanceOfInterface(void* toTypeHnd, object? obj)
6964
{
7065
const int unrollSize = 4;
@@ -134,8 +129,6 @@ internal static unsafe class CastHelpers
134129
}
135130

136131
[DebuggerHidden]
137-
[StackTraceHidden]
138-
[DebuggerStepThrough]
139132
private static object? IsInstanceOfClass(void* toTypeHnd, object? obj)
140133
{
141134
if (obj == null || RuntimeHelpers.GetMethodTable(obj) == toTypeHnd)
@@ -184,8 +177,6 @@ internal static unsafe class CastHelpers
184177
}
185178

186179
[DebuggerHidden]
187-
[StackTraceHidden]
188-
[DebuggerStepThrough]
189180
[MethodImpl(MethodImplOptions.NoInlining)]
190181
private static object? IsInstance_Helper(void* toTypeHnd, object obj)
191182
{
@@ -207,8 +198,6 @@ internal static unsafe class CastHelpers
207198
// Unlike the ChkCastInterface and ChkCastClass functions,
208199
// this test must deal with all kinds of type tests
209200
[DebuggerHidden]
210-
[StackTraceHidden]
211-
[DebuggerStepThrough]
212201
internal static object? ChkCastAny(void* toTypeHnd, object? obj)
213202
{
214203
CastResult result;
@@ -237,8 +226,6 @@ internal static unsafe class CastHelpers
237226
}
238227

239228
[DebuggerHidden]
240-
[StackTraceHidden]
241-
[DebuggerStepThrough]
242229
[MethodImpl(MethodImplOptions.NoInlining)]
243230
private static object? ChkCast_Helper(void* toTypeHnd, object obj)
244231
{
@@ -253,8 +240,6 @@ internal static unsafe class CastHelpers
253240
}
254241

255242
[DebuggerHidden]
256-
[StackTraceHidden]
257-
[DebuggerStepThrough]
258243
private static object? ChkCastInterface(void* toTypeHnd, object? obj)
259244
{
260245
const int unrollSize = 4;
@@ -321,8 +306,6 @@ internal static unsafe class CastHelpers
321306
}
322307

323308
[DebuggerHidden]
324-
[StackTraceHidden]
325-
[DebuggerStepThrough]
326309
private static object? ChkCastClass(void* toTypeHnd, object? obj)
327310
{
328311
if (obj == null || RuntimeHelpers.GetMethodTable(obj) == toTypeHnd)
@@ -336,8 +319,6 @@ internal static unsafe class CastHelpers
336319
// Optimized helper for classes. Assumes that the trivial cases
337320
// has been taken care of by the inlined check
338321
[DebuggerHidden]
339-
[StackTraceHidden]
340-
[DebuggerStepThrough]
341322
private static object? ChkCastClassSpecial(void* toTypeHnd, object obj)
342323
{
343324
MethodTable* mt = RuntimeHelpers.GetMethodTable(obj);
@@ -384,52 +365,53 @@ internal static unsafe class CastHelpers
384365
}
385366

386367
[DebuggerHidden]
387-
[StackTraceHidden]
388-
[DebuggerStepThrough]
389368
private static ref byte Unbox(void* toTypeHnd, object obj)
390369
{
391-
// this will throw NullReferenceException if obj is null, attributed to the user code, as expected.
370+
// This will throw NullReferenceException if obj is null.
392371
if (RuntimeHelpers.GetMethodTable(obj) == toTypeHnd)
393372
return ref obj.GetRawData();
394373

395374
return ref Unbox_Helper(toTypeHnd, obj);
396375
}
397376

398-
internal struct ArrayElement
377+
[DebuggerHidden]
378+
private static void ThrowIndexOutOfRangeException()
399379
{
400-
public object? Value;
380+
throw new IndexOutOfRangeException();
401381
}
402382

403383
[DebuggerHidden]
404-
[StackTraceHidden]
405-
[DebuggerStepThrough]
406-
private static ref object? ThrowArrayMismatchException()
384+
private static void ThrowArrayMismatchException()
407385
{
408386
throw new ArrayTypeMismatchException();
409387
}
410388

411389
[DebuggerHidden]
412-
[StackTraceHidden]
413-
[DebuggerStepThrough]
414-
private static ref object? LdelemaRef(Array array, nint index, void* type)
390+
private static ref object? LdelemaRef(object?[] array, nint index, void* type)
415391
{
416-
// this will throw appropriate exceptions if array is null or access is out of range.
417-
ref object? element = ref Unsafe.As<ArrayElement[]>(array)[index].Value;
392+
// This will throw NullReferenceException if array is null.
393+
if ((nuint)index >= (uint)array.Length)
394+
ThrowIndexOutOfRangeException();
395+
396+
Debug.Assert(index >= 0);
397+
ref object? element = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
418398
void* elementType = RuntimeHelpers.GetMethodTable(array)->ElementType;
419399

420-
if (elementType == type)
421-
return ref element;
400+
if (elementType != type)
401+
ThrowArrayMismatchException();
422402

423-
return ref ThrowArrayMismatchException();
403+
return ref element;
424404
}
425405

426406
[DebuggerHidden]
427-
[StackTraceHidden]
428-
[DebuggerStepThrough]
429-
private static void StelemRef(Array array, nint index, object? obj)
407+
private static void StelemRef(object?[] array, nint index, object? obj)
430408
{
431-
// this will throw appropriate exceptions if array is null or access is out of range.
432-
ref object? element = ref Unsafe.As<ArrayElement[]>(array)[index].Value;
409+
// This will throw NullReferenceException if array is null.
410+
if ((nuint)index >= (uint)array.Length)
411+
ThrowIndexOutOfRangeException();
412+
413+
Debug.Assert(index >= 0);
414+
ref object? element = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
433415
void* elementType = RuntimeHelpers.GetMethodTable(array)->ElementType;
434416

435417
if (obj == null)
@@ -454,8 +436,6 @@ private static void StelemRef(Array array, nint index, object? obj)
454436
}
455437

456438
[DebuggerHidden]
457-
[StackTraceHidden]
458-
[DebuggerStepThrough]
459439
[MethodImpl(MethodImplOptions.NoInlining)]
460440
private static void StelemRef_Helper(ref object? element, void* elementType, object obj)
461441
{
@@ -470,20 +450,17 @@ private static void StelemRef_Helper(ref object? element, void* elementType, obj
470450
}
471451

472452
[DebuggerHidden]
473-
[StackTraceHidden]
474-
[DebuggerStepThrough]
475453
private static void StelemRef_Helper_NoCacheLookup(ref object? element, void* elementType, object obj)
476454
{
477455
Debug.Assert(obj != null);
478456

479457
obj = IsInstanceOfAny_NoCacheLookup(elementType, obj);
480-
if (obj != null)
458+
if (obj == null)
481459
{
482-
WriteBarrier(ref element, obj);
483-
return;
460+
ThrowArrayMismatchException();
484461
}
485462

486-
throw new ArrayTypeMismatchException();
463+
WriteBarrier(ref element, obj);
487464
}
488465
}
489466
}

src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/InternalCalls.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ internal static int RhEndNoGCRegion()
150150

151151
[RuntimeImport(Redhawk.BaseName, "RhpAssignRef")]
152152
[MethodImpl(MethodImplOptions.InternalCall)]
153-
internal static extern unsafe void RhpAssignRef(ref object address, object obj);
153+
internal static extern unsafe void RhpAssignRef(ref object? address, object? obj);
154154

155155
[MethodImplAttribute(MethodImplOptions.InternalCall)]
156156
[RuntimeImport(Redhawk.BaseName, "RhpGcSafeZeroMemory")]

src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ namespace System.Runtime
2222
//
2323
/////////////////////////////////////////////////////////////////////////////////////////////////////
2424

25+
[StackTraceHidden]
26+
[DebuggerStepThrough]
2527
[EagerStaticClassConstruction]
2628
internal static class TypeCast
2729
{
@@ -737,23 +739,69 @@ public static unsafe void CheckArrayStore(object array, object obj)
737739
throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.ArrayTypeMismatch);
738740
}
739741

740-
internal struct ArrayElement
742+
private static unsafe void ThrowIndexOutOfRangeException(object?[] array)
741743
{
742-
public object Value;
744+
// Throw the index out of range exception defined by the classlib, using the input array's MethodTable*
745+
// to find the correct classlib.
746+
throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.IndexOutOfRange);
747+
}
748+
749+
private static unsafe void ThrowArrayMismatchException(object?[] array)
750+
{
751+
// Throw the array type mismatch exception defined by the classlib, using the input array's MethodTable*
752+
// to find the correct classlib.
753+
throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.ArrayTypeMismatch);
743754
}
744755

745756
//
746757
// Array stelem/ldelema helpers with RyuJIT conventions
747758
//
759+
760+
[RuntimeExport("RhpLdelemaRef")]
761+
public static unsafe ref object? LdelemaRef(object?[] array, nint index, MethodTable* elementType)
762+
{
763+
Debug.Assert(array is null || array.GetMethodTable()->IsArray, "first argument must be an array");
764+
765+
#if INPLACE_RUNTIME
766+
// This will throw NullReferenceException if obj is null.
767+
if ((nuint)index >= (uint)array.Length)
768+
ThrowIndexOutOfRangeException(array);
769+
770+
Debug.Assert(index >= 0);
771+
ref object? element = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
772+
#else
773+
if (array is null)
774+
{
775+
throw elementType->GetClasslibException(ExceptionIDs.NullReference);
776+
}
777+
if ((nuint)index >= (uint)array.Length)
778+
{
779+
throw elementType->GetClasslibException(ExceptionIDs.IndexOutOfRange);
780+
}
781+
ref object rawData = ref Unsafe.As<byte, object>(ref Unsafe.As<RawArrayData>(array).Data);
782+
ref object element = ref Unsafe.Add(ref rawData, index);
783+
#endif
784+
MethodTable* arrayElemType = array.GetMethodTable()->RelatedParameterType;
785+
786+
if (elementType != arrayElemType)
787+
ThrowArrayMismatchException(array);
788+
789+
return ref element;
790+
}
791+
748792
[RuntimeExport("RhpStelemRef")]
749-
public static unsafe void StelemRef(Array array, nint index, object obj)
793+
public static unsafe void StelemRef(object?[] array, nint index, object? obj)
750794
{
751795
// This is supported only on arrays
752-
Debug.Assert(array.GetMethodTable()->IsArray, "first argument must be an array");
796+
Debug.Assert(array is null || array.GetMethodTable()->IsArray, "first argument must be an array");
753797

754798
#if INPLACE_RUNTIME
755-
// this will throw appropriate exceptions if array is null or access is out of range.
756-
ref object element = ref Unsafe.As<ArrayElement[]>(array)[index].Value;
799+
// This will throw NullReferenceException if obj is null.
800+
if ((nuint)index >= (uint)array.Length)
801+
ThrowIndexOutOfRangeException(array);
802+
803+
Debug.Assert(index >= 0);
804+
ref object? element = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
757805
#else
758806
if (array is null)
759807
{
@@ -796,7 +844,7 @@ public static unsafe void StelemRef(Array array, nint index, object obj)
796844
}
797845

798846
[MethodImpl(MethodImplOptions.NoInlining)]
799-
private static unsafe void StelemRef_Helper(ref object element, MethodTable* elementType, object obj)
847+
private static unsafe void StelemRef_Helper(ref object? element, MethodTable* elementType, object obj)
800848
{
801849
CastResult result = s_castCache.TryGet((nuint)obj.GetMethodTable() + (int)AssignmentVariation.BoxedSource, (nuint)elementType);
802850
if (result == CastResult.CanCast)
@@ -808,58 +856,17 @@ private static unsafe void StelemRef_Helper(ref object element, MethodTable* ele
808856
StelemRef_Helper_NoCacheLookup(ref element, elementType, obj);
809857
}
810858

811-
private static unsafe void StelemRef_Helper_NoCacheLookup(ref object element, MethodTable* elementType, object obj)
859+
private static unsafe void StelemRef_Helper_NoCacheLookup(ref object? element, MethodTable* elementType, object obj)
812860
{
813861
object? castedObj = IsInstanceOfAny_NoCacheLookup(elementType, obj);
814-
if (castedObj != null)
862+
if (castedObj == null)
815863
{
816-
InternalCalls.RhpAssignRef(ref element, obj);
817-
return;
864+
// Throw the array type mismatch exception defined by the classlib, using the input array's
865+
// MethodTable* to find the correct classlib.
866+
throw elementType->GetClasslibException(ExceptionIDs.ArrayTypeMismatch);
818867
}
819868

820-
// Throw the array type mismatch exception defined by the classlib, using the input array's
821-
// MethodTable* to find the correct classlib.
822-
throw elementType->GetClasslibException(ExceptionIDs.ArrayTypeMismatch);
823-
}
824-
825-
[RuntimeExport("RhpLdelemaRef")]
826-
public static unsafe ref object LdelemaRef(Array array, nint index, IntPtr elementType)
827-
{
828-
Debug.Assert(array is null || array.GetMethodTable()->IsArray, "first argument must be an array");
829-
830-
#if INPLACE_RUNTIME
831-
// this will throw appropriate exceptions if array is null or access is out of range.
832-
ref object element = ref Unsafe.As<ArrayElement[]>(array)[index].Value;
833-
#else
834-
if (array is null)
835-
{
836-
throw ((MethodTable*)elementType)->GetClasslibException(ExceptionIDs.NullReference);
837-
}
838-
if ((uint)index >= (uint)array.Length)
839-
{
840-
throw ((MethodTable*)elementType)->GetClasslibException(ExceptionIDs.IndexOutOfRange);
841-
}
842-
ref object rawData = ref Unsafe.As<byte, object>(ref Unsafe.As<RawArrayData>(array).Data);
843-
ref object element = ref Unsafe.Add(ref rawData, index);
844-
#endif
845-
846-
MethodTable* elemType = (MethodTable*)elementType;
847-
MethodTable* arrayElemType = array.GetMethodTable()->RelatedParameterType;
848-
849-
if (elemType == arrayElemType)
850-
{
851-
return ref element;
852-
}
853-
854-
return ref ThrowArrayMismatchException(array);
855-
}
856-
857-
// This weird structure is for parity with CoreCLR - allows potentially to be tailcalled
858-
private static unsafe ref object ThrowArrayMismatchException(Array array)
859-
{
860-
// Throw the array type mismatch exception defined by the classlib, using the input array's MethodTable*
861-
// to find the correct classlib.
862-
throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.ArrayTypeMismatch);
869+
InternalCalls.RhpAssignRef(ref element, obj);
863870
}
864871

865872
private static unsafe object IsInstanceOfArray(MethodTable* pTargetType, object obj)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
namespace System.Diagnostics
5+
{
6+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)]
7+
public sealed class DebuggerStepThroughAttribute : Attribute
8+
{
9+
public DebuggerStepThroughAttribute() { }
10+
}
11+
}

0 commit comments

Comments
 (0)