Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion eng/CodeAnalysis.src.globalconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1370,7 +1370,10 @@ dotnet_diagnostic.SA1204.severity = none
dotnet_diagnostic.SA1205.severity = warning

# SA1206: Keyword ordering
dotnet_diagnostic.SA1206.severity = warning
# Disabled: StyleCop does not know the 'safe' contextual keyword introduced by the updated
# memory safety rules and asks for it ahead of the accessibility modifier, which is not how
# it orders 'unsafe'. Re-enable once StyleCop handles the keyword.
dotnet_diagnostic.SA1206.severity = none

@tannergooding tannergooding Aug 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this one even relevant anymore? Wasn't it entirely replaced by IDE0036, which is more powerful and stays up to date with new language keywords?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, but it does still kick in. I've got an impression that stylecop is not actively maintained?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it is maintained anymore either, its not had an update in over 8 months.

Certainly many of the rules have been replaced by in-box ones as well, so it might be worth considering what's adding value still (and what can be removed or disabled).


# SA1208: Using directive ordering
dotnet_diagnostic.SA1208.severity = none
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ internal unsafe struct ComActivationContextInternal
{
public Guid ClassId;
public Guid InterfaceId;
/// <safety>Holds only a pointer value addressing a caller-provided character buffer; reading or writing the field never dereferences it, so field access alone cannot read or write that buffer (any dereference requires an unsafe context).</safety>
public char* AssemblyPathBuffer;
/// <safety>Holds only a pointer value addressing a caller-provided character buffer; reading or writing the field never dereferences it, so field access alone cannot read or write that buffer (any dereference requires an unsafe context).</safety>
public char* AssemblyNameBuffer;
/// <safety>Holds only a pointer value addressing a caller-provided character buffer; reading or writing the field never dereferences it, so field access alone cannot read or write that buffer (any dereference requires an unsafe context).</safety>
public char* TypeNameBuffer;
public IntPtr ClassFactoryDest;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ public static int CurrentManagedThreadId
}

// Terminates this process with the given exit code.
/// <safety>QCall that passes the integer exit code to the runtime to terminate the process; it accesses no caller-supplied memory.</safety>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to require every LibraryImport to have a safety comment, even ones with trivial signatures?

@EgorBo EgorBo Aug 3, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to require every LibraryImport to have a safety comment, even ones with trivial signatures?

It is not really required, but for consistency it's nice to have an explanation why something needed an explicit caller-safe or caller-unsafe keyword for every memeber. Although, we probably won't add it to all functions with unmanaged pointrs in the sig..

But all LibraryImport (just like all extern and just like all fields of Extended/Explicit layout structs) have to have an explicit safe or unsafe keyword on them.

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Environment_Exit")]
[DoesNotReturn]
private static partial void _Exit(int exitCode);
private static safe partial void _Exit(int exitCode);

[DoesNotReturn]
public static void Exit(int exitCode) => _Exit(exitCode);

public static extern int ExitCode
/// <safety>Runtime FCall get/set of the process-wide exit code (an int); it accesses no caller-supplied memory.</safety>
public static safe extern int ExitCode
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
Expand Down Expand Up @@ -116,8 +118,9 @@ private static unsafe void InitializeCommandLineArgs(char* exePath, int argc, ch
}
}

/// <safety>QCall that returns the available processor count as an int; it accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Environment_GetProcessorCount")]
internal static partial int GetProcessorCount();
internal static safe partial int GetProcessorCount();

[UnmanagedCallersOnly]
private static unsafe void GetResourceString(char* pKey, string* pResult, Exception* pException)
Expand Down
62 changes: 42 additions & 20 deletions src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,13 @@ public static GCMemoryInfo GetGCMemoryInfo(GCKind kind)
return new GCMemoryInfo(data);
}

/// <safety>QCall that starts a no-GC region from scalar size and flag arguments and returns a status code; it accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_StartNoGCRegion")]
internal static partial int _StartNoGCRegion(long totalSize, [MarshalAs(UnmanagedType.Bool)] bool lohSizeKnown, long lohSize, [MarshalAs(UnmanagedType.Bool)] bool disallowFullBlockingGC);

/// <safety>QCall that ends the no-GC region and returns a status code; it takes no arguments and accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_EndNoGCRegion")]
internal static partial int _EndNoGCRegion();
internal static safe partial int _EndNoGCRegion();

// keep in sync with GC_ALLOC_FLAGS in gcinterface.h
internal enum GC_ALLOC_FLAGS
Expand All @@ -104,32 +106,41 @@ internal enum GC_ALLOC_FLAGS
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_AllocateNewArray")]
private static partial void AllocateNewArray(IntPtr typeHandlePtr, int length, GC_ALLOC_FLAGS flags, ObjectHandleOnStack ret);

/// <safety>QCall that returns the total managed heap byte count as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_GetTotalMemory")]
private static partial long GetTotalMemory();
private static safe partial long GetTotalMemory();

/// <safety>QCall that triggers a collection from scalar generation and mode arguments; it accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_Collect")]
private static partial void _Collect(int generation, int mode, [MarshalAs(UnmanagedType.U1)] bool lowMemoryPressure);

/// <safety>FCall that returns the maximum supported generation number as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern int GetMaxGeneration();
private static safe extern int GetMaxGeneration();

/// <safety>FCall that returns a collection count computed from scalar arguments; it accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern int _CollectionCount(int generation, int getSpecialGCCount);
private static safe extern int _CollectionCount(int generation, int getSpecialGCCount);

