diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index c12d6c854ac..d7ac59703f4 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -22,6 +22,7 @@ true true true + true diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs index 83287c4a4d0..d9f10f276ea 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -401,7 +401,11 @@ internal IEnumerator GetEnumerator() { // ! Warning: "this" is an array, not an SZArrayHelper. See comments above // ! or you may introduce a security hole! - T[] @this = Unsafe.As(this); + T[] @this; + unsafe + { + @this = Unsafe.As(this); + } int length = @this.Length; return length == 0 ? SZGenericArrayEnumerator.Empty : new SZGenericArrayEnumerator(@this, length); } @@ -411,7 +415,11 @@ private void CopyTo(T[] array, int index) // ! Warning: "this" is an array, not an SZArrayHelper. See comments above // ! or you may introduce a security hole! - T[] @this = Unsafe.As(this); + T[] @this; + unsafe + { + @this = Unsafe.As(this); + } Array.Copy(@this, 0, array, index, @this.Length); } @@ -419,7 +427,11 @@ internal int get_Count() { // ! Warning: "this" is an array, not an SZArrayHelper. See comments above // ! or you may introduce a security hole! - T[] @this = Unsafe.As(this); + T[] @this; + unsafe + { + @this = Unsafe.As(this); + } return @this.Length; } @@ -427,7 +439,11 @@ internal T get_Item(int index) { // ! Warning: "this" is an array, not an SZArrayHelper. See comments above // ! or you may introduce a security hole! - T[] @this = Unsafe.As(this); + T[] @this; + unsafe + { + @this = Unsafe.As(this); + } if ((uint)index >= (uint)@this.Length) { ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); @@ -440,7 +456,11 @@ internal void set_Item(int index, T value) { // ! Warning: "this" is an array, not an SZArrayHelper. See comments above // ! or you may introduce a security hole! - T[] @this = Unsafe.As(this); + T[] @this; + unsafe + { + @this = Unsafe.As(this); + } if ((uint)index >= (uint)@this.Length) { ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); @@ -459,7 +479,11 @@ private bool Contains(T value) { // ! Warning: "this" is an array, not an SZArrayHelper. See comments above // ! or you may introduce a security hole! - T[] @this = Unsafe.As(this); + T[] @this; + unsafe + { + @this = Unsafe.As(this); + } return Array.IndexOf(@this, value, 0, @this.Length) >= 0; } @@ -480,7 +504,11 @@ private int IndexOf(T value) { // ! Warning: "this" is an array, not an SZArrayHelper. See comments above // ! or you may introduce a security hole! - T[] @this = Unsafe.As(this); + T[] @this; + unsafe + { + @this = Unsafe.As(this); + } return Array.IndexOf(@this, value, 0, @this.Length); } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index 358dab7f436..3f6a1139349 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -435,7 +435,11 @@ private bool BindToMethodInfo(object? target, IRuntimeMethodInfo method, Runtime private static MulticastDelegate InternalAlloc(RuntimeType type) { Debug.Assert(type.IsAssignableTo(typeof(MulticastDelegate))); - return Unsafe.As(RuntimeTypeHandle.InternalAlloc(type)); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + return Unsafe.As(RuntimeTypeHandle.InternalAlloc(type)); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs index 1c9a4c6c384..dd7408950c7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs @@ -55,7 +55,11 @@ public sealed override bool Equals([NotNullWhen(true)] object? obj) // the types are the same, obj should also be a // MulticastDelegate Debug.Assert(obj is MulticastDelegate, "Shouldn't have failed here since we already checked the types are the same!"); - MulticastDelegate d = Unsafe.As(obj); + MulticastDelegate d; + unsafe + { + d = Unsafe.As(obj); + } if (_invocationCount != 0) { @@ -165,10 +169,14 @@ private static bool TrySetSlot(object?[] a, int index, object o) return false; } - private unsafe MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready) + private MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready) { // First, allocate a new multicast delegate just like this one, i.e. same type as the this object - MulticastDelegate result = Unsafe.As(RuntimeTypeHandle.InternalAllocNoChecks(RuntimeHelpers.GetMethodTable(this))); + MulticastDelegate result; + unsafe + { + result = Unsafe.As(RuntimeTypeHandle.InternalAllocNoChecks(RuntimeHelpers.GetMethodTable(this))); + } // Performance optimization - if this already points to a true multicast delegate, // copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index b64899d34bd..b11140b98be 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -2407,7 +2407,9 @@ private static bool FilterApplyMethodBase( private readonly object m_keepalive; // This will be filled with a LoaderAllocator reference when this RuntimeType represents a collectible type #pragma warning restore CS0169 #pragma warning restore CA1823 - private IntPtr m_cache; + // Must be a handle to a RuntimeTypeCache type + [RequiresUnsafe] + private unsafe IntPtr m_cache; internal IntPtr m_handle; internal static readonly RuntimeType ValueType = (RuntimeType)typeof(ValueType); @@ -2448,7 +2450,10 @@ private RuntimeTypeCache? CacheIfExists { object? cache = GCHandle.InternalGet(m_cache); Debug.Assert(cache == null || cache is RuntimeTypeCache); - return Unsafe.As(cache); + unsafe + { + return Unsafe.As(cache); + } } return null; } @@ -2465,7 +2470,10 @@ private RuntimeTypeCache Cache if (cache != null) { Debug.Assert(cache is RuntimeTypeCache); - return Unsafe.As(cache); + unsafe + { + return Unsafe.As(cache); + } } } return InitializeCache(); diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 33938989e38..1c0841e0fe9 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -50,6 +50,7 @@ annotations true + true diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d3b69efe615..a312398fab0 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -286,6 +286,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/SharedArrayPool.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/SharedArrayPool.cs index ad4488005da..a02e52f480e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/SharedArrayPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/SharedArrayPool.cs @@ -60,7 +60,11 @@ public override T[] Rent(int minimumLength) SharedArrayPoolThreadLocalArray[]? tlsBuckets = t_tlsBuckets; if (tlsBuckets is not null && (uint)bucketIndex < (uint)tlsBuckets.Length) { - buffer = Unsafe.As(tlsBuckets[bucketIndex].Array); + unsafe + { + // Array should always be an array of T[] + buffer = Unsafe.As(tlsBuckets[bucketIndex].Array); + } if (buffer is not null) { tlsBuckets[bucketIndex].Array = null; @@ -79,7 +83,10 @@ public override T[] Rent(int minimumLength) SharedArrayPoolPartitions? b = perCoreBuckets[bucketIndex]; if (b is not null) { - buffer = Unsafe.As(b.TryPop()); + unsafe + { + buffer = Unsafe.As(b.TryPop()); + } if (buffer is not null) { if (log.IsEnabled()) @@ -302,7 +309,9 @@ private SharedArrayPoolThreadLocalArray[] InitializeTlsBucketsAndTrimming() internal struct SharedArrayPoolThreadLocalArray { /// The stored array. - public Array? Array; + // Must be an array of T[] at runtime + [RequiresUnsafe] + public unsafe Array? Array; /// Environment.TickCount timestamp for when this array was observed by Trim. public int MillisecondsTimeStamp; @@ -390,6 +399,8 @@ private sealed class Partition private int _millisecondsTimestamp; [MethodImpl(MethodImplOptions.AggressiveInlining)] + // Array parameter must be a T[] + [RequiresUnsafe] public bool TryPush(Array array) { bool enqueued = false; diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentDictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentDictionary.cs index 74fafc9c8cb..b60df326abf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentDictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentDictionary.cs @@ -2258,7 +2258,9 @@ internal Node(TKey key, TValue value, int hashcode, Node? next) private sealed class Tables { /// The comparer to use for lookups in the tables. - internal readonly IEqualityComparer? _comparer; + // Must be IAlternateEqualityComparer + [RequiresUnsafe] + internal readonly unsafe IEqualityComparer? _comparer; /// A singly-linked list for each bucket. internal readonly VolatileNode[] _buckets; /// Pre-computed multiplier for use on 64-bit performing faster modulo operations. @@ -2317,6 +2319,7 @@ private static bool IsCompatibleKey(ConcurrentDictionaryGets the dictionary's alternate comparer. The dictionary must have already been verified as compatible. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [RequiresUnsafe] private static IAlternateEqualityComparer GetAlternateComparer(ConcurrentDictionary.Tables tables) where TAlternateKey : notnull, allows ref struct { diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index f6b9e545c40..e6da6ced992 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -32,7 +32,9 @@ public class Dictionary : IDictionary, IDictionary, private int _freeList; private int _freeCount; private int _version; - private IEqualityComparer? _comparer; + // Must be IAlternateEqualityComparer + [RequiresUnsafe] + private unsafe IEqualityComparer? _comparer; private KeyCollection? _keys; private ValueCollection? _values; private const int StartOfFreeList = -3; @@ -734,6 +736,7 @@ internal static bool IsCompatibleKey(Dictionary dictionary) /// Gets the dictionary's alternate comparer. The dictionary must have already been verified as compatible. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [RequiresUnsafe] internal static IAlternateEqualityComparer GetAlternateComparer(Dictionary dictionary) { Debug.Assert(IsCompatibleKey(dictionary)); diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs index b7a30cbd904..8b06ab95509 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs @@ -46,7 +46,9 @@ public class HashSet : ICollection, ISet, IReadOnlyCollection, IRead private int _freeList; private int _freeCount; private int _version; - private IEqualityComparer? _comparer; + // Must be IAlternateEqualityComparer + [RequiresUnsafe] + private unsafe IEqualityComparer? _comparer; #region Constructors @@ -441,6 +443,7 @@ internal static bool IsCompatibleItem(HashSet set) /// Gets the set's alternate comparer. The set must have already been verified as compatible. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [RequiresUnsafe] internal static IAlternateEqualityComparer GetAlternateComparer(HashSet set) { Debug.Assert(IsCompatibleItem(set)); diff --git a/src/libraries/System.Private.CoreLib/src/System/ComAwareWeakReference.cs b/src/libraries/System.Private.CoreLib/src/System/ComAwareWeakReference.cs index 404bc41c5a6..e7b741c9583 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ComAwareWeakReference.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ComAwareWeakReference.cs @@ -112,7 +112,11 @@ private void SetTarget(object? target, ComInfo? comInfo) if (_comInfo != null) { // Check if the target is still null - target = Unsafe.As(GCHandle.InternalGet(_weakHandle)); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + target = Unsafe.As(GCHandle.InternalGet(_weakHandle)); + } if (target == null) { // Resolve and reset. Perform runtime cast to catch bugs @@ -146,22 +150,40 @@ private static ComAwareWeakReference EnsureComAwareReference(ref nint taggedHand GC.SuppressFinalize(newRef); } - return Unsafe.As(GCHandle.InternalGet(taggedHandle & ~HandleTagBits)!); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + return Unsafe.As(GCHandle.InternalGet(taggedHandle & ~HandleTagBits)!); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ComAwareWeakReference GetFromTaggedReference(nint taggedHandle) { Debug.Assert((taggedHandle & ComAwareBit) != 0); - return Unsafe.As(GCHandle.InternalGet(taggedHandle & ~HandleTagBits)!); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + return Unsafe.As(GCHandle.InternalGet(taggedHandle & ~HandleTagBits)!); + } } [MethodImpl(MethodImplOptions.NoInlining)] internal static void SetTarget(ref nint taggedHandle, object? target, ComInfo? comInfo) { - ComAwareWeakReference comAwareRef = comInfo != null ? - EnsureComAwareReference(ref taggedHandle) : - Unsafe.As(GCHandle.InternalGet(taggedHandle & ~HandleTagBits)!); + ComAwareWeakReference comAwareRef; + if (comInfo != null) + { + comAwareRef = EnsureComAwareReference(ref taggedHandle); + } + else + { + // FIXME: review unsafe to confirm correct annotation + unsafe + { + comAwareRef = Unsafe.As(GCHandle.InternalGet(taggedHandle & ~HandleTagBits)!); + } + } comAwareRef.SetTarget(target, comInfo); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs index 6efa9f48554..7d99147dcef 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs @@ -78,7 +78,17 @@ public abstract partial class Delegate : ICloneable, ISerializable /// Gets a value that indicates whether the has a single invocation target. /// /// true if the has a single invocation target. - public bool HasSingleTarget => Unsafe.As(this).HasSingleTarget; + public bool HasSingleTarget + { + get + { + // FIXME: review unsafe to confirm correct annotation + unsafe + { + return Unsafe.As(this).HasSingleTarget; + } + } + } #endif /// @@ -94,7 +104,13 @@ public abstract partial class Delegate : ICloneable, ISerializable /// The method returns an empty enumerator for null delegate. /// public static System.Delegate.InvocationListEnumerator EnumerateInvocationList(TDelegate? d) where TDelegate : System.Delegate - => new InvocationListEnumerator(Unsafe.As(d)); + { + // FIXME: review unsafe to confirm correct annotation + unsafe + { + return new InvocationListEnumerator(Unsafe.As(d)); + } + } /// /// Provides an enumerator for the invocation list of a delegate. @@ -128,9 +144,13 @@ public TDelegate Current public bool MoveNext() { int index = _index + 1; - if ((_current = Unsafe.As(_delegate?.TryGetAt(index))) == null) + // FIXME: review unsafe to confirm correct annotation + unsafe { - return false; + if ((_current = Unsafe.As(_delegate?.TryGetAt(index))) == null) + { + return false; + } } _index = index; return true; diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/RequiresUnsafeAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/RequiresUnsafeAttribute.cs new file mode 100644 index 00000000000..589ff1bbf57 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/RequiresUnsafeAttribute.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that the specified method requires dynamic access to code that is not referenced + /// statically, for example through . + /// + /// + /// This allows tools to understand which methods are unsafe to call when removing unreferenced + /// code from an application. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Field, Inherited = false)] + internal sealed class RequiresUnsafeAttribute : Attribute + { + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs index 5a8bba6a6d2..766f246f0ee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs @@ -16,7 +16,9 @@ public class BinaryReader : IDisposable { private const int MaxCharBytesSize = 128; - private readonly Stream _stream; + // If _isMemoryStream is true, this must be a MemoryStream + [RequiresUnsafe] + private readonly unsafe Stream _stream; private readonly Encoding _encoding; private Decoder? _decoder; private char[]? _charBuffer; @@ -357,7 +359,11 @@ private int InternalReadChars(Span buffer) if (_isMemoryStream) { Debug.Assert(_stream is MemoryStream); - MemoryStream mStream = Unsafe.As(_stream); + MemoryStream mStream; + unsafe + { + mStream = Unsafe.As(_stream); + } int position = mStream.InternalGetPosition(); numBytes = mStream.InternalEmulateRead(numBytes); @@ -477,7 +483,12 @@ private ReadOnlySpan InternalRead(Span buffer) { // read directly from MemoryStream buffer Debug.Assert(_stream is MemoryStream); - return Unsafe.As(_stream).InternalReadSpan(buffer.Length); + MemoryStream mStream; + unsafe + { + mStream = Unsafe.As(_stream); + } + return mStream.InternalReadSpan(buffer.Length); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs index 147e3815812..867db2a2952 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs @@ -70,9 +70,12 @@ internal static unsafe long ReadScatterAtOffset(SafeFileHandle handle, IReadOnly for (int i = 0; i < buffersCount; i++) { Memory buffer = buffers[i]; - MemoryHandle memoryHandle = buffer.Pin(); - vectors[i] = new Interop.Sys.IOVector { Base = (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length }; - handles[i] = memoryHandle; + unsafe + { + MemoryHandle memoryHandle = buffer.Pin(); + vectors[i] = new Interop.Sys.IOVector { Base = (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length }; + handles[i] = memoryHandle; + } } fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors)) diff --git a/src/libraries/System.Private.CoreLib/src/System/Memory.cs b/src/libraries/System.Private.CoreLib/src/System/Memory.cs index c8eac110cb5..6133a373d59 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Memory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Memory.cs @@ -284,8 +284,11 @@ public Span Span { // Special-case string since it's the most common for ROM. - refToReturn = ref Unsafe.As(ref ((string)tmpObject).GetRawStringData()); - lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; + unsafe + { + refToReturn = ref Unsafe.As(ref ((string)tmpObject).GetRawStringData()); + lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; + } } else if (RuntimeHelpers.ObjectHasComponentSize(tmpObject)) { @@ -301,8 +304,13 @@ public Span Span // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible Debug.Assert(tmpObject is T[]); - refToReturn = ref MemoryMarshal.GetArrayDataReference(Unsafe.As(tmpObject)); - lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; + T[] array; + unsafe + { + array = Unsafe.As(tmpObject); + } + refToReturn = ref MemoryMarshal.GetArrayDataReference(array); + lengthOfUnderlyingSpan = array.Length; } else { @@ -313,7 +321,12 @@ public Span Span // constructor or other public API which would allow such a conversion. Debug.Assert(tmpObject is MemoryManager); - Span memoryManagerSpan = Unsafe.As>(tmpObject).GetSpan(); + MemoryManager manager; + unsafe + { + manager = Unsafe.As>(tmpObject); + } + Span memoryManagerSpan = manager.GetSpan(); refToReturn = ref MemoryMarshal.GetReference(memoryManagerSpan); lengthOfUnderlyingSpan = memoryManagerSpan.Length; } @@ -379,6 +392,11 @@ public Span Span /// /// An instance with nonprimitive (non-blittable) members cannot be pinned. /// + /// + /// To use this method safely, the target must not be torn while the + /// returned is in use. + /// + [RequiresUnsafe] public unsafe MemoryHandle Pin() { // Just like the Span property getter, we have special support for a mutable Memory diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 9687896e6fc..eefa666bd09 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -5905,7 +5905,11 @@ internal SpanSplitEnumerator(ReadOnlySpan source, ReadOnlySpan separators) _source = source; if (typeof(T) == typeof(char) && separators.Length == 0) { - _searchValues = Unsafe.As>(string.SearchValuesStorage.WhiteSpaceChars); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + _searchValues = Unsafe.As>(string.SearchValuesStorage.WhiteSpaceChars); + } _splitMode = SpanSplitEnumeratorMode.SearchValues; } else diff --git a/src/libraries/System.Private.CoreLib/src/System/ReadOnlyMemory.cs b/src/libraries/System.Private.CoreLib/src/System/ReadOnlyMemory.cs index 85e4feace8c..80cb8e9a92a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ReadOnlyMemory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ReadOnlyMemory.cs @@ -206,8 +206,11 @@ public ReadOnlySpan Span { // Special-case string since it's the most common for ROM. - refToReturn = ref Unsafe.As(ref ((string)tmpObject).GetRawStringData()); - lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; + unsafe + { + refToReturn = ref Unsafe.As(ref ((string)tmpObject).GetRawStringData()); + lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; + } } else if (RuntimeHelpers.ObjectHasComponentSize(tmpObject)) { @@ -223,8 +226,11 @@ public ReadOnlySpan Span // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible Debug.Assert(tmpObject is T[]); - refToReturn = ref MemoryMarshal.GetArrayDataReference(Unsafe.As(tmpObject)); - lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; + unsafe + { + refToReturn = ref MemoryMarshal.GetArrayDataReference(Unsafe.As(tmpObject)); + lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; + } } else { @@ -235,9 +241,12 @@ public ReadOnlySpan Span // constructor or other public API which would allow such a conversion. Debug.Assert(tmpObject is MemoryManager); - Span memoryManagerSpan = Unsafe.As>(tmpObject).GetSpan(); - refToReturn = ref MemoryMarshal.GetReference(memoryManagerSpan); - lengthOfUnderlyingSpan = memoryManagerSpan.Length; + unsafe + { + Span memoryManagerSpan = Unsafe.As>(tmpObject).GetSpan(); + refToReturn = ref MemoryMarshal.GetReference(memoryManagerSpan); + lengthOfUnderlyingSpan = memoryManagerSpan.Length; + } } // If the Memory or ReadOnlyMemory instance is torn, this property getter has undefined behavior. @@ -322,25 +331,36 @@ public unsafe MemoryHandle Pin() // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible Debug.Assert(tmpObject is T[]); + T[] array; + unsafe + { + array = Unsafe.As(tmpObject); + } + // Array is already pre-pinned if (_index < 0) { // Unsafe.AsPointer is safe since it's pinned - void* pointer = Unsafe.Add(Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(tmpObject))), _index & RemoveFlagsBitMask); + void* pointer = Unsafe.Add(Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(array)), _index & RemoveFlagsBitMask); return new MemoryHandle(pointer); } else { // Unsafe.AsPointer is safe since the handle pins it GCHandle handle = GCHandle.Alloc(tmpObject, GCHandleType.Pinned); - void* pointer = Unsafe.Add(Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(tmpObject))), _index); + void* pointer = Unsafe.Add(Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(array)), _index); return new MemoryHandle(pointer, handle); } } else { Debug.Assert(tmpObject is MemoryManager); - return Unsafe.As>(tmpObject).Pin(_index); + MemoryManager manager; + unsafe + { + manager = Unsafe.As>(tmpObject); + } + return manager.Pin(_index); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs index bdbb94609cd..d782e7203d6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs @@ -631,7 +631,11 @@ internal bool TryGetValueWorker(TKey key, [MaybeNullWhen(false)] out TValue valu Debug.Assert(key != null); // Key already validated as non-null int entryIndex = FindEntry(key, out object? secondary); - value = Unsafe.As(secondary); + // FIXME: Validate the unsafe block + unsafe + { + value = Unsafe.As(secondary); + } return entryIndex != -1; } @@ -682,8 +686,12 @@ internal bool TryGetEntry(int index, [NotNullWhen(true)] out TKey? key, [MaybeNu if (oKey != null) { - key = Unsafe.As(oKey); - value = Unsafe.As(oValue!); + // FIXME: Validate the unsafe block + unsafe + { + key = Unsafe.As(oKey); + value = Unsafe.As(oValue!); + } return true; } } @@ -711,7 +719,11 @@ internal bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value) if (entryIndex != -1) { RemoveIndex(entryIndex); - value = Unsafe.As(valueObject!); + // FIXME: Validate the unsafe block + unsafe + { + value = Unsafe.As(valueObject!); + } return true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index 1700fb0cca8..df22c760338 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs @@ -60,7 +60,13 @@ public void OnCompleted(Action continuation) } else if (obj != null) { - Unsafe.As(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + // obj is either Task, null, or IValueTaskSource and the other cases are checked + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + source.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.FlowExecutionContext | (_value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None)); } @@ -82,7 +88,13 @@ public void UnsafeOnCompleted(Action continuation) } else if (obj != null) { - Unsafe.As(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + // obj is either Task, null, or IValueTaskSource and the other cases are checked + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + source.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, _value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); } else @@ -102,7 +114,13 @@ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox b } else if (obj != null) { - Unsafe.As(obj).OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token, + // obj is either Task, null, or IValueTaskSource and the other cases are checked + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + source.OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token, _value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); } else @@ -165,7 +183,12 @@ public void OnCompleted(Action continuation) } else if (obj != null) { - Unsafe.As>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + source.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.FlowExecutionContext | (_value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None)); } @@ -187,7 +210,12 @@ public void UnsafeOnCompleted(Action continuation) } else if (obj != null) { - Unsafe.As>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + source.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, _value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); } else @@ -207,7 +235,12 @@ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox b } else if (obj != null) { - Unsafe.As>(obj).OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token, + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + source.OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token, _value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); } else diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 83e401940e4..091387459e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -51,7 +51,10 @@ public static T[] GetSubArray(T[] array, Range range) // an array of the exact same backing type. The cast to T[] will // never fail. - dest = Unsafe.As(Array.CreateInstanceFromArrayType(array.GetType(), length)); + unsafe + { + dest = Unsafe.As(Array.CreateInstanceFromArrayType(array.GetType(), length)); + } } // In either case, the newly-allocated array is the exact same type as the diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs index 154cd64a421..09e39599450 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs @@ -63,6 +63,7 @@ public static int SizeOf() // Mono:As [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] + [RequiresUnsafe] [return: NotNullIfNotNull(nameof(o))] public static T? As(object? o) where T : class? { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index c242ad3f2ad..78200ab3ad6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ValueTaskAwaiter.cs @@ -55,7 +55,12 @@ public void OnCompleted(Action continuation) } else if (obj != null) { - Unsafe.As(obj).OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + source.OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); } else { @@ -75,7 +80,12 @@ public void UnsafeOnCompleted(Action continuation) } else if (obj != null) { - Unsafe.As(obj).OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + source.OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); } else { @@ -94,7 +104,12 @@ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox b } else if (obj != null) { - Unsafe.As(obj).OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + source.OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); } else { @@ -137,7 +152,12 @@ public void OnCompleted(Action continuation) } else if (obj != null) { - Unsafe.As>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + source.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); } else { @@ -157,7 +177,12 @@ public void UnsafeOnCompleted(Action continuation) } else if (obj != null) { - Unsafe.As>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + source.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); } else { @@ -176,7 +201,12 @@ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox b } else if (obj != null) { - Unsafe.As>(obj).OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + source.OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs index f1ce1193e78..fb1f76071a2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -129,6 +129,7 @@ public partial struct ComInterfaceDispatch /// Desired type. /// Pointer supplied to Vtable function entry. /// Instance of type associated with dispatched function call. + [RequiresUnsafe] public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class { ManagedObjectWrapper* comInstance = ToManagedObjectWrapper(dispatchPtr); @@ -218,6 +219,7 @@ public bool IsRooted } } + [RequiresUnsafe] public ManagedObjectWrapperHolder? Holder { get @@ -226,7 +228,9 @@ public ManagedObjectWrapperHolder? Holder if (handle == IntPtr.Zero) return null; else + { return Unsafe.As(GCHandle.FromIntPtr(handle).Target); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs index 7aa92f99534..108898188c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs @@ -24,6 +24,7 @@ public struct GCHandle : IEquatable>, IDisposable where T : class? { // The actual integer handle value that the EE uses internally. + [RequiresUnsafe] private IntPtr _handle; /// @@ -49,7 +50,10 @@ public readonly T Target IntPtr handle = _handle; GCHandle.CheckUninitialized(handle); // Skip the type check to provide lowest overhead. - return Unsafe.As(GCHandle.InternalGet(handle)!); + unsafe + { + return Unsafe.As(GCHandle.InternalGet(handle)!); + } } set { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/PointerArrayMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/PointerArrayMarshaller.cs index d0a3dfae3e8..6823617d2f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/PointerArrayMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/PointerArrayMarshaller.cs @@ -50,7 +50,13 @@ public static unsafe class PointerArrayMarshaller /// The managed array to get a source for. /// The containing the managed elements to marshal. public static ReadOnlySpan GetManagedValuesSource(T*[]? managed) - => Unsafe.As(managed); + { + // IntPtr[] and T*[] have the same representation + unsafe + { + return Unsafe.As(managed); + } + } /// /// Gets a destination for the unmanaged elements in the array. @@ -86,7 +92,13 @@ public static Span GetUnmanagedValuesDestination(TUnmanagedEl /// The managed array to get a destination for. /// The of managed elements. public static Span GetManagedValuesDestination(T*[]? managed) - => Unsafe.As(managed); + { + // IntPtr[] and T*[] have the same representation + unsafe + { + return Unsafe.As(managed); + } + } /// /// Gets a source for the unmanaged elements in the array. @@ -166,7 +178,14 @@ public void FromManaged(T*[]? array, Span buffer) /// Returns a span that points to the memory where the managed values of the array are stored. /// /// A span over managed values of the array. - public ReadOnlySpan GetManagedValuesSource() => Unsafe.As(_managedArray); + public ReadOnlySpan GetManagedValuesSource() + { + // IntPtr[] and T*[] have the same representation + unsafe + { + return Unsafe.As(_managedArray); + } + } /// /// Returns a span that points to the memory where the unmanaged values of the array should be stored. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.cs index 15e8b05dd5c..a6de7e2b709 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.cs @@ -285,9 +285,14 @@ public static bool TryGetArray(ReadOnlyMemory memory, out ArraySegment // or a U[] which is blittable to a T[] (e.g., int[] and uint[]). // The array may be prepinned, so remove the high bit from the start index in the line below. - // The ArraySegment ctor will perform bounds checking on index & length. - segment = new ArraySegment(Unsafe.As(obj), index & ReadOnlyMemory.RemoveFlagsBitMask, length); + T[] array; + unsafe + { + array = Unsafe.As(obj); + } + // The ArraySegment ctor will perform bounds checking on index & length. + segment = new ArraySegment(array, index & ReadOnlyMemory.RemoveFlagsBitMask, length); return true; } else @@ -296,7 +301,12 @@ public static bool TryGetArray(ReadOnlyMemory memory, out ArraySegment // is MemoryManager. The ArraySegment ctor will perform bounds checking on index & length. Debug.Assert(obj is MemoryManager); - if (Unsafe.As>(obj).TryGetArray(out ArraySegment tempArraySegment)) + MemoryManager manager; + unsafe + { + manager = Unsafe.As>(obj); + } + if (manager.TryGetArray(out ArraySegment tempArraySegment)) { segment = new ArraySegment(tempArraySegment.Array!, tempArraySegment.Offset + index, length); return true; @@ -408,7 +418,11 @@ static IEnumerable FromString(string s, int offset, int count) // enumerable. Otherwise, return an iterator dedicated to enumerating the object. if (RuntimeHelpers.ObjectHasComponentSize(obj)) // Same check as in TryGetArray to confirm that obj is a T[] or a U[] which is blittable to a T[]. { - T[] array = Unsafe.As(obj); + T[] array; + unsafe + { + array = Unsafe.As(obj); + } index &= ReadOnlyMemory.RemoveFlagsBitMask; // the array may be prepinned, so remove the high bit from the start index in the line below. return index == 0 && length == array.Length ? array : diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs index 9f2434af1ae..546fcf5f057 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs @@ -25,6 +25,7 @@ public struct PinnedGCHandle : IEquatable>, IDisposable where T : class? { // The actual integer handle value that the EE uses internally. + [RequiresUnsafe] private IntPtr _handle; /// @@ -51,7 +52,10 @@ public readonly T Target IntPtr handle = _handle; GCHandle.CheckUninitialized(handle); // Skip the type check to provide lowest overhead. - return Unsafe.As(GCHandle.InternalGet(handle)!); + unsafe + { + return Unsafe.As(GCHandle.InternalGet(handle)!); + } } set { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs index 4243caf0097..a2e3b3beb8d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs @@ -72,7 +72,11 @@ internal static void ReleaseExternalObjectsFromCurrentThread() { foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) { - ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); + ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper; + unsafe + { + nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); + } if (nativeObjectWrapper != null && nativeObjectWrapper._contextToken == contextToken) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs index 348dfc7657d..1e612d78e85 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs @@ -24,6 +24,7 @@ public struct WeakGCHandle : IEquatable>, IDisposable where T : class? { // The actual integer handle value that the EE uses internally. + [RequiresUnsafe] private IntPtr _handle; /// @@ -53,7 +54,11 @@ public readonly bool TryGetTarget([NotNullWhen(true)] out T? target) IntPtr handle = _handle; GCHandle.CheckUninitialized(handle); // Skip the type check to provide lowest overhead. - T? obj = Unsafe.As(GCHandle.InternalGet(handle)); + T? obj; + unsafe + { + obj = Unsafe.As(GCHandle.InternalGet(handle)); + } target = obj; return obj != null; } diff --git a/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/AsciiStringSearchValuesTeddyBase.cs b/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/AsciiStringSearchValuesTeddyBase.cs index b764e3a22d8..2cb026919f9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/AsciiStringSearchValuesTeddyBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/AsciiStringSearchValuesTeddyBase.cs @@ -560,11 +560,14 @@ private bool TryFindMatch(ReadOnlySpan span, ref char searchSpace, Vector1 object? bucket = _buckets[candidateOffset]; Debug.Assert(bucket is not null); - if (TBucketized.Value - ? StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket)) - : StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket))) + unsafe { - return true; + if (TBucketized.Value + ? StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket)) + : StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket))) + { + return true; + } } candidateMask = BitOperations.ResetLowestSetBit(candidateMask); @@ -605,11 +608,14 @@ private bool TryFindMatch(ReadOnlySpan span, ref char searchSpace, Vector2 object? bucket = _buckets[candidateOffset]; Debug.Assert(bucket is not null); - if (TBucketized.Value - ? StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket)) - : StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket))) + unsafe { - return true; + if (TBucketized.Value + ? StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket)) + : StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket))) + { + return true; + } } candidateMask = BitOperations.ResetLowestSetBit(candidateMask); @@ -650,11 +656,14 @@ private bool TryFindMatch(ReadOnlySpan span, ref char searchSpace, Vector5 object? bucket = _buckets[candidateOffset]; Debug.Assert(bucket is not null); - if (TBucketized.Value - ? StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket)) - : StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket))) + unsafe { - return true; + if (TBucketized.Value + ? StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket)) + : StartsWith(ref matchRef, lengthRemaining, Unsafe.As(bucket))) + { + return true; + } } candidateMask = BitOperations.ResetLowestSetBit(candidateMask); diff --git a/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/AhoCorasickNode.cs b/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/AhoCorasickNode.cs index d2f2d9de6f0..aedaeb7fe50 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/AhoCorasickNode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/AhoCorasickNode.cs @@ -19,6 +19,8 @@ internal struct AhoCorasickNode // We save 1 child separately to avoid allocating a separate collection in such cases. private int _firstChildChar; private int _firstChildIndex; + // Must be int[] or Dictionary + [RequiresUnsafe] private object _children; // Either int[] or Dictionary public AhoCorasickNode() @@ -53,7 +55,12 @@ public readonly bool TryGetChild(char c, out int index) } else { - return Unsafe.As>(children).TryGetValue(c, out index); + Dictionary dict; + unsafe + { + dict = Unsafe.As>(children); + } + return dict.TryGetValue(c, out index); } index = 0; diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index 7acc23d53c6..1a93f9c7a24 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -947,7 +947,12 @@ private static string JoinCore(ReadOnlySpan separator, IEnumerable v // and string.Concat(IEnumerable) can be used as an efficient // enumerable-based equivalent of new string(char[]). - IEnumerator en = Unsafe.As>(e); + IEnumerator en; + // FIXME: Consider if (e is IEnumerator en) instead + unsafe + { + en = Unsafe.As>(e); + } char c = en.Current; // save the first value if (!en.MoveNext()) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs index affc292913f..967301a1eaa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @@ -10,6 +10,7 @@ namespace System.Threading { + [RequiresUnsafe] public delegate void ContextCallback(object? state); internal delegate void ContextCallback(ref TState state); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index 1cd3b2e3b54..d8687efb355 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -2378,11 +2378,16 @@ private void ExecuteWithThreadLocal(ref Task? currentTaskSlot, Thread? threadPoo } } + [RequiresUnsafe] private static readonly ContextCallback s_ecCallback = obj => { Debug.Assert(obj is Task); // Only used privately to pass directly to EC.Run - Unsafe.As(obj).InnerInvoke(); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + Unsafe.As(obj).InnerInvoke(); + } }; /// @@ -6743,7 +6748,10 @@ private static Task WhenAny(IEnumerable tasks) where TTask // since if argument was strongly-typed as an array, it would have bound to the array-based overload. if (tasks.GetType() == typeof(List)) { - return WhenAnyCore((ReadOnlySpan)CollectionsMarshal.AsSpan(Unsafe.As>(tasks))); + unsafe + { + return WhenAnyCore((ReadOnlySpan)CollectionsMarshal.AsSpan(Unsafe.As>(tasks))); + } } if (tasks is TTask[] tasksAsArray) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index 90f86c68159..a8f771612f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -59,7 +59,9 @@ namespace System.Threading.Tasks private static volatile Task? s_canceledTask; /// null if representing a successful synchronous completion, otherwise a or a . - internal readonly object? _obj; + // Must be either a Task or IValueTaskSource + [RequiresUnsafe] + internal readonly unsafe object? _obj; /// Opaque value passed through to the . internal readonly short _token; /// true to continue on the captured context; otherwise, false. @@ -173,10 +175,20 @@ public Task AsTask() { object? obj = _obj; Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); - return - obj == null ? Task.CompletedTask : - obj as Task ?? - GetTaskForValueTaskSource(Unsafe.As(obj)); + if (obj == null) + { + return Task.CompletedTask; + } + if (obj is Task t) + { + return t; + } + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + return GetTaskForValueTaskSource(source); } /// Gets a that may be used at any point in the future. @@ -310,7 +322,12 @@ public bool IsCompleted return t.IsCompleted; } - return Unsafe.As(obj).GetStatus(_token) != ValueTaskSourceStatus.Pending; + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + return source.GetStatus(_token) != ValueTaskSourceStatus.Pending; } } @@ -333,7 +350,12 @@ public bool IsCompletedSuccessfully return t.IsCompletedSuccessfully; } - return Unsafe.As(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded; + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + return source.GetStatus(_token) == ValueTaskSourceStatus.Succeeded; } } @@ -355,7 +377,12 @@ public bool IsFaulted return t.IsFaulted; } - return Unsafe.As(obj).GetStatus(_token) == ValueTaskSourceStatus.Faulted; + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + return source.GetStatus(_token) == ValueTaskSourceStatus.Faulted; } } @@ -382,7 +409,12 @@ public bool IsCanceled return t.IsCanceled; } - return Unsafe.As(obj).GetStatus(_token) == ValueTaskSourceStatus.Canceled; + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + return source.GetStatus(_token) == ValueTaskSourceStatus.Canceled; } } @@ -401,7 +433,12 @@ internal void ThrowIfCompletedUnsuccessfully() } else { - Unsafe.As(obj).GetResult(_token); + IValueTaskSource source; + unsafe + { + source = Unsafe.As(obj); + } + source.GetResult(_token); } } } @@ -464,7 +501,9 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContex /// A task canceled using `new CancellationToken(true)`. Lazily created only when first needed. private static volatile Task? s_canceledTask; /// null if has the result, otherwise a or a . - internal readonly object? _obj; + // Must be either a Task or IValueTaskSource + [RequiresUnsafe] + internal readonly unsafe object? _obj; /// The result to be used if the operation completed successfully synchronously. internal readonly TResult? _result; /// Opaque value passed through to the . @@ -585,7 +624,12 @@ public Task AsTask() return t; } - return GetTaskForValueTaskSource(Unsafe.As>(obj)); + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + return GetTaskForValueTaskSource(source); } /// Gets a that may be used at any point in the future. @@ -717,7 +761,12 @@ public bool IsCompleted return t.IsCompleted; } - return Unsafe.As>(obj).GetStatus(_token) != ValueTaskSourceStatus.Pending; + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + return source.GetStatus(_token) != ValueTaskSourceStatus.Pending; } } @@ -740,7 +789,12 @@ public bool IsCompletedSuccessfully return t.IsCompletedSuccessfully; } - return Unsafe.As>(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded; + IValueTaskSource source; + unsafe + { + source = Unsafe.As>(obj); + } + return source.GetStatus(_token) == ValueTaskSourceStatus.Succeeded; } } @@ -762,7 +816,10 @@ public bool IsFaulted return t.IsFaulted; } - return Unsafe.As>(obj).GetStatus(_token) == ValueTaskSourceStatus.Faulted; + unsafe + { + return Unsafe.As>(obj).GetStatus(_token) == ValueTaskSourceStatus.Faulted; + } } } @@ -789,7 +846,10 @@ public bool IsCanceled return t.IsCanceled; } - return Unsafe.As>(obj).GetStatus(_token) == ValueTaskSourceStatus.Canceled; + unsafe + { + return Unsafe.As>(obj).GetStatus(_token) == ValueTaskSourceStatus.Canceled; + } } } @@ -814,7 +874,10 @@ public TResult Result return t.ResultOnSuccess; } - return Unsafe.As>(obj).GetResult(_token); + unsafe + { + return Unsafe.As>(obj).GetResult(_token); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs index 29cf2dce305..7528cdc9a0a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs @@ -29,21 +29,36 @@ public ThreadInt64PersistentCounter() public static void Increment(object threadLocalCountObject) { Debug.Assert(threadLocalCountObject is ThreadLocalNode); - Unsafe.As(threadLocalCountObject).Increment(); + ThreadLocalNode node; + unsafe + { + node = Unsafe.As(threadLocalCountObject); + } + node.Increment(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decrement(object threadLocalCountObject) { Debug.Assert(threadLocalCountObject is ThreadLocalNode); - Unsafe.As(threadLocalCountObject).Decrement(); + ThreadLocalNode node; + unsafe + { + node = Unsafe.As(threadLocalCountObject); + } + node.Decrement(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add(object threadLocalCountObject, uint count) { Debug.Assert(threadLocalCountObject is ThreadLocalNode); - Unsafe.As(threadLocalCountObject).Add(count); + ThreadLocalNode node; + unsafe + { + node = Unsafe.As(threadLocalCountObject); + } + node.Add(count); } public object CreateThreadLocalCountObject() diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs index 99455d29b80..eaabe4aa432 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -1252,9 +1252,15 @@ private static void DispatchWorkItem(object workItem, Thread currentThread) else { Debug.Assert(workItem is IThreadPoolWorkItem); + IThreadPoolWorkItem tpWorkItem; + // FIXME: parent should be unsafe and specify input must be Task or IThreadPoolWorkItem + unsafe + { + tpWorkItem = Unsafe.As(workItem); + } try { - Unsafe.As(workItem).Execute(); + tpWorkItem.Execute(); } catch (Exception ex) when (ExceptionHandling.IsHandledByGlobalHandler(ex)) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs index e8b54f2314d..38e3092255e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs @@ -218,8 +218,15 @@ private struct VolatileObject { public volatile object? Value; } [Intrinsic] [NonVersionable] [return: NotNullIfNotNull(nameof(location))] - public static T Read([NotNullIfNotNull(nameof(location))] ref readonly T location) where T : class? => - Unsafe.As(Unsafe.As(ref Unsafe.AsRef(in location)).Value)!; + public static T Read([NotNullIfNotNull(nameof(location))] ref readonly T location) where T : class? + { + T result; + unsafe + { + result = Unsafe.As(Unsafe.As(ref Unsafe.AsRef(in location)).Value)!; + } + return result; + } [Intrinsic] [NonVersionable] diff --git a/src/libraries/System.Private.CoreLib/src/System/WeakReference.T.cs b/src/libraries/System.Private.CoreLib/src/System/WeakReference.T.cs index cf19880b036..a2b6214e7e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/WeakReference.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/WeakReference.T.cs @@ -143,7 +143,11 @@ private T? Target { ComAwareWeakReference cwr = ComAwareWeakReference.GetFromTaggedReference(th); - target = Unsafe.As(cwr.Target) ?? cwr.RehydrateTarget(); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + target = Unsafe.As(cwr.Target) ?? cwr.RehydrateTarget(); + } // must keep the instance alive as long as we use the handle. GC.KeepAlive(this); @@ -154,9 +158,17 @@ private T? Target // unsafe cast is ok as the handle cannot be destroyed and recycled while we keep the instance alive #if FEATURE_JAVAMARSHAL - target = Unsafe.As(GCHandle.InternalGetBridgeWait(th)); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + target = Unsafe.As(GCHandle.InternalGetBridgeWait(th)); + } #else - target = Unsafe.As(GCHandle.InternalGet(th)); + // FIXME: review unsafe to confirm correct annotation + unsafe + { + target = Unsafe.As(GCHandle.InternalGet(th)); + } #endif // must keep the instance alive as long as we use the handle. diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props b/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props index 18ae6cb8fd5..e8a5e17d0c5 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props @@ -3,6 +3,7 @@ +