diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index 568592072d..ad58f4072e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -65,22 +65,6 @@ internal static partial class ADP /// internal const int MaxBufferAccessTokenExpiry = 600; - /// - /// This member returns true if the current OS platform is Windows. - /// - /// - /// 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. - /// - #if NETFRAMEWORK - public const bool IsWindows = true; - #else - public static bool IsWindows => OperatingSystem.IsWindows(); - #endif - #region UDT #if NETFRAMEWORK @@ -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; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index d951bb5d30..85f8c87937 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -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; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs new file mode 100644 index 0000000000..b496141693 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs @@ -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; + +/// +/// Provides platform detection flags for OS-specific code paths. +/// +/// +/// This type exists to keep OS detection terse and in one place: callers write +/// OsConstants.IsWindows instead of repeating RuntimeInformation.IsOSPlatform(...) +/// 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. +/// +/// +/// Each flag is exposed as a property that directly returns an OS check. On .NET, that check is +/// OperatingSystem.Is*(), which the JIT treats as an intrinsic and constant-folds (and which +/// the IL trimmer also recognizes). On .NET Framework — where the +/// platform-check helpers do not exist — the check falls back to +/// . 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. +/// +/// +/// +/// IL trimming note: when a downstream app is published for a specific OS (a RID-specific publish), +/// the IL trimmer substitutes the underlying / +/// OperatingSystem.Is* checks with constant true/false 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. +/// +/// +/// For this to work, each guard must be used inline 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). +/// +/// +/// Do — guard inline, so the trimmer can drop the branch and its dependencies: +/// +/// if (OsConstants.IsWindows) +/// { +/// WindowsOnlyNativeCall(); // becomes `if (false) { ... }` off-Windows, then removed +/// } +/// +/// +/// +/// Don't — hide the guard in a throw helper; the trimmer cannot prove the call below is dead: +/// +/// static void ThrowIfNotWindows() +/// { +/// if (!OsConstants.IsWindows) throw new PlatformNotSupportedException(); +/// } +/// // caller: +/// ThrowIfNotWindows(); +/// WindowsOnlyNativeCall(); // still reachable to the trimmer; kept even off-Windows +/// +/// +/// +internal static class OsConstants +{ + /// + /// Gets a value indicating whether the runtime is executing on Windows. + /// + #if NET + internal static bool IsWindows => OperatingSystem.IsWindows(); + #else + internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + #endif + + /// + /// Gets a value indicating whether the runtime is executing on Linux. + /// + #if NET + internal static bool IsLinux => OperatingSystem.IsLinux(); + #else + internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + #endif + + /// + /// Gets a value indicating whether the runtime is executing on macOS. + /// + #if NET + internal static bool IsMacOS => OperatingSystem.IsMacOS(); + #else + internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + #endif + + #if NET + /// + /// Gets a value indicating whether the runtime is executing on FreeBSD. + /// + /// + /// FreeBSD support is only available in .NET 5+ and later. This property will be + /// false on .NET Framework or if the runtime does not support FreeBSD detection. + /// + internal static bool IsFreeBSD => OperatingSystem.IsFreeBSD(); + #endif +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs index b02dd24af1..f0438cd162 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs @@ -57,7 +57,7 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe /// Gets a string array containing valid certificate locations. /// private static string[] ValidCertificateLocations => - Environment.OSVersion.Platform == PlatformID.Win32NT + OsConstants.IsWindows ? [CertLocationLocalMachine, CertLocationCurrentUser] : [CertLocationCurrentUser]; @@ -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; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs index fc65808cf1..4d0d79b750 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs @@ -149,7 +149,7 @@ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out /// public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(); } @@ -180,7 +180,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(); } @@ -211,7 +211,7 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) { - throw ADP.IsWindows + throw OsConstants.IsWindows ? new NotSupportedException() : new PlatformNotSupportedException(); } @@ -219,7 +219,7 @@ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool a /// public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature) { - throw ADP.IsWindows + throw OsConstants.IsWindows ? new NotSupportedException() : new PlatformNotSupportedException(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs index 73ee585dfa..dc956d9c71 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs @@ -151,7 +151,7 @@ private static int GetProviderType(string providerName, string keyPath, bool isS /// public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(); } @@ -182,7 +182,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(); } @@ -213,7 +213,7 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) { - throw ADP.IsWindows + throw OsConstants.IsWindows ? new NotSupportedException() : new PlatformNotSupportedException(); } @@ -221,7 +221,7 @@ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool a /// public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature) { - throw ADP.IsWindows + throw OsConstants.IsWindows ? new NotSupportedException() : new PlatformNotSupportedException(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs index 678697d221..e73a69fd04 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -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) @@ -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) @@ -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) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs index c3f96b077a..660bfdccde 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -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); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs index a384095254..2dcd38c7d9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; -using System.Runtime.InteropServices; using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient @@ -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 diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs index a8fcf7ca88..63cd220d6c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs @@ -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( diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs index 4c48890fc4..6059705910 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs @@ -102,7 +102,7 @@ public SqlFileStream( FileOptions options, long allocationSize) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); }