Make authentication provider discovery AOT-safe - #4573
paulmedynski wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR replays and updates the AOT-/trimmer-safe authentication provider discovery work on top of the newer assembly-signing stack by (1) moving the auth-provider registry into the Abstractions assembly, (2) isolating reflection-based discovery behind a feature switch + trimmer substitutions, and (3) adding/reshaping tests and an AOT compatibility tool to validate trimming behavior.
Changes:
- Introduces
AuthenticationBootstrapperto lazily seed providers from app.config and the optional Azure extension, gated byMicrosoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscoveryfor AOT/trimming. - Moves the provider registry into
Microsoft.Data.SqlClient.Extensions.Abstractionsand rewiresSqlAuthenticationProvider.GetProvider/SetProviderto use it directly (removing the Abstractions→SqlClient reflection bridge). - Adds/updates unit tests and a
tools/AotCompatibilityapp to validate NativeAOT publish and trimming of the reflection-based discovery path.
Reviewed changes
Copilot reviewed 53 out of 54 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tools/AotCompatibility/README.md | Documents what the AOT compatibility tool validates and how to run/publish it. |
| tools/AotCompatibility/Program.cs | Implements runtime checks and ILC map-file verification for trimming behavior. |
| tools/AotCompatibility/Directory.Packages.props | Enables central package management for the tool without specifying versions (project-mode). |
| tools/AotCompatibility/Directory.Build.props | Prevents inheriting repo-wide Directory.Build.props settings for the tool. |
| tools/AotCompatibility/AotCompatibility.csproj | Defines the AOT/trimming validation app and sets the runtime host configuration option for the feature switch. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs | Removes legacy manager-based tests replaced by bootstrapper/registry coverage. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs | Asserts the new internal reflection-discovery switch default directly. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ILLinkSubstitutionsTests.cs | Adds tests ensuring the unified ILLink substitution resource is embedded on non-net462 TFMs. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/DummySqlAuthenticationProvider.cs | Adds a dummy provider used by net462 UnitTests app.config-based registration tests. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AuthenticationBootstrapperTests.cs | Adds core bootstrapper tests (Azure extension absent) covering config and constructor-selection logic via stubs. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj | Ensures net462 app.config is copied to output for config-driven provider tests. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/app.config | Adds UnitTests app.config configuring a dummy provider and bootstrapper settings for net462 tests. |
| src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs | Removes legacy functional tests tied to the old manager/app.config flow. |
| src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj | Removes copying FunctionalTests app.config for net462 (tests moved/restructured). |
| src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs | Removes functional-tests dummy provider implementation (test moved/rewired). |
| src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config | Removes functional-tests app.config for dummy provider registration. |
| src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs | Removes dummy-provider registration test from FunctionalTests (moved to Azure.Test/global bootstrap tests). |
| src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs | Updates helper to reflect new cached-field naming and adds helper for the new internal switch. |
| src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.xml | Unifies trimmer substitutions for auth-provider discovery and managed-networking switch in one cross-platform file. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs | Adds the new reflection-discovery switch and refactors managed-networking switch into a trimmer substitution target property. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs | Ensures authentication bootstrap runs before resolving a provider on the fed-auth path. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AuthenticationBootstrapper.cs | Replaces legacy manager with a lazy bootstrapper that loads config + Azure extension behind a feature switch and AOT annotations. |
| src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj | Embeds the unified ILLink substitution resource for all non-net462 TFMs. |
| src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs | Adds a note clarifying ref assemblies intentionally omit nullable annotations. |
| src/Microsoft.Data.SqlClient.Extensions/Azure/test/WamBrokerTests.cs | Updates comments/collection usage and removes a global-state mutation test that’s no longer needed in this form. |
| src/Microsoft.Data.SqlClient.Extensions/Azure/test/SqlAuthenticationProviderGlobalCollection.cs | Renames the serialized collection definition used for tests mutating the global registry. |
| src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs | Removes legacy “default provider installed” test tied to old manager static-init behavior. |
| src/Microsoft.Data.SqlClient.Extensions/Azure/test/AuthenticationBootstrapperTests.cs | Adds Azure-present, non-global bootstrapper tests (constructor selection via reflection access). |
| src/Microsoft.Data.SqlClient.Extensions/Azure/test/AuthenticationBootstrapperGlobalTests.cs | Adds Azure-present tests that force the full bootstrap and verify global provider registration. |
| src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs | Moves these tests into the global serialized collection to account for shared registry mutations. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs | Updates tests to validate the public API delegates to the shared registry instance (no SqlClient assembly required). |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/AuthenticationProviderRegistryTest.cs | Adds comprehensive isolated tests for registry behavior (overrides, permanence, lifecycle callbacks). |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.zh-Hant.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.zh-Hans.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.tr.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.ru.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.resx | Adds the invariant resource string(s) used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.pt-BR.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.pl.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.ko.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.ja.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.it.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.fr.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.es.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.Designer.cs | Adds the strongly-typed resource accessor for Abstractions strings. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.de.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.cs.resx | Adds localized string resources used by Abstractions exception messages. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs | Removes the Abstractions→SqlClient reflection bridge for provider get/set. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs | Rewires GetProvider/SetProvider to call the shared registry directly. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/IsExternalInit.cs | Adds an IsExternalInit polyfill for record/init support on non-NET TFMs. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AuthenticationProviderRegistry.cs | Introduces the shared concurrent provider registry with permanence semantics and lifecycle callback handling. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj | Updates InternalsVisibleTo to include SqlClient and UnitTests under signing conditions. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml | Updates docs to reflect concurrency/idempotency expectations and clarifies canonical Get/Set usage. |
| aot-auth-provider-proposal.md | Adds/updates design proposal documentation for the AOT-safe registration approach. |
Files not reviewed (1)
- src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.Designer.cs: Generated file
Suppressed comments (2)
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AuthenticationBootstrapper.cs:280
- These trace calls build interpolated/concatenated strings at the call site, which allocates even when tracing is disabled. Since SqlClientEventSource.TryTraceEvent only formats when enabled, switch to a parameterized call and compute expensive arguments (like the token string) only inside an IsTraceEnabled() guard.
src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs:388 - The remarks still refer to the old cached-field name (s_useManagedNetworking). After the rename to s_useManagedNetworkingOnWindows, this comment is now inaccurate and could mislead future edits to this reflection-based helper.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
96c9759 to
78b8175
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The AOT compatibility tool currently reports an incorrect effective default when the AppContext switch is absent, which can mislead validation runs and should be corrected before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.Designer.cs: Generated file
Suppressed comments (1)
src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs:388
- The XML doc comment still refers to the old private field name
s_useManagedNetworking, but the implementation was renamed tos_useManagedNetworkingOnWindows. Also, the field is now compiled for all#if NETbuilds; it’s only the behavior that’s Windows-guarded, so the remark about the reflection lookup failing on non-Windows is no longer accurate.
- Files reviewed: 54/55 changed files
- Comments generated: 1
- Review effort level: Lite
| bool switchFound = AppContext.TryGetSwitch( | ||
| "Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery", | ||
| out bool reflectionEnabled); |
| SqlAuthenticationMethod authenticationMethod) | ||
| { | ||
| return Internal.GetProvider(authenticationMethod); | ||
| return AuthenticationProviderRegistry.Instance.GetProvider(authenticationMethod); |
There was a problem hiding this comment.
🤖 Bootstrap before reading or writing the shared registry
GetProvider and SetProvider now access the initially empty registry without triggering discovery. If an application calls SetProvider before its first federated connection, AuthenticationBootstrapper.LoadAzureExtensionProvider later calls the overridable Registry.SetProvider path for every Entra method and replaces the application’s provider. Previously, entering SqlAuthenticationProviderManager.SetProvider initialized configuration and Azure defaults first, so the explicit provider remained the final override. The same change makes GetProvider return null before first authentication even when the Azure extension is available. Preserve the old initialization and precedence contract, either by bootstrapping before public registry access or by making default discovery add only missing providers.
| @@ -0,0 +1,123 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
There was a problem hiding this comment.
I don't think these files will be picked up by our localization flow, so any additions/changes won't receive updates. It only knows how to update the resx files at the existing location (https://sqlclientdrivers.visualstudio.com/ADO.Net/_git/Microsoft.Data.SqlClient?path=/Localize/LocProject.json). It looks like we can add an additional location? but it will take a bit of experimentation.
If you don't want to do that now, a cheap alternative without modifying the localization pipeline could be to copy the resource files over to this package at build time.
| /// <see cref="SetPermanentProvider"/>) and therefore not overridable by <see cref="SetProvider"/>. | ||
| /// </summary> | ||
| /// <param name="Provider">The registered provider. Never <see langword="null"/>.</param> | ||
| /// <param name="IsPermanent">Whether the provider must not be overridden by <see cref="SetProvider"/>.</param> |
There was a problem hiding this comment.
@paulmedynski "True if the provider ..."
|
|
||
| /// <summary> | ||
| /// Initializes a new, empty registry. Production code uses the shared <see cref="Instance"/>; | ||
| /// the constructor is exposed to tests so they can exercise registry behavior in isolation. |
There was a problem hiding this comment.
@paulmedynski "Instance. This constructor is exposed so tests can exercise ..."
| /// <summary> | ||
| /// Registers an overridable provider for the given authentication method. | ||
| /// </summary> | ||
| /// <returns> |
| /// (permanent or not). Only <see cref="SetProvider"/> is blocked by an existing permanent | ||
| /// provider; <see cref="SetPermanentProvider"/> itself always overwrites. | ||
| /// </para> | ||
| /// </remarks> |
| // That is why this class lives in the [Collection("SqlAuthenticationProviderGlobal")] collection, | ||
| // which serializes it with the other tests that mutate the shared registry. | ||
| // | ||
| // TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/41888): |
There was a problem hiding this comment.
@paulmedynski I think this prerequisite work is done.
| // from Microsoft.Data.SqlClient. This call has no global side effects -- it just returns a new | ||
| // provider instance. | ||
| // | ||
| // TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/41888): |
There was a problem hiding this comment.
@paulmedynski I think we have IVT for this now.
| Assert.Throws<ArgumentNullException>( | ||
| () => new ActiveDirectoryAuthenticationProvider((ActiveDirectoryAuthenticationProviderOptions)null!)); | ||
| } | ||
|
|
There was a problem hiding this comment.
This coverage was split across several other tests:
- WAM-broker construction/configuration behavior is covered in:
src/Microsoft.Data.SqlClient.Extensions/Azure/test/AuthenticationBootstrapperTests.cs- These verify that
UseWamBroker = trueis passed to the options constructor and preserved on the resultingActiveDirectoryAuthenticationProvider.
- Registration preserves the provider instance is covered in:
src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/AuthenticationProviderRegistryTest.csSetProvider()followed byGetProvider()asserts the same instance is returned.SqlAuthenticationProviderTest.PublicApi_DelegatesToSharedInstance()also verifies the public API and registry observe the same instance.
- Provider installation for all Active Directory methods is covered in:
src/Microsoft.Data.SqlClient.Extensions/Azure/test/AuthenticationBootstrapperGlobalTests.cs
There was a problem hiding this comment.
@paulmedynski Move this beside our other design docs.
|
|
||
| // CreateAzureAuthenticationProvider tests ---------------------------------------------- | ||
| // | ||
| // Each Stub* container mimics one shape the real Azure extension might expose: |
There was a problem hiding this comment.
@paulmedynski Do we need this now that we release all SqlClient family packages together?
78b8175 to
e89a9d7
Compare
Even with the registry moved into Abstractions, the driver still discovers app.config providers and the Azure extension via reflection (Type.GetType, Assembly.Load, Activator.CreateInstance). Under NativeAOT the trimmer has no static reference to follow, so it removes the Azure extension and every Active Directory authentication method silently loses its provider. None of the reflection carried trim annotations, so the compiler emitted no warnings. Add a feature switch that lets the trimmer remove the reflection entirely: - Add the EnableReflectionBasedAuthenticationProviderDiscovery app context switch (default true) and guard the bootstrapper's discovery with it. On .NET 9+ it carries [FeatureSwitchDefinition]; ILLink.Substitutions.xml provides the same substitution on .NET 8. - Annotate LoadConfiguration and LoadAzureExtensionProvider with [RequiresUnreferencedCode] and [RequiresDynamicCode] so trim analysis warns even when the switch is left enabled. AOT applications set the switch to false at publish time via RuntimeHostConfigurationOption with Trim="true". The trimmer then folds the guard to a constant false and eliminates the reflection code, and the app registers its providers explicitly with SqlAuthenticationProvider.SetProvider. Also split UseManagedNetworking into a platform guard plus a UseManagedNetworkingOnWindows substitution target. The old property mixed the OS check with the switch read, so the existing substitution could only be embedded conditionally; with the guard hoisted out, the substitution is safe on every platform and ILLink.Substitutions.xml is now unconditional for .NET. Add tools/AotCompatibility, a standalone publish harness that verifies the substitution actually removes LoadAzureExtensionProvider from the native image.
e89a9d7 to
e52b2b5
Compare
There was a problem hiding this comment.
🟡 Changes recommended
There are a few correctness/documentation mismatches in the new AOT verification harness and switch documentation that should be fixed to avoid misleading diagnostics and false-green validation.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs:388
- The remarks for UseManagedNetworking still refer to the old backing field name
s_useManagedNetworking, but the implementation now usess_useManagedNetworkingOnWindows. Leaving this stale reference makes the guidance misleading for future maintainers (and for anyone updating the reflection helper again).
/// The underlying s_useManagedNetworking field only exists in the SqlClient
/// assembly when it is built for .NET on Windows. The getter reads the
/// public LocalAppContextSwitches.UseManagedNetworking property, which
/// exists on all platforms and is safe to read anywhere. Only the setter
/// relies on the s_useManagedNetworking field, so callers must set this
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs:695
- The summary for EnableReflectionBasedAuthenticationProviderDiscovery mentions only Azure extension loading, but the switch is also used to guard config-based provider/initializer loading. The XML summary should reflect the full behavior to prevent confusion about what is disabled under AOT trimming.
/// <summary>
/// When set to true (the default), AuthenticationBootstrapper will
/// use reflection (Assembly.Load + Activator.CreateInstance) to discover
/// and load the Azure extension authentication provider at startup.
///
- Files reviewed: 13/13 changed files
- Comments generated: 3
- Review effort level: Lite
| { | ||
| Console.ForegroundColor = ConsoleColor.Yellow; | ||
| Console.WriteLine(" WARN: Reflection code absent despite discovery being enabled."); | ||
| Console.ResetColor(); | ||
| } |
| _sqlAuthLogger.LogInfo( | ||
| nameof(AuthenticationBootstrapper), | ||
| "Ctor", | ||
| "Reflection-based provider discovery is disabled; skipping app.config " + | ||
| "authentication provider configuration."); |
| /// <summary> | ||
| /// The name of the app context switch that controls whether | ||
| /// AuthenticationBootstrapper uses reflection to discover and load | ||
| /// the Azure extension authentication provider at startup. | ||
| /// </summary> |
Description
Makes SqlClient's authentication provider discovery safe for NativeAOT.
Stacked on #4670, which moves the provider registry into Abstractions and removes the Abstractions→SqlClient reflection. This PR handles the other reflection edge: SqlClient→Azure extension discovery.
The problem
AuthenticationBootstrapperdiscoversapp.configproviders and the Azure extension viaType.GetType,Assembly.Load, andActivator.CreateInstance. Under NativeAOT the trimmer has no static reference to follow, so it trims the Azure extension and every Active Directory authentication method silently loses its provider (#4193). None of the reflection carried trim annotations, so the compiler emitted no warnings.The change
EnableReflectionBasedAuthenticationProviderDiscoveryapp context switch (defaulttrue), guarding the bootstrapper's discovery. On .NET 9+ it carries[FeatureSwitchDefinition];ILLink.Substitutions.xmlprovides the same substitution on .NET 8.[RequiresUnreferencedCode]/[RequiresDynamicCode]onLoadConfigurationandLoadAzureExtensionProvider, so trim analysis warns even when the switch is left enabled.tools/AotCompatibility— a standalone publish harness that verifies the substitution actually removesLoadAzureExtensionProviderfrom the native image.An AOT app opts in at publish time:
The trimmer folds the guard to a constant
false, eliminates the dead branch, and removes the reachable reflection code. The app then registers providers explicitly withSqlAuthenticationProvider.SetProvider.Also:
UseManagedNetworkingsplitUseManagedNetworkingmixed the OS check with the switch read, so the existing SNI substitution could only be embedded conditionally. Hoisting the platform guard out:leaves
UseManagedNetworkingOnWindowsas a pure substitution target. The guard makes the substitution safe on every platform, soILLink.Substitutions.xmlis now unconditional for .NET.Zero-code-change AOT is not achievable
One line of startup registration is required — the trimmer has no reason to keep
ActiveDirectoryAuthenticationProviderwithout a static reference from the app. Rejected alternatives:[ModuleInitializer]in the extension (initializers of trimmed assemblies don't run — same chicken-and-egg), a source generator (viable but far more to ship and maintain), and a root descriptor XML (preserves the type but leaves theAssembly.Loadpath intact, which defeats the point). This matches the established .NET pattern, e.g.JsonSerializerContextregistration under AOT.API changes
None. The switch is internal; the trimmer contract is the documented
RuntimeHostConfigurationOptionname.Issues
Fixes #4193 (together with #4670).
Testing
ILLinkSubstitutionsTests.cs— asserts the substitution XML targets property getters that actually exist, so a rename can't silently break trimming.LocalAppContextSwitchesTest.cs/LocalAppContextSwitchesHelper.csfor the new switch and thes_useManagedNetworkingOnWindowsrename.tools/AotCompatibility— publishes a NativeAOT app and inspects the output forLoadAzureExtensionProvider, confirming the feature switch removes it. See its README for expected output in both configurations.Validation on this branch:
Microsoft.Data.SqlClient+ UnitTests build clean fornet9.0, 0 warnings.SimulatedServerTests.ConnectionTests.IntegratedAuthConnectionTest, "Cannot generate SSPI context") is pre-existing and environmental — no Kerberos on the Linux test host.Conflict resolutions worth reviewing
Replaying onto current
mainrequired resolving conflicts introduced by work that landed after the AOT branch point:LocalAppContextSwitches.cs— kept the AOT two-property design and thes_useManagedNetworkingOnWindowsfield; retained main's default-value documentation.LocalAppContextSwitchesHelper.cs— kept main'sGetLocalAppContextSwitchesType()refactor and runtime OS check, adopting the AOT field rename.LocalAppContextSwitchesTest.cs— kept main'sswitchesHelperassertions (includingUseLegacyIdleTimeoutBehavior) and added the new switch assertion.Stack
Previously this PR combined the registry relocation with the AOT work. The structural refactor is now split out into #4670 and this PR contains only the AOT changes. Supersedes the AOT content of #4348.