Skip to content

attempt to fix dotnet/maui#17265 #8416

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

Merged
merged 15 commits into from
Oct 17, 2023
Merged

attempt to fix dotnet/maui#17265 #8416

merged 15 commits into from
Oct 17, 2023

Conversation

jonathanpeppers
Copy link
Member

@jonathanpeppers jonathanpeppers commented Oct 11, 2023

Context: dotnet/maui#17265
fixes dotnet/maui#17265

Reproduced the issue in a new FixLegacyResourceDesignerStep test. It crashes at runtime, but if you change the class libraries in the test to net8.0, it works fine.

I first attempted to fix the problem for Release mode, as it seemed easier to do a "second pass" on all assemblies at the end of a linker step.

This change starts to work:

ILLink:    Library2 has an assembly reference to Library1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
ILLink:    Library2 has an assembly reference with a designer.
ILLink:     Adding reference _Microsoft.Android.Resource.Designer.
ILLink:     FixupAssemblyTypes Library2.
ILLink: Looking for String::hello.
ILLink:     Fixing up System.Int32 Foo::get_Hello()
ILLink:       Replacing IL_0000: ldsfld System.Int32 Library1.Resource/String::hello
ILLink:       With IL_0000: call System.Int32 _Microsoft.Android.Resource.Designer.Resource/String::get_hello()

But then fails with:

System.ArgumentException: Member 'System.Int32 _Microsoft.Android.Resource.Designer.Resource/String::get_hello()' is declared in another module and needs to be imported

jonathanpeppers and others added 3 commits October 11, 2023 16:36
Context: dotnet/maui#17265

Reproduced the issue in a new `FixLegacyResourceDesignerStep` test. It
crashes at runtime, but if you change the class libraries in the test to
`net8.0`, it works fine.

I first attempted to fix the problem for `Release` mode, as it seemed
easier to do a "second pass" on *all assemblies* at the end of a linker
step.

This change starts to work:

    ILLink:    Library2 has an assembly reference to Library1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
    ILLink:    Library2 has an assembly reference with a designer.
    ILLink:     Adding reference _Microsoft.Android.Resource.Designer.
    ILLink:     FixupAssemblyTypes Library2.
    ILLink: Looking for String::hello.
    ILLink:     Fixing up System.Int32 Foo::get_Hello()
    ILLink:       Replacing IL_0000: ldsfld System.Int32 Library1.Resource/String::hello
    ILLink:       With IL_0000: call System.Int32 _Microsoft.Android.Resource.Designer.Resource/String::get_hello()

But then fails with:

    System.ArgumentException: Member 'System.Int32 _Microsoft.Android.Resource.Designer.Resource/String::get_hello()' is declared in another module and needs to be imported
@dellis1972 dellis1972 changed the title [WIP] attempt to fix dotnet/maui#17265 attempt to fix dotnet/maui#17265 Oct 12, 2023
@dellis1972 dellis1972 marked this pull request as ready for review October 12, 2023 12:33
@jonpryor
Copy link
Contributor

@dellis1972: had an alternate "brilliant idea" for how to solve this: heuristics and "manual resolution".

We don't want to read all IL for all assemblies, as we believe that'll be slow.

However, we already process certain assembly metadata for all assemblies, just to view the assembly references.

I thus propose using ModuleDefinition.GetMemberReferences() to look for member references which:

  1. have a "field" reference to a type containing .Resource/, and
  2. the type in (1) cannot be resolved.

I need to try to prototype this, but for comparison consider:

% monodis --memberref ~/.nuget/packages/syncfusion.maui.core/22.2.12/lib/net7.0-android30.0/Syncfusion.Maui.Core.dll
…
1341: TypeRef[472] scrollViewTheme
        Resolved: [Microsoft.Maui]Microsoft.Maui.Resource/Style.scrollViewTheme
        Signature: int32

Syncfusion.Maui.Core.dll is referencing the field type [Microsoft.Maui]Microsoft.Maui.Resource/Style, and that type does not exist.

If we do this in a place which already has a DirectoryAssemblyResolver for "all assemblies", we'll already have all required metadata loaded, and if we find such a type which cannot be resolved, "boom", there we go.

@jonpryor
Copy link
Contributor

So haha we don't want to try to resolve everything, because Cecil can't resolve everything. In an intermediate version of the patch I'm cooking up, it's trying to fixup System.Text.Encoding.CodePages (?!) because:

ILLink: # jonp: memberRefs[120].Name=Get; .FullName=System.Char System.Char[0...,0...,0...]::Get(System.Int32,System.Int32,System.Int32); .DeclaringType=System.Char[0...,0...,0...] (TaskId:277)
ILLink: # jonp:   memberRef 'Get' resolved to:  [False ] (TaskId:277)

monodis --memberref System.Text.Encoding.CodePages.dll shows:

121: TypeSpec[8] Get
        Resolved: char [0...,0...,0...].Get
        Signature: instance char(int32, int32, int32)

I have no idea what char [0...,0...,0...].Get is supposed to even mean, but in the context of this issue, it means "MemberReference.Resolve() returns null!"

This impacts like everything: Mono.Android.dll:

memberRefs[541].Name=.ctor; .FullName=System.Void System.Action`1<Android.Widget.AutoCompleteTextView/IOnDismissListenerImplementor>::.ctor(System.Object,System.IntPtr); .DeclaringType=System.Action`1<Android.Widget.AutoCompleteTextView/IOnDismissListenerImplementor>

System.Private.CoreLib.dll:

memberRefs[3837].Name=Get; .FullName=System.Int32 System.Int32[0...,0...]::Get(System.Int32,System.Int32); .DeclaringType=System.Int32[0...,0...]

etc.

Thus I'm going with the heuristic of "memberRef declaring type contains .Resource/.

@jonpryor
Copy link
Contributor

@dellis1972: here's my current patch. it's a mess. :-)

