Skip to content

Commit 825bc44

Browse files
authored
Remove or document Unsafe.AsPointer uses in core libraries (#99146)
* Remove or document Unsafe.AsPointer uses in System.Memory - Also add internal helper method Unsafe.IsOpportunisticallyAligned - And cast a pointer to IntPtr instead of int for a unit test * Fix typo and cast size to unsigned * Address feedback * Changes for the rest of the core libraries - Other than MethodHandle code for NativeAOT - Add Unsafe.OpportunisticMisalignment * Fix compile errors * Add full CS8500 comment & fix compile error
1 parent 8510651 commit 825bc44

File tree

42 files changed

+172
-98
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+172
-98
lines changed

src/coreclr/System.Private.CoreLib/src/System/ArgIterator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private struct SigPointer
2424
private int _remainingArgs; // # of remaining args.
2525

2626
#if TARGET_WINDOWS // Native Varargs are not supported on Unix
27-
// ArgIterator is a ref struct. It does not require pinning.
27+
// ArgIterator is a ref struct. It does not require pinning, therefore Unsafe.AsPointer is safe.
2828
// This method null checks the this pointer as a side-effect.
2929
private ArgIterator* ThisPtr => (ArgIterator*)Unsafe.AsPointer(ref _argCookie);
3030

src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,9 @@ public static unsafe IReadOnlyDictionary<string, object> GetConfigurationVariabl
865865
Configurations = new Dictionary<string, object>()
866866
};
867867

868-
_EnumerateConfigurationValues(Unsafe.AsPointer(ref context), &ConfigCallback);
868+
#pragma warning disable CS8500 // takes address of managed type
869+
_EnumerateConfigurationValues(&context, &ConfigCallback);
870+
#pragma warning restore CS8500
869871
return context.Configurations!;
870872
}
871873

src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,12 @@ private static unsafe object[] InitializeStatics(IntPtr gcStaticRegionStart, int
206206
nint blockAddr = MethodTable.SupportsRelativePointers ? (nint)ReadRelPtr32(pBlock) : *pBlock;
207207
if ((blockAddr & GCStaticRegionConstants.Uninitialized) == GCStaticRegionConstants.Uninitialized)
208208
{
209+
#pragma warning disable CS8500 // takes address of managed type
209210
object? obj = null;
210211
RuntimeImports.RhAllocateNewObject(
211212
new IntPtr(blockAddr & ~GCStaticRegionConstants.Mask),
212213
(uint)GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP,
213-
Unsafe.AsPointer(ref obj));
214+
&obj);
214215
if (obj == null)
215216
{
216217
RuntimeExceptionHelpers.FailFast("Failed allocating GC static bases");
@@ -232,7 +233,8 @@ private static unsafe object[] InitializeStatics(IntPtr gcStaticRegionStart, int
232233
Unsafe.Add(ref rawSpineData, currentBase) = obj;
233234

234235
// Update the base pointer to point to the pinned object
235-
*pBlock = *(IntPtr*)Unsafe.AsPointer(ref obj);
236+
*pBlock = *(IntPtr*)&obj;
237+
#pragma warning restore CS8500
236238
}
237239

238240
currentBase++;

src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,9 @@ public static unsafe IReadOnlyDictionary<string, object> GetConfigurationVariabl
695695
Configurations = new Dictionary<string, object>()
696696
};
697697

698-
RuntimeImports.RhEnumerateConfigurationValues(Unsafe.AsPointer(ref context), &ConfigCallback);
698+
#pragma warning disable CS8500 // takes address of managed type
699+
RuntimeImports.RhEnumerateConfigurationValues(&context, &ConfigCallback);
700+
#pragma warning restore CS8500
699701
return context.Configurations!;
700702
}
701703

@@ -830,7 +832,9 @@ static T[] AllocateNewUninitializedArray(int length, bool pinned)
830832
throw new OverflowException();
831833

