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
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,6 @@ internal static partial class ADP
/// </summary>
internal const int MaxBufferAccessTokenExpiry = 600;

/// <summary>
/// This member returns true if the current OS platform is Windows.
/// </summary>
/// <remarks>
/// This is a const on .NET Framework, and a property on .NET Core, because of differing API availability and JIT requirements.
/// .NET Framework will perform basic dead branch elimination when a const value is encountered, while .NET Core can trim Windows-specific
/// code when published to non-Windows platforms.
/// .NET Core's trimming is very limited though, so this must be used inline within methods to throw PlatformNotSupportedException,
/// rather than in a throw helper.
/// </remarks>
#if NETFRAMEWORK
public const bool IsWindows = true;

@paulmedynski paulmedynski May 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This isn't true - TFM != OS. We may happen to only support .NET Framework when running on Windows, but we shouldn't be conflating the two.

#else
public static bool IsWindows => OperatingSystem.IsWindows();
#endif

#region UDT

#if NETFRAMEWORK
Expand Down Expand Up @@ -441,7 +425,7 @@ internal static ArgumentOutOfRangeException InvalidCommandBehavior(CommandBehavi
internal static object LocalMachineRegistryValue(string subkey, string queryvalue)
{
#if NET
if (!IsWindows)
if (!OsConstants.IsWindows)
{
// No registry in non-Windows environments
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ public static bool UseManagedNetworking
return s_useManagedNetworking == SwitchValue.True;
}

if (!OperatingSystem.IsWindows())
if (!OsConstants.IsWindows)
{
s_useManagedNetworking = SwitchValue.True;
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

namespace Microsoft.Data.SqlClient;

/// <summary>
/// Provides platform detection flags for OS-specific code paths.
/// </summary>
/// <remarks>
/// This type exists to keep OS detection terse and in one place: callers write
/// <c>OsConstants.IsWindows</c> instead of repeating <c>RuntimeInformation.IsOSPlatform(...)</c>
/// throughout the codebase. Centralizing it also gives us a single seam to adjust if a specific
/// OS needs special handling (for example, a different detection mechanism, a finer-grained
/// flag, or an override for testing) without touching every call site.
/// </remarks>
/// <remarks>
/// Each flag is exposed as a property that directly returns an OS check. On .NET, that check is
/// <c>OperatingSystem.Is*()</c>, which the JIT treats as an intrinsic and constant-folds (and which
/// the IL trimmer also recognizes). On .NET Framework — where the <see cref="System.OperatingSystem"/>
/// platform-check helpers do not exist — the check falls back to
/// <see cref="RuntimeInformation.IsOSPlatform"/>. Either way, returning the check directly is
/// deliberate: caching the results in static fields (for example, via a static constructor) would
/// defeat both the JIT folding and the trimmer — the trimmer cannot analyze a static constructor,
/// so it would be unable to prove which branches are dead and would keep OS-specific code (including
/// Windows-only native dependencies) in apps published for other platforms.
Comment thread
paulmedynski marked this conversation as resolved.
/// </remarks>
/// <remarks>
/// <para>
/// IL trimming note: when a downstream app is published for a specific OS (a RID-specific publish),
/// the IL trimmer substitutes the underlying <see cref="RuntimeInformation.IsOSPlatform"/> /
/// <c>OperatingSystem.Is*</c> checks with constant <c>true</c>/<c>false</c> for the target OS and
/// then removes the dead branch (along with everything it transitively references, such as
/// Windows-only native entry points). This is what lets a single cross-platform assembly trim
/// away the OS-specific code that does not apply to the published target.
/// </para>
/// <para>
/// For this to work, each guard must be used <b>inline</b> in the same method as the OS-specific
/// code it gates. The trimmer's constant folding is shallow: it reasons within the method that
/// contains the guard, so a guard hidden behind a helper traps the constant inside that helper and
/// leaves the protected code reachable (and therefore not trimmed).
/// </para>
/// <para>
/// Do — guard inline, so the trimmer can drop the branch and its dependencies:
/// <code>
/// if (OsConstants.IsWindows)
/// {
/// WindowsOnlyNativeCall(); // becomes `if (false) { ... }` off-Windows, then removed
/// }
/// </code>
/// </para>
/// <para>
/// Don't — hide the guard in a throw helper; the trimmer cannot prove the call below is dead:
/// <code>
/// static void ThrowIfNotWindows()
/// {
/// if (!OsConstants.IsWindows) throw new PlatformNotSupportedException();
/// }
/// // caller:
/// ThrowIfNotWindows();
/// WindowsOnlyNativeCall(); // still reachable to the trimmer; kept even off-Windows
/// </code>
/// </para>
/// </remarks>
internal static class OsConstants
{
/// <summary>
/// Gets a value indicating whether the runtime is executing on Windows.
/// </summary>
#if NET
internal static bool IsWindows => OperatingSystem.IsWindows();
#else
internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
#endif

/// <summary>
/// Gets a value indicating whether the runtime is executing on Linux.
/// </summary>
#if NET
internal static bool IsLinux => OperatingSystem.IsLinux();
#else
internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
#endif

/// <summary>
/// Gets a value indicating whether the runtime is executing on macOS.
/// </summary>
#if NET
internal static bool IsMacOS => OperatingSystem.IsMacOS();
#else
internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
#endif

#if NET
/// <summary>
/// Gets a value indicating whether the runtime is executing on FreeBSD.
/// </summary>
/// <remarks>
/// FreeBSD support is only available in .NET 5+ and later. This property will be
/// <c>false</c> on .NET Framework or if the runtime does not support FreeBSD detection.
/// </remarks>
internal static bool IsFreeBSD => OperatingSystem.IsFreeBSD();
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe
/// Gets a string array containing valid certificate locations.
/// </summary>
private static string[] ValidCertificateLocations =>
Environment.OSVersion.Platform == PlatformID.Win32NT
OsConstants.IsWindows
? [CertLocationLocalMachine, CertLocationCurrentUser]
: [CertLocationCurrentUser];

Expand Down Expand Up @@ -158,14 +158,13 @@ private static RSA GetCertificatePrivateKeyByPath(string keyPath, bool isSystemO
}

// Extract the store location where the cert is stored
if (storeLocationSpan.IsEmpty
&& Environment.OSVersion.Platform == PlatformID.Win32NT)
if (storeLocationSpan.IsEmpty && OsConstants.IsWindows)
{
// Default to Local Machine on Windows. Non-Windows platforms only support CurrentUser
storeLocation = StoreLocation.LocalMachine;
}
else if (storeLocationSpan.Equals(CertLocationLocalMachine.AsSpan(), StringComparison.OrdinalIgnoreCase)
&& Environment.OSVersion.Platform == PlatformID.Win32NT)
&& OsConstants.IsWindows)
{
storeLocation = StoreLocation.LocalMachine;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml' path='docs/members[@name="SqlColumnEncryptionCngProvider"]/DecryptColumnEncryptionKey/*' />
public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
if (!ADP.IsWindows)
if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -180,7 +180,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string?
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml' path='docs/members[@name="SqlColumnEncryptionCngProvider"]/EncryptColumnEncryptionKey/*' />
public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
if (!ADP.IsWindows)
if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -211,15 +211,15 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string?
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml' path='docs/members[@name="SqlColumnEncryptionCngProvider"]/SignColumnMasterKeyMetadata/*' />
public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
throw ADP.IsWindows
throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml' path='docs/members[@name="SqlColumnEncryptionCngProvider"]/VerifyColumnMasterKeyMetadata/*' />
public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
throw ADP.IsWindows
throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private static int GetProviderType(string providerName, string keyPath, bool isS
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml' path='docs/members[@name="SqlColumnEncryptionCspProvider"]/DecryptColumnEncryptionKey/*' />
public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
if (!ADP.IsWindows)
if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -182,7 +182,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string?
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml' path='docs/members[@name="SqlColumnEncryptionCspProvider"]/EncryptColumnEncryptionKey/*' />
public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
if (!ADP.IsWindows)
if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -213,15 +213,15 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string?
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml' path='docs/members[@name="SqlColumnEncryptionCspProvider"]/SignColumnMasterKeyMetadata/*' />
public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
throw ADP.IsWindows
throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml' path='docs/members[@name="SqlColumnEncryptionCspProvider"]/VerifyColumnMasterKeyMetadata/*' />
public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
throw ADP.IsWindows
throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1141,7 +1141,7 @@ internal static Exception LargeCertificatePathLength(int actualLength, int maxLe

internal static Exception NullCertificatePath(string[] validLocations, bool isSystemOp)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
Expand Down Expand Up @@ -1193,7 +1193,7 @@ internal static Exception NullCngKeyPath(bool isSystemOp)

internal static Exception InvalidCertificatePath(string actualCertificatePath, string[] validLocations, bool isSystemOp)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
Expand Down Expand Up @@ -1329,7 +1329,7 @@ internal static Exception InvalidCngKey(string masterKeyPath, string cngProvider

internal static Exception InvalidCertificateLocation(string certificateLocation, string certificatePath, string[] validLocations, bool isSystemOp)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ
// Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
// before calling SNISecGenClientContext).
#if NET
if (OperatingSystem.IsWindows())
if (OsConstants.IsWindows)
#endif
{
error = _physicalStateObj.WaitForSSLHandShakeToComplete(out protocol);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Data.Common;

namespace Microsoft.Data.SqlClient
Expand Down Expand Up @@ -173,7 +172,7 @@ static internal byte[] GetNetworkPhysicalAddressForTdsLoginOnly()

byte[] nicAddress = null;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (OsConstants.IsWindows)
{
// NIC address is stored in NetworkAddress key. However, if NetworkAddressLocal key
// has a value that is not zero, then we cannot use the NetworkAddress key and must
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,24 @@ static UserAgent()
// specific values.
//
string osType = Unknown;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (OsConstants.IsWindows)
{
osType = Windows;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
else if (OsConstants.IsLinux)
{
osType = Linux;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
else if (OsConstants.IsMacOS)
{
osType = macOS;
}
// The FreeBSD platform doesn't exist in .NET Framework at all.
#if NET
else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
#if NET
else if (OsConstants.IsFreeBSD)
{
osType = FreeBSD;
}
#endif
#endif

// Build it!
Value = Build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public SqlFileStream(
FileOptions options,
long allocationSize)
{
if (!ADP.IsWindows)
if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported);
}
Expand Down
Loading