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 ecefa338c9..c18aea1e32 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 @@ -159,10 +143,10 @@ internal static Timer UnsafeCreateTimer( state, TimeSpan.FromMilliseconds(dueTimeMilliseconds), TimeSpan.FromMilliseconds(periodMilliseconds)); - + internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) { - // Don't capture the current ExecutionContext and its AsyncLocals onto + // Don't capture the current ExecutionContext and its AsyncLocals onto // a global timer causing them to live forever bool restoreFlow = false; try @@ -184,7 +168,7 @@ internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, Ti } } } - + #region COM+ exceptions internal static ArgumentException Argument(string error) @@ -451,7 +435,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; @@ -639,7 +623,7 @@ internal static string BuildMultiPartName(string[] strings) { StringBuilder bld = new(); // Assume we want to build a full multi-part name with all parts except trimming separators for - // leading empty names (null or empty strings, but not whitespace). Separators in the middle + // leading empty names (null or empty strings, but not whitespace). Separators in the middle // should be added, even if the name part is null/empty, to maintain proper location of the parts. for (int i = 0; i < strings.Length; i++) { @@ -839,14 +823,14 @@ internal static Version GetAssemblyVersion() /// Represents a collection of Azure SQL Server endpoint URLs for various regions and environments. /// /// This array includes endpoint URLs for Azure SQL in global, Germany, US Government, - /// China, and Fabric environments. These endpoints are used to identify and interact with Azure SQL services + /// China, and Fabric environments. These endpoints are used to identify and interact with Azure SQL services /// in their respective regions or environments. internal static readonly List s_azureSqlServerEndpoints = new() { AZURE_SQL, AZURE_SQL_GERMANY, AZURE_SQL_USGOV, AZURE_SQL_CHINA, AZURE_SQL_FABRIC }; - + /// /// Contains endpoint strings for Azure SQL Server on-demand services. /// Each entry is a combination of the ONDEMAND_PREFIX and a specific Azure SQL endpoint string. @@ -872,9 +856,9 @@ internal static Version GetAssemblyVersion() internal static bool IsAzureSynapseOnDemandEndpoint(string dataSource) { return IsEndpoint(dataSource, s_azureSynapseOnDemandEndpoints) - || dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0; + || dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0; } - + internal static bool IsAzureSqlServerEndpoint(string dataSource) { return IsEndpoint(dataSource, s_azureSqlServerEndpoints); @@ -1088,7 +1072,7 @@ internal enum InternalErrorCode SqlDependencyObtainProcessDispatcherFailureObjectHandle = 50, SqlDependencyProcessDispatcherFailureCreateInstance = 51, - + SqlDependencyCommandHashIsNotAssociatedWithNotification = 53, UnknownTransactionFailure = 60, 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 16e0abb7ec..c93eeb2359 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -562,7 +562,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..09f2c27960 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs @@ -0,0 +1,70 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.Data.SqlClient; + +/// +/// Provides platform detection flags for OS-specific code paths. +/// +/// +/// These constants are computed at runtime and cached as static readonly fields. This design +/// allows the JIT compiler to elide branches in hot paths based on whether the OS flags are known +/// constants at JIT compilation time. +/// +internal static class OsConstants +{ + /// + /// Gets a value indicating whether the runtime is executing on Windows. + /// + internal static readonly bool IsWindows; + + /// + /// Gets a value indicating whether the runtime is executing on Linux. + /// + internal static readonly bool IsLinux; + + /// + /// Gets a value indicating whether the runtime is executing on macOS. + /// + internal static readonly bool IsMacOS; + + #if NET + /// + /// Gets a value indicating whether the runtime is executing on FreeBSD. + /// + /// + /// FreeBSD support is only available in .NET 5+ and later. This field will be + /// false on .NET Framework or if the runtime does not support FreeBSD detection. + /// + internal static readonly bool IsFreeBSD; + #endif + + /// + /// Initializes platform detection flags by querying . + /// + /// + /// We use a static constructor instead of a module initializer ([ModuleInitializer]) to avoid + /// the CA2255 security concern. Module initializers can be problematic because: 1. They run in + /// an unpredictable order relative to other initialization code. 2. They run before the app + /// initialization sequence, potentially before security policies are set. 3. They can + /// complicate debugging and profiling. + /// + /// Using a static constructor ensures initialization happens in a well-defined, type-safe + /// manner that is compatible with the CLR's type loading guarantees. + /// + /// The trade-off is that the OS flags won't be initialized until the OsConstants type is first + /// accessed, which may cause a slight delay in a hot path, but only once. + /// + static OsConstants() + { + IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + #if NET + IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); + #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 96312a491d..52522c7266 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -1376,7 +1376,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) @@ -1428,7 +1428,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) @@ -1564,7 +1564,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 29352c5460..920f96d07d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -981,7 +981,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 56ead6a4a2..ce24fd88c0 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); }