832834
T[]? array = null;
833-
RuntimeImports.RhAllocateNewArray(MethodTable.Of<T[]>(), (uint)length, (uint)flags, Unsafe.AsPointer(ref array));
835+
#pragma warning disable CS8500 // takes address of managed type
836+
RuntimeImports.RhAllocateNewArray(MethodTable.Of<T[]>(), (uint)length, (uint)flags, &array);
837+
#pragma warning restore CS8500
834838
if (array == null)
835839
throw new OutOfMemoryException();
836840

@@ -857,7 +861,9 @@ public static unsafe T[] AllocateArray<T>(int length, bool pinned = false)
857861
throw new OverflowException();
858862

859863
T[]? array = null;
860-
RuntimeImports.RhAllocateNewArray(MethodTable.Of<T[]>(), (uint)length, (uint)flags, Unsafe.AsPointer(ref array));
864+
#pragma warning disable CS8500 // takes address of managed type
865+
RuntimeImports.RhAllocateNewArray(MethodTable.Of<T[]>(), (uint)length, (uint)flags, &array);
866+
#pragma warning restore CS8500
861867
if (array == null)
862868
throw new OutOfMemoryException();
863869

src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,11 @@ private CompilationResult CompileMethodInternal(IMethodNode methodCodeNodeNeedin
350350
IntPtr exception;
351351
IntPtr nativeEntry;
352352
uint codeSize;
353+
#pragma warning disable CS8500 // takes address of managed type
353354
var result = JitCompileMethod(out exception,
354-
_jit, (IntPtr)Unsafe.AsPointer(ref _this), _unmanagedCallbacks,
355+
_jit, (IntPtr)(&_this), _unmanagedCallbacks,
355356
ref methodInfo, (uint)CorJitFlag.CORJIT_FLAG_CALL_GETJITFLAGS, out nativeEntry, out codeSize);
357+
#pragma warning restore CS8500
356358
if (exception != IntPtr.Zero)
357359
{
358360
if (_lastException != null)

src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs

+6-4
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ private static void Test_get_runtime_property(string[] args)
4646

4747
static string GetProperty(string name, host_runtime_contract contract)
4848
{
49-
Span<byte> nameSpan = stackalloc byte[Encoding.UTF8.GetMaxByteCount(name.Length)];
50-
byte* namePtr = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(nameSpan));
49+
int nameSize = Encoding.UTF8.GetMaxByteCount(name.Length);
50+
byte* namePtr = stackalloc byte[nameSize];
51+
Span<byte> nameSpan = new Span<byte>(namePtr, nameSize);
5152
int nameLen = Encoding.UTF8.GetBytes(name, nameSpan);
5253
nameSpan[nameLen] = 0;
5354

@@ -86,8 +87,9 @@ public static void Test_bundle_probe(string[] args)
8687

8788
unsafe static void Probe(host_runtime_contract contract, string path)
8889
{
89-
Span<byte> pathSpan = stackalloc byte[Encoding.UTF8.GetMaxByteCount(path.Length)];
90-
byte* pathPtr = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pathSpan));
90+
int pathSize = Encoding.UTF8.GetMaxByteCount(path.Length);
91+
byte* pathPtr = stackalloc byte[pathSize];
92+
Span<byte> pathSpan = new Span<byte>(pathPtr, pathSize);
9193
int pathLen = Encoding.UTF8.GetBytes(path, pathSpan);
9294
pathSpan[pathLen] = 0;
9395

src/libraries/Common/src/System/Number.NumberBuffer.cs

+5-9
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ internal unsafe ref struct NumberBuffer
2929
public bool IsNegative;
3030
public bool HasNonZeroTail;
3131
public NumberBufferKind Kind;
32-
public Span<byte> Digits;
32+
public byte* DigitsPtr;
33+
public int DigitsLength;
34+
public readonly Span<byte> Digits => new Span<byte>(DigitsPtr, DigitsLength);
3335

3436
public NumberBuffer(NumberBufferKind kind, byte* digits, int digitsLength) : this(kind, new Span<byte>(digits, digitsLength))
3537
{
@@ -48,7 +50,8 @@ public NumberBuffer(NumberBufferKind kind, Span<byte> digits)
4850
IsNegative = false;
4951
HasNonZeroTail = false;
5052
Kind = kind;
51-
Digits = digits;
53+
DigitsPtr = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(digits)); // Safe since memory must be fixed
54+
DigitsLength = digits.Length;
5255
#if DEBUG
5356
Digits.Fill(0xCC);
5457
#endif
@@ -83,13 +86,6 @@ public void CheckConsistency()
8386
}
8487
#pragma warning restore CA1822
8588

