Skip to content

Commit

Permalink
[linker] preserve interfaces on Java types
Browse files Browse the repository at this point in the history
Fixes: #7097
Context: xamarin/monodroid@a619cbe

Usage of a `Google.Android.Material.TextField.TextInputEditText`:

    var filterBox = FindViewById<TextInputEditText>(Resource.Id.filterBox);
    filterBox.TextChanged += (s, e) => { };

Crashes at runtime with:

    android.runtime.JavaProxyThrowable: System.TypeLoadException: Could not load type '{0}' from assembly '{1}'., Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
        at System.RuntimeTypeHandle.GetTypeByName(String , Boolean , Boolean , StackCrawlMark& , Boolean )
        at System.RuntimeType.GetType(String , Boolean , Boolean , StackCrawlMark& )
        at System.Type.GetType(String , Boolean )
        at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , ReadOnlySpan`1 )
    --- End of stack trace from previous location ---
        at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* )
        at Android.Runtime.JNIEnv.FindClass(String )
        at Android.Runtime.JNIEnv.AllocObject(String )
        at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* )
        at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] )
        at Android.Text.TextWatcherImplementor..ctor(Object , EventHandler`1 , EventHandler`1 , EventHandler`1 )
        at Android.Widget.TextView.add_TextChanged(EventHandler`1 )
        at AndroidApp1.MainActivity.OnCreate(Bundle savedInstanceState)
        at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(IntPtr , IntPtr , IntPtr )
        at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V(_JniMarshal_PPL_V , IntPtr , IntPtr , IntPtr )
        at crc64a6e0c00971f6cd91.MainActivity.n_onCreate(Native Method)
        at crc64a6e0c00971f6cd91.MainActivity.onCreate(MainActivity.java:29)

The problem being that the linker completely removed the
`ITextWatcher` interface, and the
`Android.Text.TextWatcherImplementor` type no longer implemented that
interface.

It appears we should preserve interfaces in .NET 6 for Java types
*except* if the type defines:

    [Android.Runtime.Register (DoNotGenerateAcw = true)]

This is on types like `Android.App.Activity` where we would *not* need
to preserve all the interfaces.

However, types like `TextWatcherImplementor` would get their
interfaces preserved appropriately.
  • Loading branch information
jonathanpeppers committed Sep 7, 2022
1 parent 8398005 commit 7e7eb17
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ void PreserveJavaObjectImplementation (TypeDefinition type)
PreserveIntPtrConstructor (type);
PreserveAttributeSetConstructor (type);
PreserveInvoker (type);
#if ILLINK
PreserveInterfaces (type);
#endif // ILLINK
}

void PreserveAttributeSetConstructor (TypeDefinition type)
Expand Down Expand Up @@ -196,7 +199,38 @@ void PreserveInvoker (TypeDefinition type)
PreserveConstructors (type, invoker);
PreserveIntPtrConstructor (invoker);
PreserveInterfaceMethods (type, invoker);
#if ILLINK
PreserveInterfaces (invoker);
#endif // ILLINK
}

#if ILLINK
void PreserveInterfaces (TypeDefinition type)
{
if (!type.HasInterfaces)
return;

// Return if [Register(DoNotGenerateAcw=true)] is on the type
if (type.HasCustomAttributes) {
foreach (var attr in type.CustomAttributes) {
if (attr.AttributeType.FullName == "Android.Runtime.RegisterAttribute") {
foreach (var property in attr.Properties) {
if (property.Name == "DoNotGenerateAcw") {
if (property.Argument.Value is bool value && value)
return;
break;
}
}
break;
}
}
}

foreach (var iface in type.Interfaces) {
Annotations.Mark (iface.InterfaceType.Resolve ());
}
}
#endif // ILLINK

TypeDefinition GetInvokerType (TypeDefinition type)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ protected override void OnCreate(Bundle bundle)
// [Test] Post
Android.Util.Log.Info(TAG, HttpClientTest.Post ());

// [Test] TextChanged
Android.Util.Log.Info (TAG, MaterialTextChanged.TextChanged (this));

var cldt = new CustomLinkerDescriptionTests();
Android.Util.Log.Info(TAG, cldt.TryAccessNonXmlPreservedMethodOfLinkerModeFullClass());
Android.Util.Log.Info(TAG, LinkTestLib.Bug21578.MulticastOption_ShouldNotBeStripped());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using Android.Content;
using Google.Android.Material.TextField;

public class MaterialTextChanged
{
// [Test]
public static string TextChanged (Context context)
{
try {
var view = new TextInputEditText (context);
view.TextChanged += (s, e) => { };
return $"[PASS] {nameof (MaterialTextChanged)}.{nameof (TextChanged)}";
} catch (Exception ex) {
return $"[FAIL] {nameof (MaterialTextChanged)}.{nameof (TextChanged)} FAILED: {ex}";
}
}
}
18 changes: 18 additions & 0 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,24 @@ public class LinkModeFullClass {
new BuildItem ("ProjectReference", "..\\Library1\\Library1.csproj"),
new BuildItem ("ProjectReference", "..\\LinkTestLib\\LinkTestLib.csproj"),
},
PackageReferences = {
KnownPackages.AndroidXMigration,
KnownPackages.AndroidXAppCompat,
KnownPackages.AndroidXAppCompatResources,
KnownPackages.AndroidXBrowser,
KnownPackages.AndroidXMediaRouter,
KnownPackages.AndroidXLegacySupportV4,
KnownPackages.AndroidXLifecycleLiveData,
KnownPackages.XamarinGoogleAndroidMaterial,
},
Sources = {
new BuildItem.Source ("MaterialTextChanged.cs") {
TextContent = () => {
using (var sr = new StreamReader (typeof (InstallAndRunTests).Assembly.GetManifestResourceStream ("Xamarin.Android.Build.Tests.Resources.LinkDescTest.MaterialTextChanged.cs")))
return sr.ReadToEnd ();
},
},
},
OtherBuildItems = {
new BuildItem ("LinkDescription", "linker.xml") {
TextContent = () => linkMode == AndroidLinkMode.SdkOnly ? "<linker/>" : @"
Expand Down

0 comments on commit 7e7eb17

Please sign in to comment.