-
Notifications
You must be signed in to change notification settings - Fork 533
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
[Mono.Android] fix trimming warnings, part 2 #8758
[Mono.Android] fix trimming warnings, part 2 #8758
Conversation
This fixes the next set of trimmer warnings found via: <IsTrimmable>true</IsTrimmable> <EnableAotAnalyzer>true</EnableAotAnalyzer> ~~ JavaObjectExtensions ~~ `JavaCast<T>()` now requires `PublicConstructors` and `NonPublicConstructors` because of `Activator.CreateInstance<T>()`. This change bubbles up to various other types that have a `Find*ById<T>()` method: * `Activity` * `Dialog` * `FragmentManager` * `View` * `Window` `GetInvokerType()` also has suppressions around `Assembly.GetType()` and `Type.MakeGenericType()`. We track this for the future at: dotnet#8724 ~~ AndroidRuntime ~~ Update `[DynamicallyAccessedMembers]` based on changes to `RegisterNativeMembers` in: dotnet/java-interop@b8f6f88 ~~ JNINativeWrapper ~~ `$(EnableAotAnalyzer)` found usage of `DynamicMethod`. Suppress for now, as we track this for the future at: dotnet#8724 ~~ ResourceIdManager ~~ Usage of `Type.GetMethod ("UpdateIdValues")` leads to decoration of `[ResourceDesignerAttribute]` with: [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] public string FullName { get; set; } I also had to suppress warnings around `Assembly.GetType()`. This *should* be OK, as `Resource.designer.cs` is always in the "root assembly" of Android application projects. ~~ JavaProxyThrowable ~~ Suppress warning around `StackFrame.GetMethod()`, we already handle null return values and exceptions. The existing code appears to be "best effort" to provide additional stack trace information. ~~ TypeManager ~~ Suppress warning around a call to `Type.GetType()` with a string passed in from Java. Not really much we can do yet, except rely on `MarkJavaObjects` trimmer step. Likely also a problem for the future: dotnet#8724
[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = rootAssembly)] | ||
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = rootAssembly)] | ||
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] | ||
static Type AssemblyGetType (Assembly a, string name) => a.GetType (name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this really work? I can't see how the trimmer would be able to determine what the returned type is and so it won't be able to preserve the public methods on it. I think at the very least the name
would need to be attributed as well, but I'm not sure if that's enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I don't think the trimmer preserves anything here -- this is just ignoring the warning.
This code is looking for a Resource.designer.cs
file that is generated in every app, which is the root assembly. There will likely be some problem here for NativeAOT one day.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did preserve the attribute that is kind of the "entry point":
[AttributeUsage (AttributeTargets.Assembly)]
public class ResourceDesignerAttribute : Attribute
{
public ResourceDesignerAttribute (string fullName)
public ResourceDesignerAttribute (
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
string fullName)
{
FullName = fullName;
}
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
public string FullName { get; set; }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add a Type
property to the attribute?
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] public Type Type { get; set; }
That would be perfectly trimmable and AOT friendly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can add a new API in .NET 9, but currently Xamarin.Android class libraries built against Mono.Android.dll
from ~2012 still work. This is different from iOS, as they have breaking changes.
So there would be some number of libraries out there that won't use the new API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is for .NET 9,so we can certainly add the suggested Type
property, and also have appropriate fallback code for when Type
is null
. (This in turn means that the new property would need to allow null
, e.g. Type? Type {get;set;}
.) We'd also want a "semantically meaningful" name; what is ResourceDesignerAttribute.Type
supposed to mean? A better name is warranted. ResourceDesignerType
, perhaps?
[assembly: Android.Runtime.ResourceDesignerAttribute(
"Example.Android.Resource",
ResourceDesignerType=typeof(Example.Android.Resource),
IsApplication=true
)]
That said… the "new" Resource design of dc3ccf2 was thought/hoped to (somewhat?) address trimming support; by using properties/methods for everything the trimmer should be able to "see" what methods are used and preserve them, while allowing everything else to be trimmed away.
Setting [DynamicallyAccessedMembers-(DynamicallyAccessedMemberTypes.PublicMethods)]
feels contrary to that intention? Why would all methods on the Resource
type need to be preserved?
Rando aside: why'd we use a string to name the Resource
type instead of typeof()
in the first place? A constructor overload feels reasonable here!
partial class ResourceDesignerAttribute {
public ResourceDesignerAttribute (Type resourceDesignerType);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting [DynamicallyAccessedMembers-(DynamicallyAccessedMemberTypes.PublicMethods)] feels contrary to that intention? Why would all methods on the Resource type need to be preserved?
We used System.Reflection to find an UpdateIdValues()
method and call it, so I think the current trimmer design is to add DynamicallyAccessedMemberTypes.PublicMethods
?
partial class ResourceDesignerAttribute {
public ResourceDesignerAttribute (Type resourceDesignerType);
}
For this part, let me see what it would take to:
- Generate usage of this overload, as we create
Resource.designer.cs
at build time - Maybe the old overload could emit a trimmer warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We used System.Reflection to find an
UpdateIdValues()
method and call it,
Yes, we used to. Commit dc3ccf2 should have removed that call for most (all?) cases. Yes, UpdateIdValues()
still exists, but "nothing" should call it (unless some customer decided to manually call it For Reasons™?). We certainly shouldn't call it, and we should be actively removing it, e.g. in ClearDesignerClass()
:
@dellis1972: do we even need [assembly: ResourceDesigner(…)]
anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dellis1972 replies on Discord:
Its not called if they are using the new Designer Assembly. If they turn it off then it will be used.
I overlooked/forgot that the new stuff can be disabled. It's controlled via $(AndroidUseDesignerAssembly)
.
When can we drop support for this? It was added in .NET 8; removing support for $(AndroidUseDesignerAssembly)
=false from .NET 9 feels too early, maybe? Maybe .NET 10?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm on with .NET 10 , people should have found all the bugs in the new stuff by then :P
"PackageSize": 10864931 | ||
"PackageSize": 10897699 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a small app size change here.
It looks like some are:
- Past .NET bumps were under the threshold
libmonodroid.so
changed, which must be other recent native code changes- We are actually preserving more stuff now. Perhaps we had some subtle trimmer bugs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are actually preserving more stuff now. Perhaps we had some subtle trimmer bugs?
Are we preserving more stuff? What stuff and why? I think this might warrant more investigation. Consider my earlier comment: we should not be preserving all public methods on the Resource
type! That also is apparently the case: we're not preserving all public methods, as indicated by the fact that UnnamedProject.dll
is smaller, as is _Microsoft.Android.Resource.Designer.dll
(by two bytes!).
Java.Interop.dll
increased in size by ~3KB, while Mono.Android.dll
shrank by ~2KB.
Of interest is that classes*.dex
also increased by ~7KB:
"classes.dex": {
- "Size": 9458972
+ "Size": 9418292
},
"classes2.dex": {
- "Size": 103648
+ "Size": 150904
},
This implies that we're emitting Java Callable Wrappers for more types. What types are we now emitting JCWs for, and why? That could be the "subtle trimmer bug" you elude to, and I would like more details here.
[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "StackFrame.GetMethod() is \"best attempt\", we handle null & exceptions")] | ||
static MethodBase? StackFrameGetMethod (StackFrame frame) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this reason hold for actual usage?
For example - in OTel, we had a similar issue, and the outcome of the discussion was that it's very important to get something for each frame. So there we rely on .ToString() on the StackFrame
which works even in NativeAOT/trimming (unless one explicitly disable stack frame info in NativeAOT as a size optimization).
I'm not saying this is wrong, just asking where is this used, and if we would like to get at least some info all the time in those scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me investigate if we could just use StackFrame.ToString()
for this. It's possible we would get the same text, and the tests would pass: 1aa0ea7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested in a console app, and it appears that StackFrame.GetMethod()
returns null
under NativeAOT. But calling StackFrame.ToString()
returns useful information that contains the method name, example:
MainActivity.OnCreate() + 0x37 at offset 55 in file:line:column <filename unknown>:0:0
To support that one day, we could parse the string, but I think delaying that work for now is best.
I left a code comment for now, linking to:
Code comments + FIXME
Doing it this way, keeps the warning present if someone was attempting to *use* NativeAOT, they still get the warning.
Context: dotnet/android#8758 (comment) Context: dotnet#1192 In 7d1e705 and b8f6f88, we suppressed IL3050, an AOT-related warning with: // FIXME: dotnet#1192 [UnconditionalSuppressMessage ("AOT", "IL3050")] // Problematic code here We don't immediately *plan* to support NativeAOT on Android in .NET 9, so it would be nice if we could suppress the warning *for now*. But if anyone tried to use this code in a NativeAOT context, they could get a warning. So instead we should use: // FIXME: dotnet#1192 #pragma warning disable IL3050 // Problematic code here #pragma warning restore IL3050 This will prevent us from introducing new `IL3050` and related warnings, but if anyone consumes `Java.Interop.dll` from NativeAOT -- they will see the warning.
Fixes: dotnet#5652 This is a WIP. This depends on dotnet#8758, will rebase after this one is merged.
Code formatting
@jonpryor on the app size changes, this is main vs this PR:
So we crossed the multi-dex threshold, and that's why I have a diff of the two apps, and it looks like constructors like these were preserved now, and so we have JCWs for JLO subclasses like: So I bet decorating
Preserved these constructors. |
* main: [Mono.Android] fix trimming warnings, part 2 (#8758)
Fixes: dotnet#5652 This is a WIP. This depends on dotnet#8758, will rebase after this one is merged.
* main: [Mono.Android] Fix race condition in AndroidMessageHandler (#8753) [ci] Fix SDL Sources Analysis for PRs from forks (#8785) [ci] Add 1ESPT override to MSBuild test stages (#8784) [ci] Do not use @self annotation for templates (#8783) [ci] Migrate to the 1ES template (#8747) [Mono.Android] fix trimming warnings, part 2 (#8758) [Xamarin.Android.Build.Tasks] set `%(DefineConstantsOnly)` for older API levels (#8777) [tests] fix duplicate sources in `NuGet.config` (#8772) Bump to xamarin/monodroid@e13723e701 (#8771)
* main: [Mono.Android] Fix race condition in AndroidMessageHandler (#8753) [ci] Fix SDL Sources Analysis for PRs from forks (#8785) [ci] Add 1ESPT override to MSBuild test stages (#8784) [ci] Do not use @self annotation for templates (#8783) [ci] Migrate to the 1ES template (#8747) [Mono.Android] fix trimming warnings, part 2 (#8758)
* main: (306 commits) [templates] Remove redundant "template" from display name. (dotnet#8773) Bump to xamarin/Java.Interop/main@a7e09b7 (dotnet#8793) [build] Include MIT license in most NuGet packages (dotnet#8787) Bump to dotnet/installer@893b762b6e 9.0.100-preview.3.24153.2 (dotnet#8782) [docs] update notes about `dotnet-trace` and `dotnet-gcdump` (dotnet#8713) [Mono.Android] Fix race condition in AndroidMessageHandler (dotnet#8753) [ci] Fix SDL Sources Analysis for PRs from forks (dotnet#8785) [ci] Add 1ESPT override to MSBuild test stages (dotnet#8784) [ci] Do not use @self annotation for templates (dotnet#8783) [ci] Migrate to the 1ES template (dotnet#8747) [Mono.Android] fix trimming warnings, part 2 (dotnet#8758) [Xamarin.Android.Build.Tasks] set `%(DefineConstantsOnly)` for older API levels (dotnet#8777) [tests] fix duplicate sources in `NuGet.config` (dotnet#8772) Bump to xamarin/monodroid@e13723e701 (dotnet#8771) Bump to xamarin/xamarin-android-tools/main@37d79c9 (dotnet#8752) Bump to dotnet/installer@d070660282 9.0.100-preview.3.24126.2 (dotnet#8763) Bump to xamarin/java.interop/main@14a9470 (dotnet#8766) $(AndroidPackVersionSuffix)=preview.3; net9 is 34.99.0.preview.3 (dotnet#8765) [Mono.Android] Do not dispose request content stream in AndroidMessageHandler (dotnet#8764) Bump com.android.tools:r8 from 8.2.42 to 8.2.47 (dotnet#8761) ...
Context: dotnet/android#8758 (comment) Context: #1192 Context: 2197579 In 7d1e705 and b8f6f88, we suppressed IL3050, an AOT-related warning with: // FIXME: #1192 [UnconditionalSuppressMessage ("AOT", "IL3050")] // Problematic code here We don't immediately *plan* to support NativeAOT on Android in .NET 9, so it would be nice if we could suppress the warning *for now*. However, if/when anyone tried to use this code in a NativeAOT context such as 2197579, they *should* get a warning (but didn't). We should instead use: // FIXME: #1192 #pragma warning disable IL3050 // Problematic code here #pragma warning restore IL3050 This will prevent us from introducing new `IL3050` and related warnings, but if anyone consumes `Java.Interop.dll` from NativeAOT they will see the warning. This (re-)introduces IL3050 warnings in the build for `samples/Hello-NativeAOTFromJNI` (2197579): % dotnet publish -c Release -r osx-x64 … …/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(665): AOT analysis warning IL3050: Java.Interop.JniRuntime.JniValueManager.<GetObjectArrayMarshaler>g__MakeGenericMethod|41_0(MethodInfo,Type): Using member 'System.Reflection.MethodInfo.MakeGenericMethod(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime. …/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(381): AOT analysis warning IL3050: Java.Interop.JniRuntime.JniValueManager.<GetInvokerType>g__MakeGenericType|31_1(Type,Type[]): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime. …/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(789): AOT analysis warning IL3050: Java.Interop.JavaPeerableValueMarshaler.CreateParameterToManagedExpression(JniValueMarshalerContext,ParameterExpression,ParameterAttributes,Type): Using member 'System.Linq.Expressions.Expression.Call(Expression,String,Type[],Expression[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. Calling a generic method requires dynamic code generation. This can be suppressed if the method is not generic. …/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs(284): AOT analysis warning IL3050: Java.Interop.JniRuntime.JniTypeManager.MakeGenericType(Type,Type): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime. …/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs(277): AOT analysis warning IL3050: Java.Interop.JniRuntime.JniTypeManager.MakeArrayType(Type): Using member 'System.Type.MakeArrayType()' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The code for an array of the specified type might not be available.
This fixes the next set of trimmer warnings found via:
JavaObjectExtensions
JavaCast<T>()
now requiresPublicConstructors
andNonPublicConstructors
because ofActivator.CreateInstance<T>()
. This change bubbles up to various other types that have aFind*ById<T>()
method:Activity
Dialog
FragmentManager
View
Window
GetInvokerType()
also has suppressions aroundAssembly.GetType()
andType.MakeGenericType()
. We track this for the future at:#8724
AndroidRuntime
Update
[DynamicallyAccessedMembers]
based on changes toRegisterNativeMembers
in:dotnet/java-interop@b8f6f88
JNINativeWrapper
$(EnableAotAnalyzer)
found usage ofDynamicMethod
. Suppress for now, as we track this for the future at:#8724
ResourceIdManager
Usage of
Type.GetMethod ("UpdateIdValues")
leads to decoration of[ResourceDesignerAttribute]
with:I also had to suppress warnings around
Assembly.GetType()
. This should be OK, asResource.designer.cs
is always in the "root assembly" of Android application projects.JavaProxyThrowable
Suppress warning around
StackFrame.GetMethod()
, we already handle null return values and exceptions. The existing code appears to be "best effort" to provide additional stack trace information.TypeManager
Suppress warning around a call to
Type.GetType()
with a string passed in from Java. Not really much we can do yet, except rely onMarkJavaObjects
trimmer step.Likely also a problem for the future:
#8724