Skip to content

Consolidate OS detection to OsConstants#4255

Open
paulmedynski wants to merge 4 commits into
mainfrom
dev/paul/os-constants
Open

Consolidate OS detection to OsConstants#4255
paulmedynski wants to merge 4 commits into
mainfrom
dev/paul/os-constants

Conversation

@paulmedynski

@paulmedynski paulmedynski commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR consolidates all OS detection logic into a single internal OsConstants class, giving the codebase one consistent, terse way to ask "what OS are we on?" (OsConstants.IsWindows, IsLinux, IsMacOS, IsFreeBSD) and a single seam for any future OS-specific handling.

Just as importantly, the flags are implemented so they remain statically analyzable, which is what lets the JIT and the IL trimmer eliminate OS-specific branches (and their dependencies, including Windows-only native SNI) in downstream apps.

OsConstants design

  • Each flag is an expression-bodied property that returns an OS check directly — it is not cached in a static field or computed in a static constructor.
  • On .NET (net8.0/net9.0): uses OperatingSystem.IsWindows()/IsLinux()/IsMacOS()/IsFreeBSD(), which the JIT recognizes as intrinsics and constant-folds, and which the IL trimmer also understands.
  • On .NET Framework (net462): falls back to RuntimeInformation.IsOSPlatform(...), since the OperatingSystem platform-check helpers don't exist there. IsFreeBSD is #if NET-only.

Why not static readonly fields / a static constructor?

The original approach cached the results in static readonly fields populated by a static constructor. That defeats trimming: the IL trimmer can't analyze a static constructor, so it can't prove a flag is constant-false on a given OS and conservatively keeps the OS-specific code (including the native SNI DLL graph) in apps published for other platforms. Returning the intrinsic check directly preserves both the JIT constant-folding and trimmer dead-code elimination.

Usage requirement (documented in the class)

For trimming to actually drop a branch, each guard must be used inline in the same method as the OS-specific code it gates. The trimmer's constant folding is shallow, so a guard hidden behind a throw-helper traps the constant inside that helper and leaves the protected code reachable. The class XML docs include do/don't examples.

Call-site replacements (10 files)

Consolidated assorted ad-hoc OS checks onto OsConstants:

  • TdsParserStaticMethods.cs, TdsParser.cs, LocalAppContextSwitches.cs — replaced RuntimeInformation.IsOSPlatform() / OperatingSystem.IsWindows().
  • UserAgent.cs — replaced RuntimeInformation.IsOSPlatform() and added FreeBSD detection.
  • AdapterUtil.cs — removed the ADP.IsWindows member; OsConstants is now the single source.
  • SqlFileStream.cs, SqlColumnEncryptionCngProvider.cs, SqlColumnEncryptionCspProvider.cs — replaced ADP.IsWindows.
  • SqlColumnEncryptionCertificateStoreProvider.cs, SqlUtil.cs — replaced Environment.OSVersion-based checks.

All existing guards were verified to be inline with the code they protect (no throw-helper indirection), so they remain trimmer-foldable.

Benefits

  • Single, consistent point of control for OS detection.
  • Preserves JIT constant-folding on .NET via OperatingSystem.Is*() intrinsics.
  • Preserves IL trimming so apps published for a specific RID can drop other-OS code paths (incl. Windows-only native dependencies) — supporting the goal of a single managed assembly per TFM.
  • Correct on .NET-Framework-on-non-Windows (no longer hard-codes IsWindows = true).

Notes

  • The UseManagedNetworking IL trimming substitution (ILLink.Substitutions.xml) is independent of this change — it body-stubs the getter based on the UseManagedNetworkingOnWindows feature switch and never reads OsConstants.

