Skip to content

GC.AllocateUninitializedArray should tighten its logic around when to return zeroed arrays #13292

Description

@GrabYourPitchforks

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions