Skip to content

Use UnsafeAccessorType in System.Private.CoreLib and the BCL #115583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
11e9cd1
Use UnsafeAccessorType in StackFrameHelper
jkoritzinsky May 13, 2025
6166c51
Convert ComActivator to use UnsafeAccessorType
jkoritzinsky May 14, 2025
df05d6b
ConvertDefaultValueAttribute to use UnsafeAccessorType
jkoritzinsky May 14, 2025
22cd4be
Move AppDomain to use UnsafeAccessorType
jkoritzinsky May 14, 2025
7cee5e3
Convert ResourceReader to use UnsafeAccessorType
jkoritzinsky May 14, 2025
7b696e7
Convert X509ResourceClient to use UnsafeAccessor
jkoritzinsky May 14, 2025
15d985f
Convert TypeDescriptor to use UnsafeAccessorType
jkoritzinsky May 14, 2025
77ba50d
Convert DelegateHelpers to use UnsafeAccessorType
jkoritzinsky May 14, 2025
fe47df6
Convert HttpWebRequest to use UnsafeAccessorType
jkoritzinsky May 14, 2025
d71b334
Convert XmlKeyHelper to use UnsafeAccessorType
jkoritzinsky May 14, 2025
a1f3bc7
Update XmlKeyHelper.cs
jkoritzinsky May 15, 2025
e26d7fa
PR feedback
jkoritzinsky May 15, 2025
84b0be2
Fix trimming test failures
jkoritzinsky May 15, 2025
8973fc5
PR feedback
jkoritzinsky May 16, 2025
a2db757
Migrate over cases where the type name is on a separate line
jkoritzinsky May 16, 2025
bcc5d8d
Convert HttpClientHandler's calls up to various native platform handl…
jkoritzinsky May 16, 2025
7a7bb75
Various fixes
jkoritzinsky May 16, 2025
6945bc8
Fix out param type
jkoritzinsky May 16, 2025
6cfd878
Fix trimming tests
jkoritzinsky May 16, 2025
314b22b
Update AssemblyName.cs
jkoritzinsky May 17, 2025
870775e
Rewrite BinarySerializer path to ensure we still trim out System.Runt…
jkoritzinsky May 20, 2025
4bf9826
suppress warning from bug
jkoritzinsky May 20, 2025
1691219
Remove tests that test removed reflection infra and add librarybuild …
jkoritzinsky May 20, 2025
eb866c9
Fix exception for WindowsPrincipal
jkoritzinsky May 20, 2025
337d504
Fix GetStatusCode return
jkoritzinsky May 21, 2025
e86536f
PR feedback
jkoritzinsky May 23, 2025
9c474cd
PR Feedback
jkoritzinsky May 23, 2025
15a327a
PR feedback
jkoritzinsky May 29, 2025
2faaecf
Merge branch 'main' into unsafe-accessor-type-usage
jkoritzinsky May 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Versioning;
Expand Down Expand Up @@ -616,7 +617,6 @@ public void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock)
[RequiresUnreferencedCode("Built-in COM support is not trim compatible", Url = "https://aka.ms/dotnet-illink/com")]
private sealed class LicenseClassFactory : IClassFactory2
{
private readonly LicenseInteropProxy _licenseProxy = new LicenseInteropProxy();
private readonly Guid _classId;

[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicConstructors)]
Expand All @@ -643,7 +643,7 @@ public void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock)

