-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
[API Proposal]: trim-safe RuntimeHelpers.RunClassConstructor(Type) overload #103679
Comments
Tagging subscribers to 'linkable-framework': @eerhardt, @vitek-karas, @LakshanF, @sbomer, @joperezr, @marek-safar |
Tagging subscribers to this area: @dotnet/area-system-reflection |
This new |
@sbomer and @MichalStrehovsky for review |
Can you make it explicit by introducing Static constructors are meant to be internal type implementation details. It is an anti-pattern to use them as part of the public type contract. |
I think I remember asking the same a while back and was told that doing so wasn't possible, but I can't recall the exact reason right now (cc. @manodasanW do you remember?). I know at the very least there was a concern about the fact that the type might often be public sealed class MyControl : UserControl
{
public static readonly DependencyProperty FooProperty = DependencyProperty.Register(
"Foo",
typeof(int),
typeof(MyControl),
null);
} |
You can either generate explicit constructor or you can do a dummy access of one of the static fields in the Initialize method. public sealed class MyControl : UserControl
{
public static readonly DependencyProperty FooProperty = DependencyProperty.Register(
"Foo",
typeof(int),
typeof(MyControl),
null);
static MyControl()
{
}
public static void Initialize()
{
}
} |
How does this work for: public class ControlBase : UserControl
{
public static readonly DependencyProperty FooProperty = DependencyProperty.Register(
"Foo",
typeof(int),
typeof(MyControl),
null);
}
public class ControlDerived : ControlBase { } Does XAML recurse from ControlDerived and call RunClassConstructor on ControlBase? (Because RunClassConstructor on ControlDerived will not run the base cctor.) If so, the proposed extension would likely not help. I would not expect it to keep things recursively. That said, keeping cctor on a single type should be achievable with the existing API surface, it's just a trimming/AOT mini-feature. DynamicallyAccessedMemberKinds.NonPublicConstructor already includes the static constructor (because that's how reflection APIs are structured as well). using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
Run(typeof(Foo));
static void Run([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type t)
{
Console.WriteLine(t.GetConstructor(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static, []));
RuntimeHelpers.RunClassConstructor(t.TypeHandle);
}
class Foo
{
static int i = Init();
private static int Init()
{
Console.WriteLine("Hello");
return 42;
}
} This will print
in a trimmed app but there's a trimming warning. The trimming warning should be fixable (was briefly discussed in dotnet/linker#1121 (comment)). There's a gotcha however. In native AOT, the static constructor is reflection-visible, but not runnable. So the above program would print:
As part of fixing the trimming warning, we'd need to fix this up too (either drop the "static constructor is reflection-visible but not |
XAML will track all types, including base types, though I can't say what the exact logic it uses to trigger those constructors is off the top of my head. @manodasanW do you know by any chance if it'll take care of invoking base constructors too?
This... Is the part that I find the most interesting. Just to triple check I'm reading this correctly, are you saying that no |
What doesn't work is calling |
(Suppressing the warning doesn't make it magically work. The suppression in XAML is invalid, including for PublishTrimmed. I rarely see a valid suppression in code outside dotnet/runtime repo. It's pretty much always a bug.) |
Bear with me, I'm not entirely sure I'm following 😅
How could you ever not have static analysis warnings for
That part I get. But in your previous example, unless I'm reading it wrong, you're just calling
Right, so it seems either way we should find some fix before claiming "we officially support AOT". Ignoring the fact that by what you said it would seem like we technically shouldn't be able to say we even officially support trimming on its own then... |
This is what I meant with "RuntimeHelpers.RunClassConstructor is not implemented on top of reflection" - the NonPublishConstructors annotation will keep the metadata for the cctor, but it's just the metadata and not the method body. And even if we had the method body, RunClassConstructor wouldn't use it because it instead uses the native data structures that are used to trigger the cctor for real. It's an implementation detail, but important to know if one were to suppress the warning (one cannot suppress it due to this implementation detail). It's fixable, it's just work, same as making sure a suppression is not needed in the first place. The "cctor logic is disconnected from reflection" behavior was inherited from .NET Native where we probably had it due to reflection blocking, but we no longer have reflection blocking and this could be cleaned up. But it's only worth the effort if we're fixing the trimming analysis for RunClassConstructors to also intrinsically accept System.Type that was annotated as NonPublicConstructors. Right now inability to reflection-invoke cctors is just a bullet point at #69919. |
Tagging subscribers to this area: @dotnet/area-system-runtime |
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas |
@MichalStrehovsky it's kinda interesting, because the System XAML compiler also generates that I'm thinking perhaps a good enough way to fix this (at least for now) would be to update the XAML compiler to explicitly pass a delegate for each type, that calls I guess the real issue is the fact we rely on static constructors in the first place, but that doesn't seem fixable... |
The .NET Native ILC parses XAML, including decompiling and analyzing XBF files. I would not be surprised if this is handled by that, or it only works by pure luck.
That would work. But also I'm making a fix for .NET 9 (delete a .NET Native leftover around how static constructors are tracked: #103946, fix the analysis not to warn in case the
The |
Background and motivation
In the XAML compiler, when targeting modern .NET, we need to invoke
RuntimeHelpers.RunClassConstructor
to make sure that the static constructor for a given type is initialized before some other code runs. Most scenarios for this revolve around dependency properties, which in some cases need to be initialized and registered in the WinRT runtime before some other code runs (eg. before the activation factory for a given type is retrieved). To make this trim-safe, the lifted XAML compiler was updated with a bunch of[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
annotations on all such types, and a suppression on the method callingRuntimeHelpers.RunClassConstructor
with these types.You can see the use in the XAML compiler here.
However, talking with @agocke and @SingleAccretion today, they pointed out that this pattern is not supported, and it's not guaranteed to work. So this proposal is for a safe, annotated overload of the same method, taking a
Type
instance instead.API Proposal
API Usage
The text was updated successfully, but these errors were encountered: