Skip to content

Commit

Permalink
Make dynamic XAML assembly ignore access checks
Browse files Browse the repository at this point in the history
Fixes #11
  • Loading branch information
Kir-Antipov committed Nov 25, 2024
1 parent 3596a77 commit cadeec0
Showing 1 changed file with 59 additions and 0 deletions.
59 changes: 59 additions & 0 deletions src/HotAvalonia/Helpers/AvaloniaControlHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Emit;
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
Expand All @@ -18,13 +19,71 @@ internal static class AvaloniaControlHelper
/// </summary>
private static readonly FieldInfo? s_stylesAppliedField;

/// <summary>
/// The `Type` property of the <c>XamlX.IL.SreTypeSystem.SreType</c> class.
/// </summary>
private static readonly PropertyInfo? s_xamlTypeProperty;

/// <summary>
/// The <see cref="IInjection"/> instance responsible for injecting the
/// <see cref="OnNewSreMethodBuilder(MethodBuilder, IEnumerable{object})"/>
/// callback.
/// </summary>
[SuppressMessage("CodeQuality", "IDE0052", Justification = "Injections must be kept alive.")]
private static readonly IInjection? s_sreMethodBuilderInjection;

/// <summary>
/// Initializes static members of the <see cref="AvaloniaControlHelper"/> class.
/// </summary>
static AvaloniaControlHelper()
{
FieldInfo? stylesAppliedField = typeof(StyledElement).GetField("_stylesApplied", BindingFlags.NonPublic | BindingFlags.Instance);
s_stylesAppliedField = stylesAppliedField?.FieldType == typeof(bool) ? stylesAppliedField : null;

Assembly xamlLoaderAssembly = typeof(AvaloniaRuntimeXamlLoader).Assembly;
Type? sreType = xamlLoaderAssembly.GetType("XamlX.IL.SreTypeSystem+SreType");
s_xamlTypeProperty = sreType?.GetProperty("Type");

BindingFlags ctorFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
Type? sreMethodBuilder = xamlLoaderAssembly.GetType("XamlX.IL.SreTypeSystem+SreTypeBuilder+SreMethodBuilder");
ConstructorInfo? sreMethodBuilderCtor = sreMethodBuilder?.GetConstructors(ctorFlags).FirstOrDefault(x => x.GetParameters().Length > 1);
if (sreMethodBuilderCtor is not null)
s_sreMethodBuilderInjection = CallbackInjector.Inject(sreMethodBuilderCtor, OnNewSreMethodBuilder);
}

/// <summary>
/// Fixes newly created <c>XamlX.IL.SreTypeSystem.SreTypeBuilder.SreMethodBuilder</c>
/// instances.
/// </summary>
/// <param name="methodBuilder">
/// The <see cref="MethodBuilder"/> used to create the
/// <c>SreMethodBuilder</c> instance.
/// </param>
/// <param name="parameters">
/// The parameters used to create the <c>SreMethodBuilder</c>
/// instance represented by a collection of <c>SreType</c> objects.
/// </param>
private static void OnNewSreMethodBuilder(MethodBuilder methodBuilder, IEnumerable<object> parameters)
{
// `Avalonia.Markup.Xaml.Loader` does not handle scenarios where
// the control population logic needs to reference private members,
// which are commonly used for subscribing to events (e.g., `Click`,
// `TextChanged`, etc.). To circumvent this problem, we need this
// callback to track the creation of dynamic `Populate` methods and
// patch their containing assembly with `IgnoresAccessChecksToAttribute`.
if (!AvaloniaRuntimeXamlScanner.IsDynamicPopulateMethod(methodBuilder))
return;

if (methodBuilder.DeclaringType?.Assembly is not AssemblyBuilder assembly)
return;

IEnumerable<Type> parameterTypes = parameters
.Where(static x => x is not null && x.GetType() == s_xamlTypeProperty?.DeclaringType)
.Select(static x => (Type?)s_xamlTypeProperty?.GetValue(x))
.Where(static x => x is not null)!;

foreach (Type parameterType in parameterTypes)
assembly.AllowAccessTo(parameterType);
}

/// <summary>
Expand Down

0 comments on commit cadeec0

Please sign in to comment.