Copilot AI review requested due to automatic review settings May 1, 2026 15:36
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board May 1, 2026
@paulmedynski paulmedynski added this to the 7.1.0-preview2 milestone May 1, 2026
@paulmedynski paulmedynski moved this from To triage to In progress in SqlClient Board May 1, 2026
@paulmedynski paulmedynski added the Code Health 💊 Issues/PRs that are targeted to source code quality improvements. label May 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR centralizes runtime OS detection behind a new internal OsConstants type, replacing scattered RuntimeInformation / Environment.OSVersion / OperatingSystem.IsWindows() checks to keep platform branching consistent across the driver.

Changes:

  • Added OsConstants with cached OS flags (IsWindows, IsLinux, IsMacOS, IsFreeBSD (NET), IsUnix).
  • Replaced OS checks across SqlClient core, encryption providers, registry helpers, and SqlFileStream.
  • Simplified some call sites by removing ADP.IsWindows and other ad-hoc platform detection.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs Introduces centralized OS detection flags used by the rest of the PR.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs Removes ADP.IsWindows and uses OsConstants for registry availability checks.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs Switches Windows-only registry/NIC logic to OsConstants.IsWindows.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs Replaces OperatingSystem.IsWindows() with OsConstants.IsWindows in SSL handshake path.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs Uses OsConstants for OS name selection, including FreeBSD under #if NET.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs Uses OsConstants in managed/native networking switch logic.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs Replaces Environment.OSVersion.Platform checks with OsConstants.IsWindows for error shaping.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs Uses OsConstants.IsWindows to gate LocalMachine vs CurrentUser behavior.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs Uses OsConstants for platform guards around Windows-only CNG operations.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs Uses OsConstants for platform guards around Windows-only CSP operations.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs Replaces ADP.IsWindows with OsConstants.IsUnix for platform not supported gating.
Comments suppressed due to low confidence (1)

src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs:442

  • This check uses OsConstants.IsUnix, but the comment says "No registry in non-Windows environments". If IsUnix remains limited to Linux/macOS/FreeBSD, this will attempt registry access on other non-Windows platforms. Prefer if (!OsConstants.IsWindows) here (or ensure IsUnix is equivalent to !IsWindows).
            #if NET
            if (OsConstants.IsUnix)
            {
                // No registry in non-Windows environments
                return null;
            }

Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs Outdated
Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs Outdated
Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs Outdated
/// 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.

Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs Outdated
Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs Outdated
#endif

/// <summary>
/// Initializes platform detection flags by querying <see cref="RuntimeInformation"/>.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This impacts IL trimming, which runs at publish time. The IL trimmer needs to be able to statically see that IsWindows will always be false on Linux, otherwise it'll leave the Windows-specific code (including the native SNI, with all its dependencies on the native DLL...) in the Linux app.

I've linked against the CI artifacts and confirmed that the IL trimmer can't analyze the static constructor, so I think this rules out everything besides exposing properties which returns the value of RuntimeInformation.IsOSPlatform (as ADP.IsWindows currently does - but expanded for each OS.)

Incidentally, exposing it as a field or a property also blocks .NET Framework from removing dead code paths (which in turn can consume the inlining budget), which is why IsWindows is currently a const. I've not got a strong preference there though, perhaps the performance gains are small enough that correctness is more important here.

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.

Good catch — confirmed the static constructor blocks the trimmer. Reworked OsConstants in 49c3f72 to expose each flag as a property that returns RuntimeInformation.IsOSPlatform(...) directly, so the trimmer can fold the guards and drop OS-specific branches (including the Windows-only native SNI graph) when published for another platform.

I also verified the UseManagedNetworking ILLink.Substitutions.xml mechanism is independent of this change — it body-stubs the getter based on the UseManagedNetworkingOnWindows feature switch at publish time, so it never reads OsConstants.

I went with properties over const to keep correctness on .NET Framework-running-on-non-Windows; per your note, the netfx dead-code-elimination tradeoff seems acceptable in exchange for correctness.

@codecov

