In dotnet/coreclr#24504, the ArrayPool<T>-derived types were changed to use GC.AllocateUninitializedArray<T>(...) under the covers as a performance optimization. The AllocateUninitializedArray method uses RuntimeHelpers.IsReferenceOrContainsReferences as an implementation detail when determining whether to return a zeroed array or an uninitialized array back to the caller.
https://github.com/dotnet/coreclr/blob/af9edac7acd4735eebd7a293b27b1c509989dae8/src/System.Private.CoreLib/src/System/GC.cs#L665-L668
This logic is a little too relaxed. It appears that the AllocateUninitializedArray method is trying to ask the question "Is any arbitrary bit pattern valid for type T?", but the IsReferenceOrContainsReferences method answers the separate question "Is type T 'interesting' to the GC?"
There are several value types where IsReferenceOrContainsReferences returns false but which cannot store arbitrary bit patterns without causing problems. Among them:
- System.Boolean (most consumers expect it to contain only
0 or 1)
- System.Decimal (see https://github.com/dotnet/coreclr/issues/16336)
- System.DateTime / System.DateTimeOffset
- System.Runtime.InteropServices.GCHandle
- System.Buffers.StandardFormat
- System.Text.Rune
The behaviors of calling instance methods on these types or of consuming these types when they've been initialized with arbitrary bit patterns is undefined, and it could result in anything from nonsensical answers being generated to crashing the runtime.
Normally we'd tell developers not to look at the elements of arrays returned by GC.AllocateUninitializedArray or ArrayPool<T>.Rent, but there are some situations where they might not be able to control it. For example, if I'm under the VS debugger and I step past a call to AllocateUninitializedArray or Rent, the debugger could start listing the array's elements automatically even though my own code wouldn't have done such a thing.
If we do not want to change GC.AllocateUninitializedArray (perhaps because we consider it an "unsafe" API), we should at least consider putting the stronger checks into the ArrayPool<T>-derived classes.
In dotnet/coreclr#24504, the
ArrayPool<T>-derived types were changed to useGC.AllocateUninitializedArray<T>(...)under the covers as a performance optimization. TheAllocateUninitializedArraymethod usesRuntimeHelpers.IsReferenceOrContainsReferencesas an implementation detail when determining whether to return a zeroed array or an uninitialized array back to the caller.https://github.com/dotnet/coreclr/blob/af9edac7acd4735eebd7a293b27b1c509989dae8/src/System.Private.CoreLib/src/System/GC.cs#L665-L668
This logic is a little too relaxed. It appears that the
AllocateUninitializedArraymethod is trying to ask the question "Is any arbitrary bit pattern valid for type T?", but theIsReferenceOrContainsReferencesmethod answers the separate question "Is type T 'interesting' to the GC?"There are several value types where
IsReferenceOrContainsReferencesreturns false but which cannot store arbitrary bit patterns without causing problems. Among them:0or1)The behaviors of calling instance methods on these types or of consuming these types when they've been initialized with arbitrary bit patterns is undefined, and it could result in anything from nonsensical answers being generated to crashing the runtime.
Normally we'd tell developers not to look at the elements of arrays returned by
GC.AllocateUninitializedArrayorArrayPool<T>.Rent, but there are some situations where they might not be able to control it. For example, if I'm under the VS debugger and I step past a call toAllocateUninitializedArrayorRent, the debugger could start listing the array's elements automatically even though my own code wouldn't have done such a thing.If we do not want to change
GC.AllocateUninitializedArray(perhaps because we consider it an "unsafe" API), we should at least consider putting the stronger checks into theArrayPool<T>-derived classes.