public void GetLicInfo(ref LICINFO licInfo)
{
_licenseProxy.GetLicInfo(_classType, out bool runtimeKeyAvail, out bool licVerified);
LicenseInteropProxy.GetLicInfo(_classType, out bool runtimeKeyAvail, out bool licVerified);

// The LICINFO is a struct with a DWORD size field and two BOOL fields. Each BOOL
// is typedef'd from a DWORD, therefore the size is manually computed as below.
Expand All @@ -654,7 +654,7 @@ public void GetLicInfo(ref LICINFO licInfo)

public void RequestLicKey(int dwReserved, [MarshalAs(UnmanagedType.BStr)] out string pBstrKey)
{
pBstrKey = _licenseProxy.RequestLicKey(_classType);
pBstrKey = LicenseInteropProxy.RequestLicKey(_classType);
}

public void CreateInstanceLic(
Expand All @@ -677,7 +677,7 @@ private void CreateInstanceInner(
{
var interfaceType = BasicClassFactory.CreateValidatedInterfaceType(_classType, ref riid, pUnkOuter);

object obj = _licenseProxy.AllocateAndValidateLicense(_classType, key, isDesignTime);
object obj = LicenseInteropProxy.AllocateAndValidateLicense(_classType, key, isDesignTime);
if (pUnkOuter != null)
{
obj = BasicClassFactory.CreateAggregatedObject(pUnkOuter, obj);
Expand All @@ -699,51 +699,68 @@ internal sealed class LicenseInteropProxy
private static readonly Type? s_licenseAttrType = Type.GetType("System.ComponentModel.LicenseProviderAttribute, System.ComponentModel.TypeConverter", throwOnError: false);
private static readonly Type? s_licenseExceptionType = Type.GetType("System.ComponentModel.LicenseException, System.ComponentModel.TypeConverter", throwOnError: false);

// LicenseManager
private readonly MethodInfo _createWithContext;

// LicenseInteropHelper
private readonly MethodInfo _validateTypeAndReturnDetails;
private readonly MethodInfo _getCurrentContextInfo;

// CLRLicenseContext
private readonly MethodInfo _createDesignContext;
private readonly MethodInfo _createRuntimeContext;

// LicenseContext
private readonly MethodInfo _setSavedLicenseKey;

[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
private readonly Type _licInfoHelper;

private readonly MethodInfo _licInfoHelperContains;
private const string LicenseManagerTypeName = "System.ComponentModel.LicenseManager, System.ComponentModel.TypeConverter";
private const string LicenseContextTypeName = "System.ComponentModel.LicenseContext, System.ComponentModel.TypeConverter";
private const string LicenseInteropHelperTypeName = "System.ComponentModel.LicenseManager+LicenseInteropHelper, System.ComponentModel.TypeConverter";
private const string CLRLicenseContextTypeName = "System.ComponentModel.LicenseManager+CLRLicenseContext, System.ComponentModel.TypeConverter";
private const string LicenseRefTypeName = "System.ComponentModel.License&, System.ComponentModel.TypeConverter";
private const string LicInfoHelperLicenseContextTypeName = "System.ComponentModel.LicenseManager+LicInfoHelperLicenseContext, System.ComponentModel.TypeConverter";

// RCW Activation
private object? _licContext;
private Type? _targetRcwType;

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:ReflectionToDynamicallyAccessedMembers",
Justification = "The type parameter to LicenseManager.CreateWithContext method has PublicConstructors annotation. We only invoke this method" +
"from AllocateAndValidateLicense which annotates the value passed in with the same annotation.")]
public LicenseInteropProxy()
{
Type licManager = Type.GetType("System.ComponentModel.LicenseManager, System.ComponentModel.TypeConverter", throwOnError: true)!;

Type licContext = Type.GetType("System.ComponentModel.LicenseContext, System.ComponentModel.TypeConverter", throwOnError: true)!;
_setSavedLicenseKey = licContext.GetMethod("SetSavedLicenseKey", BindingFlags.Instance | BindingFlags.Public)!;
_createWithContext = licManager.GetMethod("CreateWithContext", [typeof(Type), licContext])!;

Type interopHelper = licManager.GetNestedType("LicenseInteropHelper", BindingFlags.NonPublic)!;
_validateTypeAndReturnDetails = interopHelper.GetMethod("ValidateAndRetrieveLicenseDetails", BindingFlags.Static | BindingFlags.Public)!;
_getCurrentContextInfo = interopHelper.GetMethod("GetCurrentContextInfo", BindingFlags.Static | BindingFlags.Public)!;

Type clrLicContext = licManager.GetNestedType("CLRLicenseContext", BindingFlags.NonPublic)!;
_createDesignContext = clrLicContext.GetMethod("CreateDesignContext", BindingFlags.Static | BindingFlags.Public)!;
_createRuntimeContext = clrLicContext.GetMethod("CreateRuntimeContext", BindingFlags.Static | BindingFlags.Public)!;

_licInfoHelper = licManager.GetNestedType("LicInfoHelperLicenseContext", BindingFlags.NonPublic)!;
_licInfoHelperContains = _licInfoHelper.GetMethod("Contains", BindingFlags.Instance | BindingFlags.Public)!;
}
[UnsafeAccessor(UnsafeAccessorKind.Method)]
private static extern void SetSavedLicenseKey(
[UnsafeAccessorType(LicenseContextTypeName)] object licContext,
Type type,
string key);

[UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "Manually validated that the annotations are kept in sync.")]
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod)]
private static extern object? CreateWithContext(
[UnsafeAccessorType(LicenseManagerTypeName)] object? licManager,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type,
[UnsafeAccessorType(LicenseContextTypeName)] object licContext
);

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod)]
private static extern bool ValidateAndRetrieveLicenseDetails(
[UnsafeAccessorType(LicenseInteropHelperTypeName)] object? licInteropHelper,
[UnsafeAccessorType(LicenseContextTypeName)] object? licContext,
Type type,
[UnsafeAccessorType(LicenseRefTypeName)] out object? license,
out string? licenseKey);

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod)]
[return: UnsafeAccessorType(LicenseContextTypeName)]
private static extern object? GetCurrentContextInfo(
[UnsafeAccessorType(LicenseInteropHelperTypeName)] object? licInteropHelper,
Type type,
out bool isDesignTime,
out string? key);

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod)]
[return: UnsafeAccessorType(CLRLicenseContextTypeName)]
private static extern object CreateDesignContext(
[UnsafeAccessorType(CLRLicenseContextTypeName)] object? context,
Type type);

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod)]
[return: UnsafeAccessorType(CLRLicenseContextTypeName)]
private static extern object CreateRuntimeContext(
[UnsafeAccessorType(CLRLicenseContextTypeName)] object? context,
Type type,
string? key);