86-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
87-
public byte* GetDigitsPointer()
88-
{
89-
// This is safe to do since we are a ref struct
90-
return (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(Digits));
91-
}
92-
9389
//
9490
// Code coverage note: This only exists so that Number displays nicely in the VS watch window. So yes, I know it works.
9591
//

src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.T.cs

+1-8
Original file line numberDiff line numberDiff line change
@@ -424,14 +424,7 @@ public void MultipleCallsToGetSpan()
424424
Assert.True(span.Length >= 256);
425425
Span<T> newSpan = output.GetSpan();
426426
Assert.Equal(span.Length, newSpan.Length);
427-
428-
unsafe
429-
{
430-
void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
431-
void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan));
432-
Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan);
433-
}
434-
427+
Assert.Equal(0, Unsafe.ByteOffset(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(newSpan)));
435428
Assert.Equal(span.Length, output.GetSpan().Length);
436429
}
437430
finally

src/libraries/System.Memory/tests/MemoryMarshal/CreateFromPinnedArray.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,16 @@ public static unsafe void CreateFromPinnedArrayVerifyPinning()
190190
int[] pinnedArray = { 90, 91, 92, 93, 94, 95, 96, 97, 98 };
191191
GCHandle pinnedGCHandle = GCHandle.Alloc(pinnedArray, GCHandleType.Pinned);
192192

193+
// Unsafe.AsPointer is used to ensure we catch if the GC moves the memory
193194
Memory<int> pinnedMemory = MemoryMarshal.CreateFromPinnedArray(pinnedArray, 0, 2);
194195
void* pinnedPtr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(pinnedMemory.Span));
195196
void* memoryHandlePinnedPtr = pinnedMemory.Pin().Pointer;
196197

197198
GC.Collect();
198199
GC.Collect(2);
199200

200-
Assert.Equal((int)pinnedPtr, (int)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pinnedMemory.Span)));
201-
Assert.Equal((int)memoryHandlePinnedPtr, (int)pinnedGCHandle.AddrOfPinnedObject().ToPointer());
201+
Assert.Equal((IntPtr)pinnedPtr, (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pinnedMemory.Span)));
202+
Assert.Equal((IntPtr)memoryHandlePinnedPtr, pinnedGCHandle.AddrOfPinnedObject());
202203

203204
pinnedGCHandle.Free();
204205
}

src/libraries/System.Memory/tests/MemoryMarshal/GetArrayDataReference.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static unsafe void GetArrayDataReference_EmptyInput_ReturnsRefToWhereFirs
4343

4444
ref int theRef = ref MemoryMarshal.GetArrayDataReference(theArray);
4545

46-
Assert.True(Unsafe.AsPointer(ref theRef) != null);
46+
Assert.False(Unsafe.IsNullRef(ref theRef));
4747
Assert.True(Unsafe.AreSame(ref theRef, ref MemoryMarshal.GetReference(theArray.AsSpan())));
4848

4949
ref int theMdArrayRef = ref Unsafe.As<byte, int>(ref MemoryMarshal.GetArrayDataReference((Array)theArray)); // szarray passed to generalized Array helper

