Skip to content

Commit

Permalink
[linker] preserve interfaces on *Implementor 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.

Reviewing the `MarkJavaObjects` linker step, the `PreserveImplementor`
merely preserved any methods named `*Handler`? I added code to
preserve interfaces with a matching name, such as: `ITextWatcher` ->
`TextWatcherImplementor`.

I updated a test for this scenario.
  • Loading branch information
jonathanpeppers committed Aug 1, 2022
1 parent 57d42a8 commit 91efce1
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ void ProcessType (TypeDefinition type)
return;

PreserveJavaObjectImplementation (type);

if (IsImplementor (type))
PreserveImplementor (type);
PreserveImplementor (type);

// If a user overrode a method, we need to preserve it,
// because it won't be referenced anywhere, but it will
Expand Down Expand Up @@ -264,24 +262,20 @@ void PreserveConstructors (TypeDefinition type, TypeDefinition helper)
PreserveMethod (type, ctor);
}

static bool IsImplementor (TypeDefinition type)
{
return type.Name.EndsWith ("Implementor", StringComparison.Ordinal) && type.Inherits ("Java.Lang.Object");
}

static bool IsUserType (TypeDefinition type)
{
return !MonoAndroidHelper.IsFrameworkAssembly (type.Module.Assembly.Name.Name + ".dll");
}

void PreserveImplementor (TypeDefinition type)
{
if (!type.HasMethods)
if (!type.Name.EndsWith ("Implementor", StringComparison.Ordinal) || !type.Inherits ("Java.Lang.Object"))
return;

foreach (MethodDefinition method in type.Methods)
if (method.Name.EndsWith ("Handler", StringComparison.Ordinal))
PreserveMethod (type, method);
if (!type.HasInterfaces)
return;
foreach (var iface in type.Interfaces) {
Annotations.Mark (ifaceType.Resolve ());
}
}
}
}
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 91efce1

Please sign in to comment.