[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
[return:UnsafeAccessorType(LicInfoHelperLicenseContextTypeName)]
private static extern object CreateLicInfoHelperLicenseContext();

[UnsafeAccessor(UnsafeAccessorKind.Method)]
private static extern bool Contains(
[UnsafeAccessorType(LicInfoHelperLicenseContextTypeName)] object? licInfoHelperContext,
string assemblyName);

// Helper function to create an object from the native side
public static object Create()
Expand All @@ -769,35 +786,35 @@ public static bool HasLicense(Type type)
//
// COM normally doesn't expect this function to fail so this method
// should only throw in the case of a catastrophic error (stack, memory, etc.)
public void GetLicInfo(Type type, out bool runtimeKeyAvail, out bool licVerified)
public static void GetLicInfo(Type type, out bool runtimeKeyAvail, out bool licVerified)
{
runtimeKeyAvail = false;
licVerified = false;

// Types are as follows:
// LicenseContext, Type, out License, out string
object licContext = Activator.CreateInstance(_licInfoHelper)!;
var parameters = new object?[] { licContext, type, /* out */ null, /* out */ null };
bool isValid = (bool)_validateTypeAndReturnDetails.Invoke(null, BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameters, culture: null)!;
object licContext = CreateLicInfoHelperLicenseContext();
bool isValid = ValidateAndRetrieveLicenseDetails(null, licContext, type, out object? license, out _);
if (!isValid)
{
return;
}

var license = (IDisposable?)parameters[2];
if (license != null)
if (license is IDisposable disp)
{
license.Dispose();
// Dispose of the license if it implements IDisposable
// and we are not in design mode. This is a bit of a hack
// but we need to do this to avoid leaking the license.
// The license will be disposed of when the context is
// disposed of.
disp.Dispose();
licVerified = true;
}

parameters = [type.AssemblyQualifiedName];
runtimeKeyAvail = (bool)_licInfoHelperContains.Invoke(licContext, BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameters, culture: null)!;
runtimeKeyAvail = Contains(licContext, type.AssemblyQualifiedName!);
}

// The CLR invokes this whenever a COM client invokes
// IClassFactory2::RequestLicKey on a managed class.
public string RequestLicKey(Type type)
public static string RequestLicKey(Type type)
{
// License will be null, since we passed no instance,
// however we can still retrieve the "first" license
Expand All @@ -806,20 +823,14 @@ public string RequestLicKey(Type type)
// like LicFileLicenseProvider that don't require the
// instance to grant a key.

// Types are as follows:
// LicenseContext, Type, out License, out string
var parameters = new object?[] { /* use global LicenseContext */ null, type, /* out */ null, /* out */ null };
bool isValid = (bool)_validateTypeAndReturnDetails.Invoke(null, BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameters, culture: null)!;
if (!isValid)
if (!ValidateAndRetrieveLicenseDetails(null, null, type, out object? license, out string? licenseKey))
{
throw new COMException(); // E_FAIL
}

((IDisposable?)parameters[2])?.Dispose();

var licenseKey = (string?)parameters[3] ?? throw new COMException(); // E_FAIL
((IDisposable?)license)?.Dispose();

return licenseKey;
return licenseKey ?? throw new COMException(); // E_FAIL
}

// The CLR invokes this whenever a COM client invokes
Expand All @@ -832,25 +843,21 @@ public string RequestLicKey(Type type)
// If we are being entered because of a call to ICF::CreateInstanceLic(),
// "isDesignTime" will be "false" and "key" will point to a non-null
// license key.
public object AllocateAndValidateLicense([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, string? key, bool isDesignTime)
public static object AllocateAndValidateLicense([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, string? key, bool isDesignTime)
{
object?[] parameters;
object? licContext;
object licContext;
if (isDesignTime)
{
parameters = [type];
licContext = _createDesignContext.Invoke(null, BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameters, culture: null);
licContext = CreateDesignContext(null, type);
}
else
{
parameters = [type, key];
licContext = _createRuntimeContext.Invoke(null, BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameters, culture: null);
licContext = CreateRuntimeContext(null, type, key);
}

try
{
parameters = [type, licContext];
return _createWithContext.Invoke(null, BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameters, culture: null)!;
return CreateWithContext(null, type, licContext)!;
}
catch (Exception exception) when (exception.GetType() == s_licenseExceptionType)
{
Expand All @@ -864,14 +871,10 @@ public void GetCurrentContextInfo(RuntimeTypeHandle rth, out bool isDesignTime,
{
Type targetRcwTypeMaybe = Type.GetTypeFromHandle(rth)!;

// Types are as follows:
// Type, out bool, out string -> LicenseContext
var parameters = new object?[] { targetRcwTypeMaybe, /* out */ null, /* out */ null };
_licContext = _getCurrentContextInfo.Invoke(null, BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameters, culture: null);
_licContext = GetCurrentContextInfo(null, targetRcwTypeMaybe, out isDesignTime, out string? key);

_targetRcwType = targetRcwTypeMaybe;
isDesignTime = (bool)parameters[1]!;
bstrKey = Marshal.StringToBSTR((string)parameters[2]!);
bstrKey = Marshal.StringToBSTR((string)key!);
}

// The CLR invokes this when instantiating a licensed COM
Expand All @@ -886,8 +889,8 @@ public void SaveKeyInCurrentContext(IntPtr bstrKey)
}

string key = Marshal.PtrToStringBSTR(bstrKey);
var parameters = new object?[] { _targetRcwType, key };
_setSavedLicenseKey.Invoke(_licContext, BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameters, culture: null);

SetSavedLicenseKey(_licContext!, _targetRcwType!, key);
}
}
}
Loading
Loading