src/libraries/System.Memory/tests/MemoryPool/MemoryPool.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public static void MemoryPoolSpan()
5252
{
5353
unsafe
5454
{
55+
// Unsafe.AsPointer is safe here since it's pinned
5556
void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(sp));
5657
Assert.Equal((IntPtr)newMemoryHandle.Pointer, (IntPtr)pSpan);
5758
}
@@ -77,6 +78,7 @@ public static void MemoryPoolPin(int elementIndex)
7778
{
7879
unsafe
7980
{
81+
// Unsafe.AsPointer is safe here since it's pinned
8082
void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(sp.Slice(elementIndex)));
8183
Assert.Equal((IntPtr)pSpan, ((IntPtr)newMemoryHandle.Pointer));
8284
}
@@ -112,6 +114,7 @@ public static void MemoryPoolPinOffsetAtEnd()
112114
{
113115
unsafe
114116
{
117+
// Unsafe.AsPointer is safe here since it's pinned
115118
void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(sp.Slice(elementIndex)));
116119
Assert.Equal((IntPtr)pSpan, ((IntPtr)newMemoryHandle.Pointer));
117120
}
@@ -219,11 +222,7 @@ public static void MemoryPoolTryGetArray()
219222
unsafe
220223
{
221224
Assert.True(MemoryMarshal.TryGetArray(memory, out arraySegment));
222-
fixed (int* pArray = arraySegment.Array)
223-
{
224-
void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(memory.Span));
225-
Assert.Equal((IntPtr)pSpan, (IntPtr)pArray);
226-
}
225+
Assert.Equal(0, Unsafe.ByteOffset(ref MemoryMarshal.GetArrayDataReference(arraySegment.Array), ref MemoryMarshal.GetReference(memory.Span)));
227226
}
228227
}
229228
}

src/libraries/System.Memory/tests/ReadOnlySpan/AsSpan.cs

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ static unsafe void Validate(string text, int start, int length, ReadOnlySpan<cha
9191
Assert.Equal(length, span.Length);
9292
fixed (char* pText = text)
9393
{
94+
// Unsafe.AsPointer is safe here since it's pinned (since text and span should be the same string)
9495
char* expected = pText + start;
9596
void* actual = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
9697
Assert.Equal((IntPtr)expected, (IntPtr)actual);

src/libraries/System.Memory/tests/TestHelpers.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static unsafe void ValidateNonNullEmpty<T>(this Span<T> span)
3737
Assert.True(span.IsEmpty);
3838

3939
// Validate that empty Span is not normalized to null
40-
Assert.True(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)) != null);
40+
Assert.False(Unsafe.IsNullRef(ref MemoryMarshal.GetReference(span)));
4141
}
4242

4343
public delegate void AssertThrowsAction<T>(Span<T> span);
@@ -98,7 +98,7 @@ public static unsafe void ValidateNonNullEmpty<T>(this ReadOnlySpan<T> span)
9898
Assert.True(span.IsEmpty);
9999

100100
// Validate that empty Span is not normalized to null
101-
Assert.True(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)) != null);
101+
Assert.False(Unsafe.IsNullRef(ref MemoryMarshal.GetReference(span)));
102102
}
103103

104104
public delegate void AssertThrowsActionReadOnly<T>(ReadOnlySpan<T> span);

src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs

+8
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,14 @@ protected unsafe void WriteEvent(int eventId, long arg1, byte[]? arg2)
11201120
}
11211121
}
11221122

1123+
// Returns the object as a IntPtr - safe when only used for logging
1124+
internal static unsafe nint ObjectIDForEvents(object? o)
1125+
{
1126+
#pragma warning disable CS8500 // takes address of managed type
1127+
return *(nint*)&o;
1128+
#pragma warning restore CS8500
1129+
}
1130+
11231131
#pragma warning restore 1591
11241132

11251133
/// <summary>