/// <safety>FCall that returns the GC segment size as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern ulong GetSegmentSize();
internal static safe extern ulong GetSegmentSize();

/// <safety>FCall that returns a percentage as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int GetLastGCPercentTimeInGC();
internal static safe extern int GetLastGCPercentTimeInGC();

/// <safety>FCall that returns the size of the generation identified by an integer argument as a scalar; it accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern ulong GetGenerationSize(int gen);
internal static safe extern ulong GetGenerationSize(int gen);

/// <safety>QCall that records added memory pressure from a scalar byte count; it accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_AddMemoryPressure")]
private static partial void _AddMemoryPressure(ulong bytesAllocated);
private static safe partial void _AddMemoryPressure(ulong bytesAllocated);

/// <safety>QCall that records removed memory pressure from a scalar byte count; it accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_RemoveMemoryPressure")]
private static partial void _RemoveMemoryPressure(ulong bytesAllocated);
private static safe partial void _RemoveMemoryPressure(ulong bytesAllocated);

public static void AddMemoryPressure(long bytesAllocated)
{
Expand Down Expand Up @@ -340,8 +351,9 @@ private static unsafe uint RunFinalizers()
return count;
}

/// <safety>QCall that blocks until pending finalizers drain; it takes no arguments and accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_WaitForPendingFinalizers")]
private static partial void _WaitForPendingFinalizers();
private static safe partial void _WaitForPendingFinalizers();

public static void WaitForPendingFinalizers()
{
Expand Down Expand Up @@ -419,8 +431,9 @@ public static long GetTotalMemory(bool forceFullCollection)
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_UnregisterFrozenSegment")]
private static partial void _UnregisterFrozenSegment(IntPtr segmentHandle);

/// <safety>FCall that returns the current thread's allocated byte count as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern long GetAllocatedBytesForCurrentThread();
public static safe extern long GetAllocatedBytesForCurrentThread();


/// <summary>
Expand All @@ -429,23 +442,29 @@ public static long GetTotalMemory(bool forceFullCollection)
/// <param name="precise">If true, gather a precise number, otherwise gather a fairly count. Gathering a precise value triggers at a significant performance penalty.</param>
public static long GetTotalAllocatedBytes(bool precise = false) => precise ? GetTotalAllocatedBytesPrecise() : GetTotalAllocatedBytesApproximate();

/// <safety>FCall that returns an approximate process-wide allocated byte count as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern long GetTotalAllocatedBytesApproximate();
private static safe extern long GetTotalAllocatedBytesApproximate();

/// <safety>QCall that returns a precise process-wide allocated byte count as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_GetTotalAllocatedBytesPrecise")]
private static partial long GetTotalAllocatedBytesPrecise();
private static safe partial long GetTotalAllocatedBytesPrecise();

/// <safety>FCall that enables full-GC notifications from scalar percentage arguments and returns a status flag; it accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool _RegisterForFullGCNotification(int maxGenerationPercentage, int largeObjectHeapPercentage);
private static safe extern bool _RegisterForFullGCNotification(int maxGenerationPercentage, int largeObjectHeapPercentage);

/// <safety>FCall that cancels full-GC notifications and returns a status flag; it takes no arguments and accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool _CancelFullGCNotification();
private static safe extern bool _CancelFullGCNotification();

/// <safety>QCall that waits for an approaching full GC using a scalar timeout and returns a status code; it accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_WaitForFullGCApproach")]
private static partial int _WaitForFullGCApproach(int millisecondsTimeout);
private static safe partial int _WaitForFullGCApproach(int millisecondsTimeout);

/// <safety>QCall that waits for full-GC completion using a scalar timeout and returns a status code; it accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_WaitForFullGCComplete")]
private static partial int _WaitForFullGCComplete(int millisecondsTimeout);
private static safe partial int _WaitForFullGCComplete(int millisecondsTimeout);

public static void RegisterForFullGCNotification(int maxGenerationThreshold, int largeObjectHeapThreshold)
{
Expand Down Expand Up @@ -594,8 +613,9 @@ public MemoryLoadChangeNotification(float lowMemoryPercent, float highMemoryPerc
private static readonly List<MemoryLoadChangeNotification> s_notifications = new List<MemoryLoadChangeNotification>();
private static float s_previousMemoryLoad = float.MaxValue;

/// <safety>FCall that returns the current memory load percentage as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern uint GetMemoryLoad();
internal static safe extern uint GetMemoryLoad();

private static bool InvokeMemoryLoadChangeNotifications()
{
Expand Down Expand Up @@ -769,8 +789,9 @@ internal static long GetGenerationBudget(int generation)
return _GetGenerationBudget(generation);
}

/// <safety>QCall that returns the allocation budget of the generation identified by an integer argument as a scalar; it accesses no caller-supplied memory.</safety>
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_GetGenerationBudget")]
internal static partial long _GetGenerationBudget(int generation);
internal static safe partial long _GetGenerationBudget(int generation);

internal static void UnregisterMemoryLoadChangeNotification(Action notification)
{
Expand Down Expand Up @@ -856,8 +877,9 @@ public static T[] AllocateArray<T>(int length, bool pinned = false) // T[] rathe
return result!;
}

/// <safety>FCall that returns the cumulative GC pause duration as a scalar; it takes no arguments and accesses no caller-supplied memory.</safety>
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern long _GetTotalPauseDuration();
private static safe extern long _GetTotalPauseDuration();

/// <summary>
/// Gets the total amount of time paused in GC since the beginning of the process.
Expand Down
Loading
Loading