Bug Report
UnsafeAccessorType reflection will bypass Native Profiler assembly redirection for DiagnosticSource (v11+) on .NET 10+ (post PR #4783)
Summary
The UnsafeAccessorTypeAttribute (introduced in .NET 10) bypasses our native profiler's assembly redirection mechanism, which will cause type and state drift on .NET 10+ when OpenTelemetry upgrades to System.Diagnostics.DiagnosticSource 11.
This issue is not currently reproducible but will manifest once we switch to DS 11 after .NET 11 is released.
Background: Native Profiler Assembly Redirection
Our assembly conflict resolution approach (introduced in PR #4783) works as follows:
- Native profiler (
cor_profiler.cpp) redirects AssemblyRefs for key dependencies using the redirection map in assembly_redirection_net.h
- Managed AssemblyResolver subscribes to
DefaultALC.Resolving event to handle redirected dependencies
- When a higher version than the app originally referenced is needed, we load it into a custom ALC to avoid runtime crashes
Goal: Prevent version conflicts by ensuring all code paths reference our redirected (higher) dependency versions.
The Problem: UnsafeAccessor Bypasses Redirection
.NET 10 introduced UnsafeAccessorTypeAttribute, a JIT-level reflection mechanism:
- Native redirection of
AssemblyRef does not affect assembly resolution during reflection
- Unlike traditional managed reflection, it performs JIT-level type resolution that sticks to the ALC context of the requesting assembly, bypassing
CurrentContextualReflectionContext (so customALC.EnterContextualReflection() has no effect)
Why UnsafeAccessorType breaks Native Assembly redirection: When the runtime is requested to load an assembly from Default ALC and can find it (e.g. in TPA list), it loads immediately to Default ALC—there is no opportunity to interfere.
General impact: Any assembly in Default ALC using UnsafeAccessorType to reference our redirected dependencies will cause drift when our version is higher than what originally referenced.
Why This Is Especially Critical for OpenTelemetry
The .NET runtime itself (System.Private.CoreLib) uses UnsafeAccessorType during initialization to access types from DiagnosticSource:
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetInstance")]
[return: UnsafeAccessorType("System.Diagnostics.Metrics.MetricsEventSource, System.Diagnostics.DiagnosticSource")]
static extern object GetInstance(
[UnsafeAccessorType("System.Diagnostics.Metrics.MetricsEventSource, System.Diagnostics.DiagnosticSource")] object? _);
Source: EventSourceInitHelper in System.Private.CoreLib
Why this is critical: OpenTelemetry initialization triggers this runtime code path. DiagnosticSource is foundational to OpenTelemetry and must be loaded exactly once.
Current State and Future Break Scenario
Why not reproducible now:
For .NET 10, we have no DiagnosticSource redirection in assembly_redirection_net.h. This is intentional: DS 10 ships with .NET 10 and satisfies OpenTelemetry's requirements, loading to Default ALC without conflict.
What breaks when .NET 11 releases:
When .NET 11 ships with DS 11, OpenTelemetry will upgrade accordingly. All .NET versions—including .NET 10—will receive a DS 11 redirection.
For .NET 10 applications:
- Runtime (System.Private.CoreLib) loaded in Default ALC uses
UnsafeAccessorType to load MetricsEventSource from DS
- Our AssemblyRef redirection does not apply, and JIT reflection ignores contextual reflection—DS 10 is resolved from TPA and loaded immediately to Default ALC without raising the Resolving event
- Our
AssemblyResolver (via other code paths) loads DS 11 to custom ALC
- Result: Two DS versions across ALCs → type drift and state drift
Proposed Solution
Extend the native profiler to update UnsafeAccessorTypeAttribute assembly references (not just AssemblyRefs) when bumping dependency versions. This ensures UnsafeAccessor resolution respects redirections and triggers the Resolving event handler.
Status: I have a POC that successfully redirects UnsafeAccessorType definition which results in triggering the Resolving event.
Related Issue
This issue is related to: Issue #4924 - UnsafeAccessorType breaks StartupHook isolation. Both stem from UnsafeAccessorType bypassing our assembly conflict resolution mechanisms, but affect differently different deployment modes.
Runtime environment
- OpenTelemetry Automatic Instrumentation version: Discovered during work on PR #4783
- OS: All platforms
- .NET version: .NET 10+ (will manifest when .NET 11 is released)
- Deployment mode: Native Profiler deployment
Bug Report
UnsafeAccessorType reflection will bypass Native Profiler assembly redirection for DiagnosticSource (v11+) on .NET 10+ (post PR #4783)
Summary
The
UnsafeAccessorTypeAttribute(introduced in .NET 10) bypasses our native profiler's assembly redirection mechanism, which will cause type and state drift on .NET 10+ when OpenTelemetry upgrades to System.Diagnostics.DiagnosticSource 11.This issue is not currently reproducible but will manifest once we switch to DS 11 after .NET 11 is released.
Background: Native Profiler Assembly Redirection
Our assembly conflict resolution approach (introduced in PR #4783) works as follows:
cor_profiler.cpp) redirectsAssemblyRefsfor key dependencies using the redirection map inassembly_redirection_net.hDefaultALC.Resolvingevent to handle redirected dependenciesGoal: Prevent version conflicts by ensuring all code paths reference our redirected (higher) dependency versions.
The Problem: UnsafeAccessor Bypasses Redirection
.NET 10 introduced
UnsafeAccessorTypeAttribute, a JIT-level reflection mechanism:AssemblyRefdoes not affect assembly resolution during reflectionCurrentContextualReflectionContext(socustomALC.EnterContextualReflection()has no effect)Why UnsafeAccessorType breaks Native Assembly redirection: When the runtime is requested to load an assembly from Default ALC and can find it (e.g. in TPA list), it loads immediately to Default ALC—there is no opportunity to interfere.
General impact: Any assembly in Default ALC using
UnsafeAccessorTypeto reference our redirected dependencies will cause drift when our version is higher than what originally referenced.Why This Is Especially Critical for OpenTelemetry
The .NET runtime itself (System.Private.CoreLib) uses
UnsafeAccessorTypeduring initialization to access types from DiagnosticSource:Source: EventSourceInitHelper in System.Private.CoreLib
Why this is critical: OpenTelemetry initialization triggers this runtime code path.
DiagnosticSourceis foundational to OpenTelemetry and must be loaded exactly once.Current State and Future Break Scenario
Why not reproducible now:
For .NET 10, we have no DiagnosticSource redirection in
assembly_redirection_net.h. This is intentional: DS 10 ships with .NET 10 and satisfies OpenTelemetry's requirements, loading to Default ALC without conflict.What breaks when .NET 11 releases:
When .NET 11 ships with DS 11, OpenTelemetry will upgrade accordingly. All .NET versions—including .NET 10—will receive a DS 11 redirection.
For .NET 10 applications:
UnsafeAccessorTypeto loadMetricsEventSourcefrom DSAssemblyResolver(via other code paths) loads DS 11 to custom ALCProposed Solution
Extend the native profiler to update
UnsafeAccessorTypeAttributeassembly references (not just AssemblyRefs) when bumping dependency versions. This ensures UnsafeAccessor resolution respects redirections and triggers theResolvingevent handler.Status: I have a POC that successfully redirects UnsafeAccessorType definition which results in triggering the Resolving event.
Related Issue
This issue is related to: Issue #4924 - UnsafeAccessorType breaks StartupHook isolation. Both stem from
UnsafeAccessorTypebypassing our assembly conflict resolution mechanisms, but affect differently different deployment modes.Runtime environment