src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/NativeRuntimeEventSource.Threading.NativeSinks.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ private void ContentionLockCreated(nint LockID, nint AssociatedObjectID, ushort
9494

9595
[NonEvent]
9696
[MethodImpl(MethodImplOptions.NoInlining)]
97-
public void ContentionLockCreated(Lock lockObj) => ContentionLockCreated(lockObj.LockIdForEvents, lockObj.ObjectIdForEvents);
97+
public void ContentionLockCreated(Lock lockObj) => ContentionLockCreated(lockObj.LockIdForEvents, ObjectIDForEvents(lockObj));
9898

9999
[Event(81, Level = EventLevel.Informational, Message = Messages.ContentionStart, Task = Tasks.Contention, Opcode = EventOpcode.Start, Version = 2, Keywords = Keywords.ContentionKeyword)]
100100
private void ContentionStart(
@@ -115,7 +115,7 @@ public void ContentionStart(Lock lockObj) =>
115115
ContentionFlagsMap.Managed,
116116
DefaultClrInstanceId,
117117
lockObj.LockIdForEvents,
118-
lockObj.ObjectIdForEvents,
118+
ObjectIDForEvents(lockObj),
119119
lockObj.OwningThreadId);
120120

121121
[Event(91, Level = EventLevel.Informational, Message = Messages.ContentionStop, Task = Tasks.Contention, Opcode = EventOpcode.Stop, Version = 1, Keywords = Keywords.ContentionKeyword)]
@@ -360,7 +360,7 @@ private void WaitHandleWaitStart(
360360
public unsafe void WaitHandleWaitStart(
361361
WaitHandleWaitSourceMap waitSource = WaitHandleWaitSourceMap.Unknown,
362362
object? associatedObject = null) =>
363-
WaitHandleWaitStart(waitSource, *(nint*)Unsafe.AsPointer(ref associatedObject));
363+
WaitHandleWaitStart(waitSource, ObjectIDForEvents(associatedObject));
364364

365365
[Event(302, Level = EventLevel.Verbose, Message = Messages.WaitHandleWaitStop, Task = Tasks.WaitHandleWait, Opcode = EventOpcode.Stop, Version = 0, Keywords = Keywords.WaitHandleKeyword)]
366366
public void WaitHandleWaitStop(ushort ClrInstanceID = DefaultClrInstanceId)

src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/NativeRuntimeEventSource.Threading.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private unsafe void ContentionLockCreated(nint LockID, nint AssociatedObjectID,
107107

108108
[NonEvent]
109109
[MethodImpl(MethodImplOptions.NoInlining)]
110-
public void ContentionLockCreated(Lock lockObj) => ContentionLockCreated(lockObj.LockIdForEvents, lockObj.ObjectIdForEvents);
110+
public void ContentionLockCreated(Lock lockObj) => ContentionLockCreated(lockObj.LockIdForEvents, ObjectIDForEvents(lockObj));
111111

112112
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "Parameters to this method are primitive and are trimmer safe")]
113113
[Event(81, Level = EventLevel.Informational, Message = Messages.ContentionStart, Task = Tasks.Contention, Opcode = EventOpcode.Start, Version = 2, Keywords = Keywords.ContentionKeyword)]
@@ -146,7 +146,7 @@ public void ContentionStart(Lock lockObj) =>
146146
ContentionFlagsMap.Managed,
147147
DefaultClrInstanceId,
148148
lockObj.LockIdForEvents,
149-
lockObj.ObjectIdForEvents,
149+
ObjectIDForEvents(lockObj),
150150
lockObj.OwningThreadId);
151151

152152
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "Parameters to this method are primitive and are trimmer safe")]
@@ -557,7 +557,7 @@ private unsafe void WaitHandleWaitStart(
557557
public unsafe void WaitHandleWaitStart(
558558
WaitHandleWaitSourceMap waitSource = WaitHandleWaitSourceMap.Unknown,
559559
object? associatedObject = null) =>
560-
WaitHandleWaitStart(waitSource, *(nint*)Unsafe.AsPointer(ref associatedObject));
560+
WaitHandleWaitStart(waitSource, ObjectIDForEvents(associatedObject));
561561

562562
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "Parameters to this method are primitive and are trimmer safe")]
563563
[Event(302, Level = EventLevel.Verbose, Message = Messages.WaitHandleWaitStop, Task = Tasks.WaitHandleWait, Opcode = EventOpcode.Stop, Version = 0, Keywords = Keywords.WaitHandleKeyword)]

0 commit comments

Comments
 (0)