diff --git a/external/xamarin-android-tools b/external/xamarin-android-tools
--- a/external/xamarin-android-tools
+++ b/external/xamarin-android-tools
@@ -1 +1 @@
-Subproject commit 8a971d94a3fa2f0e8f69c5cf742c6836c14be1cd
+Subproject commit 8a971d94a3fa2f0e8f69c5cf742c6836c14be1cd-dirty
diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs
index 6804db9ce..605d5268e 100644
--- a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs
+++ b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs
@@ -337,6 +337,7 @@ namespace Xamarin.Android.Net
 
 		protected override void Dispose (bool disposing)
 		{
+			global::System.Console.WriteLine ($"# jonp: AndroidMessageHandler.Dispose ({disposing}): {new System.Diagnostics.StackTrace (true)}");
 			disposed  = true;
 
 			base.Dispose (disposing);
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
index 793320858..0849f5ea5 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
@@ -35,6 +35,7 @@ namespace MonoDroid.Tuner
 		protected override void EndProcess ()
 		{
 			base.EndProcess ();
+			LogMessage ($"# jonp: FixLegacyResourceDesignerStep.EndProcess");
 
 			if (designerAssembly != null) {
 				LogMessage ($"  Setting Action on {designerAssembly.Name} to Link.");
@@ -72,6 +73,7 @@ namespace MonoDroid.Tuner
 
 		internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly, TypeDefinition designer = null)
 		{
+			LogMessage ($"# jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: assembly={assembly?.FullName}, designer={designer?.FullName}");
 			if (designer is not null) {
 				LogMessage ($"   {assembly.Name.Name} has an assembly reference with a designer.");
 			} else if (!FindResourceDesigner (assembly, mainApplication: false, out designer, out _)) {
@@ -86,6 +88,8 @@ namespace MonoDroid.Tuner
 				}
 			}
 
+			LogMessage ($"# jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? {assembly?.FullName}");
+
 			// This is expected for the first call, in <LinkAssembliesNoShrink/>
 			if (!designerLoaded)
 				LoadDesigner ();
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
index 292ac2401..c428f250e 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
@@ -42,6 +42,7 @@ namespace MonoDroid.Tuner  {
 
 		internal bool FindResourceDesigner (AssemblyDefinition assembly, bool mainApplication, out TypeDefinition designer, out CustomAttribute designerAttribute)
 		{
+			LogMessage ($"# jonp: LinkDesignerBase.FindResourceDesigner: looking at assembly: {assembly.FullName}; mainApplication={mainApplication}");
 			string designerFullName = null;
 			designer = null;
 			designerAttribute = null;
@@ -65,6 +66,7 @@ namespace MonoDroid.Tuner  {
 
 				}
 			}
+#if false
 			if (string.IsNullOrEmpty(designerFullName)) {
 				// Check for known designers which have been removed.
 				foreach (var knownDesigner in KnownDesignerAssemblies) {
@@ -76,6 +78,34 @@ namespace MonoDroid.Tuner  {
 				}
 				return false;
 			}
+#endif
+
+			var memberRefs = assembly.MainModule.GetMemberReferences ().ToList ();
+			LogMessage ($"# jonp: LinkDesignerBase.FindResourceDesigner: looking at assembly: {assembly.FullName}; memberRefs.Count={memberRefs.Count}");
+			var memberIdx = 0;
+			foreach (var memberRef in memberRefs) {
+				memberIdx++;
+				if (!memberRef.DeclaringType?.ToString()?.Contains (".Resource/") ?? true) {
+					continue;
+				}
+				LogMessage ($"# jonp: memberRefs[{memberIdx}].Name={memberRef?.Name}; .FullName={memberRef.FullName}; .DeclaringType={memberRef.DeclaringType}");
+				var resolved = false;
+				try {
+					var def = memberRef.Resolve ();
+					resolved = def != null;
+					LogMessage ($"# jonp:   memberRef '{memberRef?.Name}' resolved to: {def?.FullName} [{def != null} {def?.GetType().FullName}]");
+				}
+				catch (Exception _ex) {
+					LogMessage ($"# jonp: exception resolving memberRef {memberRef?.Name}! {_ex}");
+					resolved = false;
+				}
+				if (!resolved) {
+					LogMessage ($"# jonp: Adding _Linker.Generated.Resource to {assembly.Name.Name}");
+					designer = new TypeDefinition ("_Linker.Generated", "Resource", TypeAttributes.Public | TypeAttributes.AnsiClass);
+					designer.BaseType = new TypeDefinition ("System", "Object", TypeAttributes.Public | TypeAttributes.AnsiClass);
+					return true;
+				}
+			}
 
 			foreach (ModuleDefinition module in assembly.Modules)
 			{
@@ -239,6 +269,7 @@ namespace MonoDroid.Tuner  {
 
 		protected override void EndProcess ()
 		{
+			LogMessage ($"# jonp: LinkDesignerBase.EndProcess; processedAssemblies.Count={processedAssemblies.Count}; allAssemblies.Count={allAssemblies.Count}");
 			// This is a "second pass" to fix assemblies with references to assemblies with a designer
 			if (processedAssemblies.Count > 0) {
 				foreach (var assembly in allAssemblies) {
@@ -249,12 +280,14 @@ namespace MonoDroid.Tuner  {
 					if (action == AssemblyAction.Delete)
 						continue;
 
+					#if false
 					foreach (var processedAssembly in processedAssemblies) {
 						if (ProcessAssemblyDesignerSecondPass (assembly, processedAssembly) &&
 								(action == AssemblyAction.Skip || action == AssemblyAction.Copy)) {
 							Annotations.SetAction (assembly, AssemblyAction.Save);
 						}
 					}
+					#endif
 				}
 			}
 		}
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
index 880c3abc4..13201ec7a 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
@@ -45,7 +45,9 @@ This file contains the .NET 5-specific targets to customize ILLink
           Include="ProguardConfiguration"
           Value="$(_ProguardProjectConfiguration)"
       />
+      <!--
       <_TrimmerCustomData Include="AndroidKnownDesignerAssemblies" Value="@(AndroidKnownDesignerAssemblies)" />
+      -->
 
       <!--
         Used for the <ILLink CustomSteps="@(_TrimmerCustomSteps)" /> value:

Most importantly, it doesn't work. I don't know why.

Anyway, repro:

# https://github.com/Zack-G-I-T/SfListView.Net8Bug
cd SfListView.Net8Bug/SfListViewBug
~/Developer/src/xamarin/xamarin-android/dotnet-local.sh build -f net8.0-android -p:Configuration=Release -v:diag -p:_ExtraTrimmerArgs=--verbose -p:TrimmerSingleWarn=false > br.txt

where ~/Developer/src/xamarin/xamarin-android/dotnet-local.sh is from my xamarin-android checkout.

The good: it appropriately detects that Syncfusion.Maui.Core.dll needs fixing up. (Yay!)

ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: assembly=Syncfusion.Maui.Core, Version=22.2.12.0, Culture=neutral, PublicKeyToken=null, designer=
ILLink: # jonp: LinkDesignerBase.FindResourceDesigner: looking at assembly: Syncfusion.Maui.Core, Version=22.2.12.0, Culture=neutral, PublicKeyToken=null; mainApplication=False
ILLink: # jonp: LinkDesignerBase.FindResourceDesigner: looking at assembly: Syncfusion.Maui.Core, Version=22.2.12.0, Culture=neutral, PublicKeyToken=null; memberRefs.Count=1725
ILLink: # jonp: memberRefs[1341].Name=scrollViewTheme; .FullName=System.Int32 Microsoft.Maui.Resource/Style::scrollViewTheme; .DeclaringType=Microsoft.Maui.Resource/Style
ILLink: # jonp:   memberRef 'scrollViewTheme' resolved to:  [False ]
ILLink: # jonp: Adding _Linker.Generated.Resource to Syncfusion.Maui.Core
ILLink:    Syncfusion.Maui.Core has a designer.
ILLink:    BaseType: System.Object.
ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? Syncfusion.Maui.Core, Version=22.2.12.0, Culture=neutral, PublicKeyToken=null
ILLink:     Adding reference _Microsoft.Android.Resource.Designer.
ILLink:     FixupAssemblyTypes Syncfusion.Maui.Core
ILLink:     ClearDesignerClass Microsoft.Maui.Controls.Compatibility.
ILLink:     TryRemoving _Linker.Generated.Resource

So, yay, we found it.

Additionally, the assemblies we want to fixup aren't "bananas": no Mono.Android, no System.Private.CoreLib, etc.:

ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? Microsoft.Maui.Controls.Compatibility, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (TaskId:86)
ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? Syncfusion.Maui.Core, Version=22.2.12.0, Culture=neutral, PublicKeyToken=null (TaskId:86)
ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? Xamarin.Jetbrains.Annotations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (TaskId:86)
ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? Xamarin.Kotlin.StdLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (TaskId:86)
ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? Xamarin.Kotlin.StdLib.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (TaskId:86)
ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? Xamarin.Kotlin.StdLib.Jdk7, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (TaskId:86)
ILLink: # jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? Xamarin.Kotlin.StdLib.Jdk8, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (TaskId:86)

(Though we do want to fixup Xamarin.Kotlin.StdLib, Xamarin.Kotlin.StdLib.Jdk7, and others, and I'm less certain about the "lack of bananas" for those…)

…but also this is concerning:

ILLink: # jonp: LinkDesignerBase.FindResourceDesigner: looking at assembly: Microsoft.Maui.Controls.Compatibility, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null; memberRefs.Count=3818
ILLink: # jonp: memberRefs[416].Name=get_toolbar; .FullName=System.Int32 _Microsoft.Android.Resource.Designer.Resource/Layout::get_toolbar(); .DeclaringType=_Microsoft.Android.Resource.Designer.Resource/Layout
ILLink: # jonp:   memberRef 'get_toolbar' resolved to:  [False ]

Microsoft.Maui.Controls.Compatibility.dll is a .NET 8 assembly, and is using the new Resource Designer infrastructure. Why isn't Resource.Layout.toolbar resolved? Because it's not in _Microsoft.Android.Resource.Designer.dll! (Wait, what?)

https://github.com/dotnet/maui/blob/c67df3012f222fc191f30782d318a2fb5d1a4fc0/src/Compatibility/Core/src/Android/AppCompat/FormsAppCompatActivity.cs#L214

This weird me out, and is quite concerning.

Meanwhile, previously I wrote "it doesn't work." By that I mean that while we detect that the assembly needs fixing up, it is not in fact fixed up. Which is bizarre, because Syncfusion.Maui.Core.dll is rewritten; diff shows that the files are not the same, and ikdasm shows that all the System.Runtime references have been replaced with System.Private.CoreLib references, so it has been updated, but the updates didn't include replacing ldsfld with call …. I also don't see any log messages from FixBody(), so something is screwy there.

@jonpryor
Copy link
Contributor

With the addition of ever more LogMessage() calls, I see that FixBody() is invoked for Syncfusion.Maui.Core.Internals.ListViewScrollViewHandler::CreatePlatformView() -- which contains the ldsfld that needs fixing up -- but it isn't matching.

…oh, because of:

https://github.com/xamarin/xamarin-android/blob/2b0761b159f679608f1d9a5c5ec186121471cfc7/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs#L156

which, because we "invent" the Resource type, will never match. Consequently, FixBody() also needs some form of updating…

@jonpryor
Copy link
Contributor

I wrote

Consequently, FixBody() also needs some form of updating…

Updated.

With this New (delusions of "final" except for all the printfs) patch, Syncfusion.Maui.Core.dll is fixed up so that it no longer references the field [Microsoft.Maui]Microsoft.Maui.Resource/Attribute::scrollViewStyle, and instead references the method [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/Attribute::get_scrollViewStyle(), which feels like SUCCESS:

diff --git a/external/xamarin-android-tools b/external/xamarin-android-tools
--- a/external/xamarin-android-tools
+++ b/external/xamarin-android-tools
@@ -1 +1 @@
-Subproject commit 8a971d94a3fa2f0e8f69c5cf742c6836c14be1cd
+Subproject commit 8a971d94a3fa2f0e8f69c5cf742c6836c14be1cd-dirty
diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs
index 6804db9ce..605d5268e 100644
--- a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs
+++ b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs
@@ -337,6 +337,7 @@ namespace Xamarin.Android.Net
 
 		protected override void Dispose (bool disposing)
 		{
+			global::System.Console.WriteLine ($"# jonp: AndroidMessageHandler.Dispose ({disposing}): {new System.Diagnostics.StackTrace (true)}");
 			disposed  = true;
 
 			base.Dispose (disposing);
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
index 793320858..6078a6585 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
@@ -35,6 +35,7 @@ namespace MonoDroid.Tuner
 		protected override void EndProcess ()
 		{
 			base.EndProcess ();
+			LogMessage ($"# jonp: FixLegacyResourceDesignerStep.EndProcess");
 
 			if (designerAssembly != null) {
 				LogMessage ($"  Setting Action on {designerAssembly.Name} to Link.");
@@ -72,6 +73,7 @@ namespace MonoDroid.Tuner
 
 		internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly, TypeDefinition designer = null)
 		{
+			LogMessage ($"# jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: assembly={assembly?.FullName}, designer={designer?.FullName}");
 			if (designer is not null) {
 				LogMessage ($"   {assembly.Name.Name} has an assembly reference with a designer.");
 			} else if (!FindResourceDesigner (assembly, mainApplication: false, out designer, out _)) {
@@ -86,6 +88,8 @@ namespace MonoDroid.Tuner
 				}
 			}
 
+			LogMessage ($"# jonp: FixLegacyResourceDesignerStep.ProcessAssemblyDesigner: fixing up? {assembly?.FullName}");
+
 			// This is expected for the first call, in <LinkAssembliesNoShrink/>
 			if (!designerLoaded)
 				LoadDesigner ();
@@ -145,6 +149,7 @@ namespace MonoDroid.Tuner
 
 		protected override void FixBody (MethodBody body, TypeDefinition designer)
 		{
+			LogMessage ($"# jonp: FixLegacyResourceDesignerStep.FixBody: body={body?.Method?.FullName}, designer={designer?.FullName}");
 			// replace
 			// IL_0068: ldsfld int32 Xamarin.Forms.Platform.Android.Resource/Layout::Toolbar
 			// with
@@ -156,10 +161,8 @@ namespace MonoDroid.Tuner
 			{
 				if (i.OpCode != OpCodes.Ldsfld)
 					continue;
-				string line = i.ToString ();
-				int idx = line.IndexOf (designerFullName, StringComparison.Ordinal);
-				if (idx >= 0) {
-					string key = line.Substring (idx + designerFullName.Length);
+				var key = GetFixupKey (i, designerFullName);
+				if (key != null) {
 					LogMessage ($"Looking for {key}.");
 					var found = lookup.TryGetValue (key, out MethodDefinition method);
 					if (!found) {
@@ -192,5 +195,34 @@ namespace MonoDroid.Tuner
 				processor.Replace(i.Key, i.Value);
 			}
 		}
+
+		string GetFixupKey (Instruction instruction, string designerFullName)
+		{
+			string line = instruction.ToString ();
+			LogMessage ($" jonp: GetFixupKey: looking at instruction: {line}");
+			int idx = line.IndexOf (designerFullName, StringComparison.Ordinal);
+			if (idx >= 0) {
+				return line.Substring (idx + designerFullName.Length);
+			}
+			LogMessage ($" jonp: GetFixupKey: instruction.Operand is {instruction.Operand?.GetType ()}");
+			if (instruction.Operand is FieldReference fieldRef &&
+					(fieldRef.DeclaringType?.ToString()?.Contains (".Resource/") ?? false)) {
+				var canResolve = false;
+				try {
+					var resolved  = fieldRef.Resolve ();
+					canResolve    = resolved != null;
+				} catch (Exception) {
+				}
+				if (canResolve)
+					return null;
+				var type  = fieldRef.DeclaringType.FullName;
+				var s     = type.LastIndexOf ('/');
+				type      = type.Substring (s + 1);
+				var key   = type + "::" + fieldRef.Name;
+				LogMessage ($" jonp: GetFixupKey: .Resource/ heuristic; key={key}");
+				return key;
+			}
+			return null;
+		}
 	}
 }
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
index 292ac2401..cde68f7de 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
@@ -42,6 +42,7 @@ namespace MonoDroid.Tuner  {
 
 		internal bool FindResourceDesigner (AssemblyDefinition assembly, bool mainApplication, out TypeDefinition designer, out CustomAttribute designerAttribute)
 		{
+			LogMessage ($"# jonp: LinkDesignerBase.FindResourceDesigner: looking at assembly: {assembly.FullName}; mainApplication={mainApplication}");
 			string designerFullName = null;
 			designer = null;
 			designerAttribute = null;
@@ -65,6 +66,7 @@ namespace MonoDroid.Tuner  {
 
 				}
 			}
+#if false
 			if (string.IsNullOrEmpty(designerFullName)) {
 				// Check for known designers which have been removed.
 				foreach (var knownDesigner in KnownDesignerAssemblies) {
@@ -76,6 +78,34 @@ namespace MonoDroid.Tuner  {
 				}
 				return false;
 			}
+#endif
+
+			var memberRefs = assembly.MainModule.GetMemberReferences ().ToList ();
+			LogMessage ($"# jonp: LinkDesignerBase.FindResourceDesigner: looking at assembly: {assembly.FullName}; memberRefs.Count={memberRefs.Count}");
+			var memberIdx = 0;
+			foreach (var memberRef in memberRefs) {
+				memberIdx++;
+				if (!memberRef.DeclaringType?.ToString()?.Contains (".Resource/") ?? true) {
+					continue;
+				}
+				LogMessage ($"# jonp: memberRefs[{memberIdx}].Name={memberRef?.Name}; .FullName={memberRef.FullName}; .DeclaringType={memberRef.DeclaringType}");
+				var resolved = false;
+				try {
+					var def = memberRef.Resolve ();
+					resolved = def != null;
+					LogMessage ($"# jonp:   memberRef '{memberRef?.Name}' resolved to: {def?.FullName} [{def != null} {def?.GetType().FullName}]");
+				}
+				catch (Exception _ex) {
+					LogMessage ($"# jonp: exception resolving memberRef {memberRef?.Name}! {_ex}");
+					resolved = false;
+				}
+				if (!resolved) {
+					LogMessage ($"# jonp: Adding _Linker.Generated.Resource to {assembly.Name.Name}");
+					designer = new TypeDefinition ("_Linker.Generated", "Resource", TypeAttributes.Public | TypeAttributes.AnsiClass);
+					designer.BaseType = new TypeDefinition ("System", "Object", TypeAttributes.Public | TypeAttributes.AnsiClass);
+					return true;
+				}
+			}
 
 			foreach (ModuleDefinition module in assembly.Modules)
 			{
@@ -133,6 +163,7 @@ namespace MonoDroid.Tuner  {
 
 		protected void FixType (TypeDefinition type, TypeDefinition localDesigner)
 		{
+			LogMessage ($"# jonp: LinkDesignerBase.FixType: type={type.FullName}; localDesigner={localDesigner.FullName}");
 			foreach (MethodDefinition method in type.Methods)
 			{
 				if (!method.HasBody)
@@ -200,6 +231,7 @@ namespace MonoDroid.Tuner  {
 
 		protected void FixupAssemblyTypes (AssemblyDefinition assembly, TypeDefinition designer)
 		{
+			LogMessage ($"# jonp: LinkDesignerBase.FixupAssemblyTypes; assembly={assembly.FullName}; designer={designer.FullName}");
 			foreach (ModuleDefinition module in assembly.Modules)
 			{
 				foreach (TypeDefinition type in module.Types)
@@ -239,6 +271,7 @@ namespace MonoDroid.Tuner  {
 
 		protected override void EndProcess ()
 		{
+			LogMessage ($"# jonp: LinkDesignerBase.EndProcess; processedAssemblies.Count={processedAssemblies.Count}; allAssemblies.Count={allAssemblies.Count}");
 			// This is a "second pass" to fix assemblies with references to assemblies with a designer
 			if (processedAssemblies.Count > 0) {
 				foreach (var assembly in allAssemblies) {
@@ -249,12 +282,14 @@ namespace MonoDroid.Tuner  {
 					if (action == AssemblyAction.Delete)
 						continue;
 
+					#if false
 					foreach (var processedAssembly in processedAssemblies) {
 						if (ProcessAssemblyDesignerSecondPass (assembly, processedAssembly) &&
 								(action == AssemblyAction.Skip || action == AssemblyAction.Copy)) {
 							Annotations.SetAction (assembly, AssemblyAction.Save);
 						}
 					}
+					#endif
 				}
 			}
 		}
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
index 880c3abc4..13201ec7a 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
@@ -45,7 +45,9 @@ This file contains the .NET 5-specific targets to customize ILLink
           Include="ProguardConfiguration"
           Value="$(_ProguardProjectConfiguration)"
       />
+      <!--
       <_TrimmerCustomData Include="AndroidKnownDesignerAssemblies" Value="@(AndroidKnownDesignerAssemblies)" />
+      -->
 
       <!--
         Used for the <ILLink CustomSteps="@(_TrimmerCustomSteps)" /> value:

if (!FindResourceDesigner (assembly, mainApplication: false, out TypeDefinition designer, out CustomAttribute designerAttribute)) {
if (designer is not null) {
LogMessage ($" {assembly.Name.Name} has an assembly reference with a designer.");
} else if (!FindResourceDesigner (assembly, mainApplication: false, out designer, out _)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hitting "perfect is the enemy of the good" territory -- so don't take this comment as a suggestion to change anything -- but I don't like the code flow here:

  • It requires that FindResourceDesigner create a new TypeDefinition instance, even though that instance shouldn't be used for anything, and ideally should be linked away entirely.
  • I suspect we could have/create a scenario in which an assembly references multiple Resource types, in which case returning one type -- if that type were used for anything -- would be inappropriate.

This complaint likely isn't worth addressing now, I just wanted to comment on it.

The "new fix" doesn't require us to touch as much code.

I could restore much of the original code to make the diff smaller.
@jonathanpeppers
Copy link
Member Author

I was able to remove a lot of the old changes. I also cleaned up #jonp log messages.

It still appears to fix the original SyncFusion sample:

image

Both Debug & Release builds of this app build & run for me.

@jonpryor
Copy link
Contributor

jonpryor commented Oct 16, 2023

Draft commit message:

[Xamarin.Android.Build.Tasks] Fixup indirect resource references (#8416)

Fixes: https://github.com/dotnet/maui/issues/17265

Context: dc3ccf28cdbe9f8c0a705400b83c11a85c81a980
Context: https://github.com/Zack-G-I-T/SfListView.Net8Bug/tree/ecb25af3329391858d1d64c4875ca58771e2b66c

Commit dc3ccf28 completely reworked how Android Resources work,
moving from a `$(RootNamespace).Resource` type which contained fields
(which needed to be updated at runtime across numerous assemblies)
to a "`_Microsoft.Android.Resource.Designer` reference assembly"
which contained *methods* for each Resource id.

To maintain backward compatibility, pre-.NET 8 assemblies were
rewritten so that instead of accessing fields:

	ldsfld     int32 $(RootNamespace).Resource/Layout::Toolbar

they became method calls:

	call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/Layout::get_Toolbar()

Unfortunately we encountered a missing corner case: the previous
fixup logic only operated on assemblies that themselves contained a
`$(RootNamespace).Resource` type, which in turn (generally) required
that the assembly be built from a project that had
`@(AndroidResource)` items.

In dotnet/maui#17265 and Zack-G-I-T/SfListView.Net8Bug, we
encountered an assembly that:

 1. Did *not* contain a `Resource` type, and
 2. Used Resource ids from other assemblies, and
 3. Was a .NET 7 assembly, so it and its dependencies were all using
    the .NET 7 field-based Resource approach.

Setup:

  * `Ako` and `Bko` are net7.0-android projects which contain an
    `@(AndroidResource)`
  * `RefsLibs` is a net7.0-android project which references `Ako` and
    `Bko`, and uses the Resource values from them.
  * `App` is a net8.0-android project which references `RefsLibs`.


"Repro" setup:

	dotnet new androidlib -n Ako
	# Set `$(TargetFramework)`=net7.0-android
	# Add Ako/Resources/values/strings.xml with String resource ako_name

	dotnet new androidlib -n Bko
	# Set `$(TargetFramework)`=net7.0-android
	# Add Bko/Resources/values/strings.xml with String resource bko_name

	dotnet new androidlib -n RefsLibs
	# Set `$(TargetFramework)`=net7.0-android
	# Add ProjectReference to ..\Ako\Ako.csproj, ..\Bko\Bko.csproj
	# Update `RefsLibs\Class1.cs` to use Ako.Resource.String.ako_name, Bko.Resource.String.bko_name

	dotnet new android -n App
	# *Remains* `$(TargetFramework)`=net8.0-android
	# Add ProjectReference to ..\RefsLibs\RefsLibs.csproj

The punch:

	dotnet build App/App.csproj -p:Configuration=Release

This fails to build with .NET 8 RC1:

	ILLink : error IL1013: Error processing '/Users/jon/Downloads/dotnet-sdk-8.0.100-rc.1.23455.8-osx-x64/packs/Microsoft.Android.Sdk.Darwin/34.0.0-rc.1.432/targets/../PreserveLists/Mono.Android.xml'.
	Fatal error in IL Linker
	Unhandled exception. Mono.Linker.LinkerFatalErrorException: ILLink: error IL1013: Error processing '/Users/jon/Downloads/dotnet-sdk-8.0.100-rc.1.23455.8-osx-x64/packs/Microsoft.Android.Sdk.Darwin/34.0.0-rc.1.432/targets/../PreserveLists/Mono.Android.xml'.
	 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'key')
	   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
	   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
	
	…/build/Microsoft.NET.ILLink.targets(87,5): error NETSDK1144: Optimizing assemblies for size failed. Optimization can be disabled by setting the PublishTrimmed property to false.

"Interestingly", it *succeeds* with .NET 8 RC2 (no build errors).

Regardless, with both .NET 8 RC1 and RC2, the app is *broken*:

	% dotnet tool install --global dotnet-ilverify

	% $HOME/.dotnet/tools/ilverify App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll \
		--tokens --system-module System.Private.CoreLib \
		-r 'App/obj/Release/net8.0-android/android-arm/linked/*.dll' 
	[IL]: Error [ClassLoadGeneral]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Class1::.ctor()] Failed to load type 'String' from assembly 'Ako, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
	[IL]: Error [CallCtor]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Resource::.ctor()][offset 0x00000001] call to .ctor only allowed to initialize this pointer from within a .ctor. Try newobj.
	[IL]: Error [ThisUninitReturn]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Resource::.ctor()][offset 0x00000006] Return from .ctor when this is uninitialized.
	3 Error(s) Verifying …/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll

If you disassemble `RefsLibs.dll`, you find that it's using `ldsfld`,
not `call`:

	% ikdasm App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll
	
	  .method public hidebysig specialname rtspecialname 
	          instance void  .ctor() cil managed
	  {
	    // Code size       29 (0x1d)
	    .maxstack  8
	    IL_0000:  ldarg.0
	    IL_0001:  ldsfld     int32 [Ako]Ako.Resource/String::ako_name
	    IL_0006:  stfld      int32 RefsLibs.Class1::a
	    IL_000b:  ldarg.0
	    IL_000c:  ldsfld     int32 [Bko]Bko.Resource/String::bko_name
	    IL_0011:  stfld      int32 RefsLibs.Class1::b
	    IL_0016:  ldarg.0
	    IL_0017:  call       instance void [System.Private.CoreLib]System.Object::.ctor()
	    IL_001c:  ret
	  } // end of method Class1::.ctor

If you attempt to run the .NET 8 RC2 build, it fails at runtime:

	I MonoDroid: android.runtime.JavaProxyThrowable: [System.TypeLoadException]: Arg_TypeLoadException
	I MonoDroid:     at App.MainActivity.OnCreate(Unknown Source:0)
	I MonoDroid:     at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(Unknown Source:0)

The problem is that `RefsLibs.dll` isn't being fixed up as part of
dc3ccf28, because it doesn't contain any `@(AndroidResource)` values
or an `[assembly: ResourceDesigner]` custom attribute.

`RefsLibs.dll` is "free floating", with nothing to indicate that it
needs to be updated.

Fix this scenario by updating `LinkDesignerBase` to "sanity check"
all "Resource-like" member references which cannot be resolved.

Consider:

	% monodis --memberref RefsLibs/bin/Release/net7.0-android/RefsLibs.dll
	
	18: TypeRef[23] ako_name
	        Resolved: [Ako]Ako.Resource/String.ako_name
	        Signature: int32
	
	19: TypeRef[25] bko_name
	        Resolved: [Bko]Bko.Resource/String.bko_name
	        Signature: int32

These are field references.  In the context of .NET 8/dc3ccf28, these
fields *will not exist*; they cannot be resolved.  `LinkDesignerBase`
will check the member references table of *all* assemblies included
in the app, and if any member references contain a declaring type
which contains `.Resource/` *and* that member reference cannot be
resolved, we will assume that it is an `@(AndroidReference)` and
replace it with a `call` to the appropriate method.

With the fix in place, `ilverify` no longer reports
`Error [ClassLoadGeneral]`, and `ikdasm` shows:

	% ikdasm App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll
	
	  .method public hidebysig specialname rtspecialname 
	          instance void  .ctor() cil managed
	  {
	    // Code size       29 (0x1d)
	    .maxstack  8
	    IL_0000:  ldarg.0
	    IL_0001:  call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/String::get_ako_name()
	    IL_0006:  stfld      int32 RefsLibs.Class1::a
	    IL_000b:  ldarg.0
	    IL_000c:  call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/String::get_bko_name()
	    IL_0011:  stfld      int32 RefsLibs.Class1::b
	    IL_0016:  ldarg.0
	    IL_0017:  call       instance void [System.Private.CoreLib]System.Object::.ctor()
	    IL_001c:  ret
	  } // end of method Class1::.ctor

Note that the .NET 7 `ldsfld` has been replaced with `call`.

Co-authored-by: Dean Ellis <[email protected]>

@michaelonz
Copy link

Hi Guys - this issue is effecting me .... any ideas when this will be available to try? (or if there is a way to try it now then please let me know how :) )

Copy link
Contributor

@dellis1972 dellis1972 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me.

@dellis1972
Copy link
Contributor

dellis1972 commented Oct 17, 2023

Hi Guys - this issue is effecting me .... any ideas when this will be available to try? (or if there is a way to try it now then please let me know how :) )

You can use the AndroidUseDesignerAssembly property if you set that to false and revert back to the old system.

@jonpryor jonpryor merged commit 03018e0 into main Oct 17, 2023
@jonpryor jonpryor deleted the test-maui-17265 branch October 17, 2023 15:00
jonathanpeppers added a commit that referenced this pull request Oct 17, 2023
Fixes: dotnet/maui#17265

Context: dc3ccf2
Context: https://github.com/Zack-G-I-T/SfListView.Net8Bug/tree/ecb25af3329391858d1d64c4875ca58771e2b66c

Commit dc3ccf2 completely reworked how Android Resources work,
moving from a `$(RootNamespace).Resource` type which contained fields
(which needed to be updated at runtime across numerous assemblies)
to a "`_Microsoft.Android.Resource.Designer` reference assembly"
which contained *methods* for each Resource id.

To maintain backward compatibility, pre-.NET 8 assemblies were
rewritten so that instead of accessing fields:

	ldsfld     int32 $(RootNamespace).Resource/Layout::Toolbar

they became method calls:

	call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/Layout::get_Toolbar()

Unfortunately we encountered a missing corner case: the previous
fixup logic only operated on assemblies that themselves contained a
`$(RootNamespace).Resource` type, which in turn (generally) required
that the assembly be built from a project that had
`@(AndroidResource)` items.

In dotnet/maui#17265 and Zack-G-I-T/SfListView.Net8Bug, we
encountered an assembly that:

 1. Did *not* contain a `Resource` type, and
 2. Used Resource ids from other assemblies, and
 3. Was a .NET 7 assembly, so it and its dependencies were all using
    the .NET 7 field-based Resource approach.

Setup:

  * `Ako` and `Bko` are net7.0-android projects which contain an
    `@(AndroidResource)`
  * `RefsLibs` is a net7.0-android project which references `Ako` and
    `Bko`, and uses the Resource values from them.
  * `App` is a net8.0-android project which references `RefsLibs`.


"Repro" setup:

	dotnet new androidlib -n Ako
	# Set `$(TargetFramework)`=net7.0-android
	# Add Ako/Resources/values/strings.xml with String resource ako_name

	dotnet new androidlib -n Bko
	# Set `$(TargetFramework)`=net7.0-android
	# Add Bko/Resources/values/strings.xml with String resource bko_name

	dotnet new androidlib -n RefsLibs
	# Set `$(TargetFramework)`=net7.0-android
	# Add ProjectReference to ..\Ako\Ako.csproj, ..\Bko\Bko.csproj
	# Update `RefsLibs\Class1.cs` to use Ako.Resource.String.ako_name, Bko.Resource.String.bko_name

	dotnet new android -n App
	# *Remains* `$(TargetFramework)`=net8.0-android
	# Add ProjectReference to ..\RefsLibs\RefsLibs.csproj

The punch:

	dotnet build App/App.csproj -p:Configuration=Release

This fails to build with .NET 8 RC1:

	ILLink : error IL1013: Error processing '/Users/jon/Downloads/dotnet-sdk-8.0.100-rc.1.23455.8-osx-x64/packs/Microsoft.Android.Sdk.Darwin/34.0.0-rc.1.432/targets/../PreserveLists/Mono.Android.xml'.
	Fatal error in IL Linker
	Unhandled exception. Mono.Linker.LinkerFatalErrorException: ILLink: error IL1013: Error processing '/Users/jon/Downloads/dotnet-sdk-8.0.100-rc.1.23455.8-osx-x64/packs/Microsoft.Android.Sdk.Darwin/34.0.0-rc.1.432/targets/../PreserveLists/Mono.Android.xml'.
	 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'key')
	   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
	   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
	   …
	…/build/Microsoft.NET.ILLink.targets(87,5): error NETSDK1144: Optimizing assemblies for size failed. Optimization can be disabled by setting the PublishTrimmed property to false.

"Interestingly", it *succeeds* with .NET 8 RC2 (no build errors).

Regardless, with both .NET 8 RC1 and RC2, the app is *broken*:

	% dotnet tool install --global dotnet-ilverify

	% $HOME/.dotnet/tools/ilverify App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll \
		--tokens --system-module System.Private.CoreLib \
		-r 'App/obj/Release/net8.0-android/android-arm/linked/*.dll' 
	[IL]: Error [ClassLoadGeneral]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Class1::.ctor()] Failed to load type 'String' from assembly 'Ako, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
	[IL]: Error [CallCtor]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Resource::.ctor()][offset 0x00000001] call to .ctor only allowed to initialize this pointer from within a .ctor. Try newobj.
	[IL]: Error [ThisUninitReturn]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Resource::.ctor()][offset 0x00000006] Return from .ctor when this is uninitialized.
	3 Error(s) Verifying …/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll

If you disassemble `RefsLibs.dll`, you find that it's using `ldsfld`,
not `call`:

	% ikdasm App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll
	…
	  .method public hidebysig specialname rtspecialname 
	          instance void  .ctor() cil managed
	  {
	    // Code size       29 (0x1d)
	    .maxstack  8
	    IL_0000:  ldarg.0
	    IL_0001:  ldsfld     int32 [Ako]Ako.Resource/String::ako_name
	    IL_0006:  stfld      int32 RefsLibs.Class1::a
	    IL_000b:  ldarg.0
	    IL_000c:  ldsfld     int32 [Bko]Bko.Resource/String::bko_name
	    IL_0011:  stfld      int32 RefsLibs.Class1::b
	    IL_0016:  ldarg.0
	    IL_0017:  call       instance void [System.Private.CoreLib]System.Object::.ctor()
	    IL_001c:  ret
	  } // end of method Class1::.ctor

If you attempt to run the .NET 8 RC2 build, it fails at runtime:

	I MonoDroid: android.runtime.JavaProxyThrowable: [System.TypeLoadException]: Arg_TypeLoadException
	I MonoDroid:     at App.MainActivity.OnCreate(Unknown Source:0)
	I MonoDroid:     at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(Unknown Source:0)

The problem is that `RefsLibs.dll` isn't being fixed up as part of
dc3ccf2, because it doesn't contain any `@(AndroidResource)` values
or an `[assembly: ResourceDesigner]` custom attribute.

`RefsLibs.dll` is "free floating", with nothing to indicate that it
needs to be updated.

Fix this scenario by updating `LinkDesignerBase` to "sanity check"
all "Resource-like" member references which cannot be resolved.

Consider:

	% monodis --memberref RefsLibs/bin/Release/net7.0-android/RefsLibs.dll
	…
	18: TypeRef[23] ako_name
	        Resolved: [Ako]Ako.Resource/String.ako_name
	        Signature: int32
	
	19: TypeRef[25] bko_name
	        Resolved: [Bko]Bko.Resource/String.bko_name
	        Signature: int32

These are field references.  In the context of .NET 8/dc3ccf28, these
fields *will not exist*; they cannot be resolved.  `LinkDesignerBase`
will check the member references table of *all* assemblies included
in the app, and if any member references contain a declaring type
which contains `.Resource/` *and* that member reference cannot be
resolved, we will assume that it is an `@(AndroidReference)` and
replace it with a `call` to the appropriate method.

With the fix in place, `ilverify` no longer reports
`Error [ClassLoadGeneral]`, and `ikdasm` shows:

	% ikdasm App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll
	…
	  .method public hidebysig specialname rtspecialname 
	          instance void  .ctor() cil managed
	  {
	    // Code size       29 (0x1d)
	    .maxstack  8
	    IL_0000:  ldarg.0
	    IL_0001:  call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/String::get_ako_name()
	    IL_0006:  stfld      int32 RefsLibs.Class1::a
	    IL_000b:  ldarg.0
	    IL_000c:  call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/String::get_bko_name()
	    IL_0011:  stfld      int32 RefsLibs.Class1::b
	    IL_0016:  ldarg.0
	    IL_0017:  call       instance void [System.Private.CoreLib]System.Object::.ctor()
	    IL_001c:  ret
	  } // end of method Class1::.ctor

Note that the .NET 7 `ldsfld` has been replaced with `call`.

Co-authored-by: Dean Ellis <[email protected]>
jonathanpeppers added a commit that referenced this pull request Oct 17, 2023
Fixes: dotnet/maui#17265

Context: dc3ccf2
Context: https://github.com/Zack-G-I-T/SfListView.Net8Bug/tree/ecb25af3329391858d1d64c4875ca58771e2b66c

Commit dc3ccf2 completely reworked how Android Resources work,
moving from a `$(RootNamespace).Resource` type which contained fields
(which needed to be updated at runtime across numerous assemblies)
to a "`_Microsoft.Android.Resource.Designer` reference assembly"
which contained *methods* for each Resource id.

To maintain backward compatibility, pre-.NET 8 assemblies were
rewritten so that instead of accessing fields:

	ldsfld     int32 $(RootNamespace).Resource/Layout::Toolbar

they became method calls:

	call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/Layout::get_Toolbar()

Unfortunately we encountered a missing corner case: the previous
fixup logic only operated on assemblies that themselves contained a
`$(RootNamespace).Resource` type, which in turn (generally) required
that the assembly be built from a project that had
`@(AndroidResource)` items.

In dotnet/maui#17265 and Zack-G-I-T/SfListView.Net8Bug, we
encountered an assembly that:

 1. Did *not* contain a `Resource` type, and
 2. Used Resource ids from other assemblies, and
 3. Was a .NET 7 assembly, so it and its dependencies were all using
    the .NET 7 field-based Resource approach.

Setup:

  * `Ako` and `Bko` are net7.0-android projects which contain an
    `@(AndroidResource)`
  * `RefsLibs` is a net7.0-android project which references `Ako` and
    `Bko`, and uses the Resource values from them.
  * `App` is a net8.0-android project which references `RefsLibs`.


"Repro" setup:

	dotnet new androidlib -n Ako
	# Set `$(TargetFramework)`=net7.0-android
	# Add Ako/Resources/values/strings.xml with String resource ako_name

	dotnet new androidlib -n Bko
	# Set `$(TargetFramework)`=net7.0-android
	# Add Bko/Resources/values/strings.xml with String resource bko_name

	dotnet new androidlib -n RefsLibs
	# Set `$(TargetFramework)`=net7.0-android
	# Add ProjectReference to ..\Ako\Ako.csproj, ..\Bko\Bko.csproj
	# Update `RefsLibs\Class1.cs` to use Ako.Resource.String.ako_name, Bko.Resource.String.bko_name

	dotnet new android -n App
	# *Remains* `$(TargetFramework)`=net8.0-android
	# Add ProjectReference to ..\RefsLibs\RefsLibs.csproj

The punch:

	dotnet build App/App.csproj -p:Configuration=Release

This fails to build with .NET 8 RC1:

	ILLink : error IL1013: Error processing '/Users/jon/Downloads/dotnet-sdk-8.0.100-rc.1.23455.8-osx-x64/packs/Microsoft.Android.Sdk.Darwin/34.0.0-rc.1.432/targets/../PreserveLists/Mono.Android.xml'.
	Fatal error in IL Linker
	Unhandled exception. Mono.Linker.LinkerFatalErrorException: ILLink: error IL1013: Error processing '/Users/jon/Downloads/dotnet-sdk-8.0.100-rc.1.23455.8-osx-x64/packs/Microsoft.Android.Sdk.Darwin/34.0.0-rc.1.432/targets/../PreserveLists/Mono.Android.xml'.
	 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'key')
	   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
	   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
	   …
	…/build/Microsoft.NET.ILLink.targets(87,5): error NETSDK1144: Optimizing assemblies for size failed. Optimization can be disabled by setting the PublishTrimmed property to false.

"Interestingly", it *succeeds* with .NET 8 RC2 (no build errors).

Regardless, with both .NET 8 RC1 and RC2, the app is *broken*:

	% dotnet tool install --global dotnet-ilverify

	% $HOME/.dotnet/tools/ilverify App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll \
		--tokens --system-module System.Private.CoreLib \
		-r 'App/obj/Release/net8.0-android/android-arm/linked/*.dll' 
	[IL]: Error [ClassLoadGeneral]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Class1::.ctor()] Failed to load type 'String' from assembly 'Ako, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
	[IL]: Error [CallCtor]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Resource::.ctor()][offset 0x00000001] call to .ctor only allowed to initialize this pointer from within a .ctor. Try newobj.
	[IL]: Error [ThisUninitReturn]: […/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll : RefsLibs.Resource::.ctor()][offset 0x00000006] Return from .ctor when this is uninitialized.
	3 Error(s) Verifying …/App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll

If you disassemble `RefsLibs.dll`, you find that it's using `ldsfld`,
not `call`:

	% ikdasm App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll
	…
	  .method public hidebysig specialname rtspecialname 
	          instance void  .ctor() cil managed
	  {
	    // Code size       29 (0x1d)
	    .maxstack  8
	    IL_0000:  ldarg.0
	    IL_0001:  ldsfld     int32 [Ako]Ako.Resource/String::ako_name
	    IL_0006:  stfld      int32 RefsLibs.Class1::a
	    IL_000b:  ldarg.0
	    IL_000c:  ldsfld     int32 [Bko]Bko.Resource/String::bko_name
	    IL_0011:  stfld      int32 RefsLibs.Class1::b
	    IL_0016:  ldarg.0
	    IL_0017:  call       instance void [System.Private.CoreLib]System.Object::.ctor()
	    IL_001c:  ret
	  } // end of method Class1::.ctor

If you attempt to run the .NET 8 RC2 build, it fails at runtime:

	I MonoDroid: android.runtime.JavaProxyThrowable: [System.TypeLoadException]: Arg_TypeLoadException
	I MonoDroid:     at App.MainActivity.OnCreate(Unknown Source:0)
	I MonoDroid:     at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(Unknown Source:0)

The problem is that `RefsLibs.dll` isn't being fixed up as part of
dc3ccf2, because it doesn't contain any `@(AndroidResource)` values
or an `[assembly: ResourceDesigner]` custom attribute.

`RefsLibs.dll` is "free floating", with nothing to indicate that it
needs to be updated.

Fix this scenario by updating `LinkDesignerBase` to "sanity check"
all "Resource-like" member references which cannot be resolved.

Consider:

	% monodis --memberref RefsLibs/bin/Release/net7.0-android/RefsLibs.dll
	…
	18: TypeRef[23] ako_name
	        Resolved: [Ako]Ako.Resource/String.ako_name
	        Signature: int32
	
	19: TypeRef[25] bko_name
	        Resolved: [Bko]Bko.Resource/String.bko_name
	        Signature: int32

These are field references.  In the context of .NET 8/dc3ccf28, these
fields *will not exist*; they cannot be resolved.  `LinkDesignerBase`
will check the member references table of *all* assemblies included
in the app, and if any member references contain a declaring type
which contains `.Resource/` *and* that member reference cannot be
resolved, we will assume that it is an `@(AndroidReference)` and
replace it with a `call` to the appropriate method.

With the fix in place, `ilverify` no longer reports
`Error [ClassLoadGeneral]`, and `ikdasm` shows:

	% ikdasm App/obj/Release/net8.0-android/android-arm/linked/RefsLibs.dll
	…
	  .method public hidebysig specialname rtspecialname 
	          instance void  .ctor() cil managed
	  {
	    // Code size       29 (0x1d)
	    .maxstack  8
	    IL_0000:  ldarg.0
	    IL_0001:  call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/String::get_ako_name()
	    IL_0006:  stfld      int32 RefsLibs.Class1::a
	    IL_000b:  ldarg.0
	    IL_000c:  call       int32 [_Microsoft.Android.Resource.Designer]_Microsoft.Android.Resource.Designer.Resource/String::get_bko_name()
	    IL_0011:  stfld      int32 RefsLibs.Class1::b
	    IL_0016:  ldarg.0
	    IL_0017:  call       instance void [System.Private.CoreLib]System.Object::.ctor()
	    IL_001c:  ret
	  } // end of method Class1::.ctor

Note that the .NET 7 `ldsfld` has been replaced with `call`.

Co-authored-by: Dean Ellis <[email protected]>
grendello added a commit to grendello/xamarin-android that referenced this pull request Oct 18, 2023
* main:
  [Mono.Android] Generate API docs with "full" verbosity (dotnet#8435)
  [profiled-aot] update profile for .NET 8 GA (dotnet#8428)
  Bump to xamarin/Java.Interop/main@3c83179 (dotnet#8429)
  [Xamarin.Android.Build.Tasks] Small refactoring to `AddKeepAlives` method (dotnet#8423)
  [Xamarin.Android.Build.Tasks] Fixup indirect resource references (dotnet#8416)
@github-actions github-actions bot locked and limited conversation to collaborators Jan 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[regression/8.0.0-preview.1.7762] .NET 8 RC1 cannot use Syncfusion controls - SfListView gives System.TypeLoadException
4 participants