Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Xamarin.Android.Build.Tasks] Fixup indirect resource references (#8416)
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]>
- Loading branch information