codecov Bot commented May 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.10345% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.41%. Comparing base (7a01dbe) to head (f5035e4).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...qlClient/src/Microsoft/Data/SqlClient/UserAgent.cs 75.00% 1 Missing ⚠️
...lient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4255      +/-   ##
==========================================
- Coverage   66.02%   64.41%   -1.61%     
==========================================
  Files         277      273       -4     
  Lines       42988    65787   +22799     
==========================================
+ Hits        28382    42379   +13997     
- Misses      14606    23408    +8802     
Flag Coverage Δ
CI-SqlClient ?
PR-SqlClient-Project 64.41% <93.10%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- Create new OsConstants class with IsWindows, IsLinux, IsMacOS, IsFreeBSD, and IsUnix fields
- Add comprehensive XML documentation and explain why static constructor is preferred over [ModuleInitializer] (CA2255 rule)
- Replace all RuntimeInformation.IsOSPlatform() calls with OsConstants fields in:
  - TdsParserStaticMethods.cs
  - UserAgent.cs
  - AdapterUtil.cs (now forwards to OsConstants)
- Replace Environment.OSVersion.Platform checks with OsConstants in:
  - SqlColumnEncryptionCertificateStoreProvider.cs (3 instances)
  - SqlUtil.cs (3 instances)
- Replace OperatingSystem static methods with OsConstants in:
  - LocalAppContextSwitches.cs (uses IsUnix for clearer semantics)
  - TdsParser.cs
- Replace all ADP.IsWindows usage with OsConstants in:
  - SqlFileStream.cs
  - SqlColumnEncryptionCngProvider.cs (4 instances)
  - SqlColumnEncryptionCspProvider.cs (4 instances)
- Remove unused AdapterUtil.IsWindows property

Benefits:
- Single point of control for OS detection
- Improved JIT optimization through static readonly cached flags
- Clearer semantics with IsUnix flag for Unix-like platforms
- Consistent API surface across the codebase
- Remove OsConstants.IsUnix to avoid non-Windows ambiguity
- Update Windows-only guards in SqlFileStream, CNG, CSP, and switch logic
- Keep explicit OS flags (Windows/Linux/macOS/FreeBSD)
Copilot AI review requested due to automatic review settings June 26, 2026 15:15
@paulmedynski paulmedynski force-pushed the dev/paul/os-constants branch from f5035e4 to 8f5e2d0 Compare June 26, 2026 15:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs Outdated
Replace the static readonly fields and static constructor in OsConstants
with properties that directly return RuntimeInformation.IsOSPlatform(...).
The IL trimmer cannot analyze a static constructor, so the cached-field
design left OS-specific code (including Windows-only native dependencies)
in apps published for other platforms. Returning the intrinsic directly
lets the trimmer fold each guard to a constant and drop the dead branch.

Also document why the type exists (brevity and encapsulation), the
inline-guard requirement for trimming, and reconcile the stale remark
about JIT branch elision.

Addresses PR #4255 review feedback from @edwardneal.
On modern .NET, OperatingSystem.IsWindows()/IsLinux()/IsMacOS()/IsFreeBSD()
are JIT intrinsics that get constant-folded (and are recognized by the IL
trimmer), so switch the OsConstants properties to them under #if NET.
.NET Framework keeps RuntimeInformation.IsOSPlatform(...) since the
OperatingSystem platform-check helpers do not exist there.

This addresses the automated reviewer feedback that RuntimeInformation
.IsOSPlatform(...) misses the JIT constant-folding optimization on
net8/net9, while preserving the trimming correctness from the prior change.

Addresses PR #4255 review feedback.
Copilot AI review requested due to automatic review settings June 26, 2026 15:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

@paulmedynski paulmedynski marked this pull request as ready for review June 26, 2026 17:15
@paulmedynski paulmedynski requested a review from a team as a code owner June 26, 2026 17:15
@paulmedynski paulmedynski enabled auto-merge (squash) June 26, 2026 17:15
@paulmedynski paulmedynski moved this from In progress to In review in SqlClient Board Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Code Health 💊 Issues/PRs that are targeted to source code quality improvements.

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

5 participants