From 83a549f2f4dc62bb36ace3cbbc34386202701566 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Thu, 24 Apr 2025 13:48:06 -1000 Subject: [PATCH] [XABT] Separate marshal method storage from `MarshalMethodClassifier` to `MarshalMethodCollection`. --- .../MonoDroid.Tuner/FindJavaObjectsStep.cs | 6 +- .../Tasks/GenerateJavaStubs.cs | 15 +- .../Tasks/GenerateMainAndroidManifest.cs | 2 +- .../Tasks/RewriteMarshalMethods.cs | 11 +- .../MarshalMethodTests.cs | 199 ++++++++++ .../Utilities/CecilExtensions.cs | 186 +++++++++ .../Utilities/JCWGenerator.cs | 57 +-- .../MarshalMethodsAssemblyRewriter.cs | 8 +- .../Utilities/MarshalMethodsClassifier.cs | 352 ++++++------------ .../Utilities/MarshalMethodsCollection.cs | 303 +++++++++++++++ .../Utilities/NativeCodeGenState.cs | 4 +- .../Utilities/XAJavaTypeScanner.cs | 13 + .../Android.Runtime/RegisterAttribute.cs | 20 +- 13 files changed, 863 insertions(+), 313 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/MarshalMethodTests.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/CecilExtensions.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsCollection.cs diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs index 5adc2e27582..2f196057af7 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs @@ -119,8 +119,10 @@ List ConvertToCallableWrappers (List types) DefaultMonoRuntimeInitialization = "mono.MonoPackageManager.LoadApplication (context);", }; - if (UseMarshalMethods) - reader_options.MethodClassifier = new MarshalMethodsClassifier (Context, Context.Resolver, Log); + if (UseMarshalMethods) { + var classifier = new MarshalMethodsClassifier (Context, Context.Resolver, Log); + reader_options.MethodClassifier = new MarshalMethodsCollection (classifier); + } foreach (var type in types) { var wrapper = CecilImporter.CreateType (type, Context, reader_options); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index e06e51ebe3e..3cb7e47f74c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -212,25 +212,28 @@ internal static Dictionary MaybeGetArchAssemblies (Dictionary XAAssemblyResolver resolver = MakeResolver (useMarshalMethods, arch, assemblies); var tdCache = new TypeDefinitionCache (); (List allJavaTypes, List javaTypesForJCW) = ScanForJavaTypes (resolver, tdCache, assemblies, userAssemblies, useMarshalMethods); - var jcwContext = new JCWGeneratorContext (arch, resolver, assemblies.Values, javaTypesForJCW, tdCache, useMarshalMethods); + var jcwContext = new JCWGeneratorContext (arch, resolver, assemblies.Values, javaTypesForJCW, tdCache); var jcwGenerator = new JCWGenerator (Log, jcwContext) { CodeGenerationTarget = codeGenerationTarget, }; - bool success; + bool success = true; if (generateJavaCode && RunCheckedBuild) { - success = jcwGenerator.GenerateAndClassify (AndroidSdkPlatform, outputPath: Path.Combine (OutputDirectory, "src"), ApplicationJavaClass); + success = jcwGenerator.Generate (AndroidSdkPlatform, outputPath: Path.Combine (OutputDirectory, "src"), ApplicationJavaClass); generatedJavaFiles = jcwGenerator.GeneratedJavaFiles; - } else { - success = jcwGenerator.Classify (AndroidSdkPlatform); } if (!success) { return (false, null); } - return (true, new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, jcwGenerator.Classifier)); + MarshalMethodsCollection? marshalMethodsCollection = null; + + if (useMarshalMethods) + marshalMethodsCollection = MarshalMethodsCollection.FromAssemblies (arch, assemblies.Values.ToList (), resolver, Log); + + return (true, new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, marshalMethodsCollection)); } (List allJavaTypes, List javaTypesForJCW) ScanForJavaTypes (XAAssemblyResolver res, TypeDefinitionCache cache, Dictionary assemblies, Dictionary userAssemblies, bool useMarshalMethods) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs index 5310cc1290f..1b9195d85c7 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs @@ -177,7 +177,7 @@ void GenerateAdditionalProviderSources (NativeCodeGenState codeGenState, IList 0) { - Log.LogWarning ($"[{state.TargetArch}] Number of methods in the project that will be registered dynamically: {state.Classifier.RejectedMethodCount}"); + if (state.Classifier.DynamicallyRegisteredMarshalMethods.Count > 0) { + Log.LogWarning ($"[{state.TargetArch}] Number of methods in the project that will be registered dynamically: {state.Classifier.DynamicallyRegisteredMarshalMethods.Count}"); } - if (state.Classifier.WrappedMethodCount > 0) { + var wrappedCount = state.Classifier.MarshalMethods.Sum (m => m.Value.Count (m2 => m2.NeedsBlittableWorkaround)); + + if (wrappedCount > 0) { // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers - Log.LogDebugMessage ($"[{state.TargetArch}] Number of methods in the project that need marshal method wrappers: {state.Classifier.WrappedMethodCount}"); + Log.LogDebugMessage ($"[{state.TargetArch}] Number of methods in the project that need marshal method wrappers: {wrappedCount}"); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/MarshalMethodTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/MarshalMethodTests.cs new file mode 100644 index 00000000000..20873467b64 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/MarshalMethodTests.cs @@ -0,0 +1,199 @@ +#nullable enable +using System; +using System.IO; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; +using Xamarin.Android.Tasks; +using Xamarin.ProjectTools; + +namespace Xamarin.Android.Build.Tests; + +public class MarshalMethodTests : BaseTest +{ + [Test] + public void MarshalMethodsCollectionScanning () + { + // This test does 2 things: + // - Builds a binding project in Debug mode to create an assembly that contains convertible + // marshal methods to ensure they are found by MarshalMethodsCollection + // - Builds the same project in Release mode which rewrites the assembly to ensure those + // same marshal methods can be found after they are rewritten + var proj = new XamarinAndroidApplicationProject { + ProjectName = "mmtest", + }; + + proj.Sources.Add (new AndroidItem.AndroidLibrary ("javaclasses.jar") { + BinaryContent = () => ResourceData.JavaSourceJarTestJar, + }); + + proj.AndroidJavaSources.Add (new AndroidItem.AndroidJavaSource ("JavaSourceTestInterface.java") { + Encoding = Encoding.ASCII, + TextContent = () => ResourceData.JavaSourceTestInterface, + Metadata = { { "Bind", "True" } }, + }); + + proj.AndroidJavaSources.Add (new AndroidItem.AndroidJavaSource ("JavaSourceTestExtension.java") { + Encoding = Encoding.ASCII, + TextContent = () => ResourceData.JavaSourceTestExtension, + Metadata = { { "Bind", "True" } }, + }); + + proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_MAINACTIVITY}", """ + // Implements Java interface method + class MyGreeter : Java.Lang.Object, Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterface { + public virtual string? GreetWithQuestion (string? p0, Java.Util.Date? p1, string? p2) => "greetings!"; + } + + // Overrides implemented Java interface method + class MyExtendedGreeter : MyGreeter { + public override string? GreetWithQuestion (string? p0, Java.Util.Date? p1, string? p2) => "more greetings!"; + } + + // Implements Java interface method (duplicate) + class MyGreeter2 : Java.Lang.Object, Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterface { + public virtual string? GreetWithQuestion (string? p0, Java.Util.Date? p1, string? p2) => "duplicate greetings!"; + } + + // Overrides Java class method + class MyOverriddenGreeter : Com.Xamarin.Android.Test.Msbuildtest.JavaSourceTestExtension { + public override string? GreetWithQuestion (string? p0, Java.Util.Date? p1, string? p2) => "even more greetings!"; + } + """); + + var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "`dotnet build` should succeed"); + builder.AssertHasNoWarnings (); + + var intermediateDebugOutputPath = Path.Combine (Root, builder.ProjectDirectory, proj.IntermediateOutputPath, "android", "assets", "arm64-v8a"); + var outputDebugDll = Path.Combine (intermediateDebugOutputPath, $"{proj.ProjectName}.dll"); + + var log = new TaskLoggingHelper (new MockBuildEngine (TestContext.Out, [], [], []), nameof (MarshalMethodsCollectionScanning)); + var xaResolver = new XAAssemblyResolver (Tools.AndroidTargetArch.Arm64, log, false); + xaResolver.SearchDirectories.Add (Path.GetDirectoryName (outputDebugDll)!); + + var collection = MarshalMethodsCollection.FromAssemblies (Tools.AndroidTargetArch.Arm64, [CreateItem (outputDebugDll, "arm64-v8a")], xaResolver, log); + + Assert.AreEqual (3, collection.MarshalMethods.Count); + Assert.AreEqual (0, collection.ConvertedMarshalMethods.Count); + + var key1 = "Android.App.Activity, Mono.Android\tOnCreate"; + var key2 = "Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterface, mmtest\tGreetWithQuestion"; + var key3 = "Com.Xamarin.Android.Test.Msbuildtest.JavaSourceTestExtension, mmtest\tGreetWithQuestion"; + + Assert.AreEqual (1, collection.MarshalMethods [key1].Count); + Assert.AreEqual (2, collection.MarshalMethods [key2].Count); + Assert.AreEqual (1, collection.MarshalMethods [key3].Count); + + AssertMarshalMethodData (collection.MarshalMethods [key1] [0], + callbackField: null, + connector: "System.Delegate Android.App.Activity::GetOnCreate_Landroid_os_Bundle_Handler()", + declaringType: "mmtest.MainActivity", + implementedMethod: "System.Void mmtest.MainActivity::OnCreate(Android.OS.Bundle)", + jniMethodName: "onCreate", + jniMethodSignature: "(Landroid/os/Bundle;)V", + jniTypeName: "com/xamarin/marshalmethodscollectionscanning/MainActivity", + nativeCallback: "System.Void Android.App.Activity::n_OnCreate_Landroid_os_Bundle_(System.IntPtr,System.IntPtr,System.IntPtr)", + registeredMethod: "System.Void Android.App.Activity::OnCreate(Android.OS.Bundle)"); + + AssertMarshalMethodData (collection.MarshalMethods [key2] [0], + callbackField: null, + connector: "System.Delegate Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterfaceInvoker::GetGreetWithQuestion_Ljava_lang_String_Ljava_util_Date_Ljava_lang_String_Handler()", + declaringType: "mmtest.MyGreeter", + implementedMethod: "System.String Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterface::GreetWithQuestion(System.String,Java.Util.Date,System.String)", + jniMethodName: "greetWithQuestion", + jniMethodSignature: "(Ljava/lang/String;Ljava/util/Date;Ljava/lang/String;)Ljava/lang/String;", + jniTypeName: "crc644a923d2fc5ca7023/MyGreeter", + nativeCallback: "System.IntPtr Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterfaceInvoker::n_GreetWithQuestion_Ljava_lang_String_Ljava_util_Date_Ljava_lang_String_(System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr)", + registeredMethod: "System.String Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterface::GreetWithQuestion(System.String,Java.Util.Date,System.String)"); + + AssertMarshalMethodData (collection.MarshalMethods [key2] [1], + callbackField: null, + connector: "System.Delegate Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterfaceInvoker::GetGreetWithQuestion_Ljava_lang_String_Ljava_util_Date_Ljava_lang_String_Handler()", + declaringType: "mmtest.MyGreeter2", + implementedMethod: "System.String Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterface::GreetWithQuestion(System.String,Java.Util.Date,System.String)", + jniMethodName: "greetWithQuestion", + jniMethodSignature: "(Ljava/lang/String;Ljava/util/Date;Ljava/lang/String;)Ljava/lang/String;", + jniTypeName: "crc644a923d2fc5ca7023/MyGreeter2", + nativeCallback: "System.IntPtr Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterfaceInvoker::n_GreetWithQuestion_Ljava_lang_String_Ljava_util_Date_Ljava_lang_String_(System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr)", + registeredMethod: "System.String Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterface::GreetWithQuestion(System.String,Java.Util.Date,System.String)"); + + AssertMarshalMethodData (collection.MarshalMethods [key3] [0], + callbackField: null, + connector: "System.Delegate Com.Xamarin.Android.Test.Msbuildtest.JavaSourceTestExtension::GetGreetWithQuestion_Ljava_lang_String_Ljava_util_Date_Ljava_lang_String_Handler()", + declaringType: "mmtest.MyOverriddenGreeter", + implementedMethod: "System.String mmtest.MyOverriddenGreeter::GreetWithQuestion(System.String,Java.Util.Date,System.String)", + jniMethodName: "greetWithQuestion", + jniMethodSignature: "(Ljava/lang/String;Ljava/util/Date;Ljava/lang/String;)Ljava/lang/String;", + jniTypeName: "crc644a923d2fc5ca7023/MyOverriddenGreeter", + nativeCallback: "System.IntPtr Com.Xamarin.Android.Test.Msbuildtest.JavaSourceTestExtension::n_GreetWithQuestion_Ljava_lang_String_Ljava_util_Date_Ljava_lang_String_(System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr)", + registeredMethod: "System.String Com.Xamarin.Android.Test.Msbuildtest.JavaSourceTestExtension::GreetWithQuestion(System.String,Java.Util.Date,System.String)"); + + // Recompile with Release so marshal methods get rewritten + proj.IsRelease = true; + + Assert.IsTrue (builder.Build (proj), "`dotnet build` should succeed"); + builder.AssertHasNoWarnings (); + + // Rescan for modified marshal methods + var intermediateReleaseOutputPath = Path.Combine (Root, builder.ProjectDirectory, proj.IntermediateOutputPath, "android-arm64", "linked"); + var outputReleaseDll = Path.Combine (intermediateReleaseOutputPath, $"{proj.ProjectName}.dll"); + + xaResolver = new XAAssemblyResolver (Tools.AndroidTargetArch.Arm64, log, false); + xaResolver.SearchDirectories.Add (Path.GetDirectoryName (outputReleaseDll)!); + + var releaseCollection = MarshalMethodsCollection.FromAssemblies (Tools.AndroidTargetArch.Arm64, [CreateItem (outputReleaseDll, "arm64-v8a")], xaResolver, log); + + Assert.AreEqual (0, releaseCollection.MarshalMethods.Count); + Assert.AreEqual (3, releaseCollection.ConvertedMarshalMethods.Count); + + AssertRewrittenMethodData (releaseCollection.ConvertedMarshalMethods [key1] [0], collection.MarshalMethods [key1] [0]); + AssertRewrittenMethodData (releaseCollection.ConvertedMarshalMethods [key2] [0], collection.MarshalMethods [key2] [0]); + AssertRewrittenMethodData (releaseCollection.ConvertedMarshalMethods [key2] [1], collection.MarshalMethods [key2] [1]); + AssertRewrittenMethodData (releaseCollection.ConvertedMarshalMethods [key3] [0], collection.MarshalMethods [key3] [0]); + } + + void AssertMarshalMethodData (MarshalMethodEntry entry, string? callbackField, string? connector, string? declaringType, + string? implementedMethod, string? jniMethodName, string? jniMethodSignature, string? jniTypeName, + string? nativeCallback, string? registeredMethod) + { + Assert.AreEqual (callbackField, entry.CallbackField?.ToString (), "Callback field should be the same."); + Assert.AreEqual (connector, entry.Connector?.ToString (), "Connector should be the same."); + Assert.AreEqual (declaringType, entry.DeclaringType.ToString (), "Declaring type should be the same."); + Assert.AreEqual (implementedMethod, entry.ImplementedMethod?.ToString (), "Implemented method should be the same."); + Assert.AreEqual (jniMethodName, entry.JniMethodName, "JNI method name should be the same."); + Assert.AreEqual (jniMethodSignature, entry.JniMethodSignature, "JNI method signature should be the same."); + Assert.AreEqual (jniTypeName, entry.JniTypeName, "JNI type name should be the same."); + Assert.AreEqual (nativeCallback, entry.NativeCallback.ToString (), "Native callback should be the same."); + Assert.AreEqual (registeredMethod, entry.RegisteredMethod?.ToString (), "Registered method should be the same."); + } + + void AssertRewrittenMethodData (ConvertedMarshalMethodEntry converted, MarshalMethodEntry entry) + { + // Things that are different between the two: + Assert.IsNull (converted.CallbackField, "Callback field will be null."); + Assert.IsNull (converted.Connector, "Connector will be null."); + + var nativeCallback = converted.NativeCallback?.ToString () ?? ""; + var paren = nativeCallback.IndexOf ('('); + var convertedNativeCallback = nativeCallback.Substring (0, paren) + "_mm_wrapper" + nativeCallback.Substring (paren); + Assert.AreEqual (convertedNativeCallback, converted.ConvertedNativeCallback?.ToString (), "ConvertedNativeCallback should be the same."); + + // Things that should be the same between the two: + Assert.AreEqual (entry.DeclaringType.ToString (), converted.DeclaringType.ToString (), "Declaring type should be the same."); + Assert.AreEqual (entry.ImplementedMethod?.ToString (), converted.ImplementedMethod?.ToString (), "Implemented method should be the same."); + Assert.AreEqual (entry.JniMethodName, converted.JniMethodName, "JNI method name should be the same."); + Assert.AreEqual (entry.JniMethodSignature, converted.JniMethodSignature, "JNI method signature should be the same."); + Assert.AreEqual (entry.JniTypeName, converted.JniTypeName, "JNI type name should be the same."); + Assert.AreEqual (entry.NativeCallback.ToString (), converted.NativeCallback?.ToString (), "Native callback should be the same."); + Assert.AreEqual (entry.RegisteredMethod?.ToString (), converted.RegisteredMethod?.ToString (), "Registered method should be the same."); + } + + static ITaskItem CreateItem (string itemSpec, string abi) + { + var item = new TaskItem (itemSpec); + item.SetMetadata ("Abi", abi); + return item; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CecilExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CecilExtensions.cs new file mode 100644 index 00000000000..a111a25efc4 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/CecilExtensions.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Android.Runtime; +using Java.Interop.Tools.Cecil; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Xamarin.Android.Tasks; + +static class CecilExtensions +{ + public static MethodDefinition? GetBaseRegisteredMethod (MethodDefinition method, IMetadataResolver cache) + { + MethodDefinition bmethod; + while ((bmethod = method.GetBaseDefinition (cache)) != method) { + method = bmethod; + + if (HasMethodRegistrationAttributes (method)) { + return method; + } + } + return null; + } + + static readonly string [] MethodRegistrationAttributes = new []{ + typeof (RegisterAttribute).FullName, + "Java.Interop.JniConstructorSignatureAttribute", + "Java.Interop.JniMethodSignatureAttribute", + }; + + // Keep in sync w/ HasMethodRegistrationAttributes() + public static IEnumerable GetMethodRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p) + { + foreach (var a in CecilExtensions.GetAttributes (p, a => CecilExtensions.ToRegisterAttribute (a))) { + yield return a; + } + foreach (var c in p.GetCustomAttributes ("Java.Interop.JniConstructorSignatureAttribute")) { + var r = RegisterFromJniConstructorSignatureAttribute (c); + if (r == null) { + continue; + } + yield return r; + } + foreach (var c in p.GetCustomAttributes ("Java.Interop.JniMethodSignatureAttribute")) { + var r = RegisterFromJniMethodSignatureAttribute (c); + if (r == null) { + continue; + } + yield return r; + } + } + + internal static RegisterAttribute? RegisterFromJniMethodSignatureAttribute (CustomAttribute attr) + { + // attr.Resolve (); + RegisterAttribute? r = null; + if (attr.ConstructorArguments.Count == 2) + r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, + (string) attr.ConstructorArguments [1].Value, + "", + attr); + return r; + } + + internal static RegisterAttribute? RegisterFromJniConstructorSignatureAttribute (CustomAttribute attr) + { + // attr.Resolve (); + RegisterAttribute? r = null; + if (attr.ConstructorArguments.Count == 1) + r = new RegisterAttribute ( + name: ".ctor", + signature: (string) attr.ConstructorArguments [0].Value, + connector: "", + originAttribute: attr); + return r; + } + + // Keep in sync w/ GetMethodRegistrationAttributes() + public static bool HasMethodRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p) + { + foreach (CustomAttribute custom_attribute in p.CustomAttributes) { + var customAttrType = custom_attribute.Constructor.DeclaringType.FullName; + foreach (var t in MethodRegistrationAttributes) { + if (customAttrType == t) + return true; + } + } + return false; + } + + public static IEnumerable GetAttributes (Mono.Cecil.ICustomAttributeProvider p, Func selector) + where TAttribute : class + { + return GetAttributes (p, typeof (TAttribute).FullName!, selector); + } + + public static IEnumerable GetAttributes (Mono.Cecil.ICustomAttributeProvider p, string attributeName, Func selector) + where TAttribute : class + { + return p.GetCustomAttributes (attributeName) + .Select (selector) + .Where (v => v != null) + .Select (v => v!); + } + + internal static IEnumerable GetTypeRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p) + { + foreach (var a in CecilExtensions.GetAttributes (p, a => CecilExtensions.ToRegisterAttribute (a))) { + yield return a; + } + foreach (var c in p.GetCustomAttributes ("Java.Interop.JniTypeSignatureAttribute")) { + var r = RegisterFromJniTypeSignatureAttribute (c); + if (r == null) { + continue; + } + yield return r; + } + } + + internal static RegisterAttribute? RegisterFromJniTypeSignatureAttribute (CustomAttribute attr) + { + // attr.Resolve (); + RegisterAttribute? r = null; + if (attr.ConstructorArguments.Count == 1) + r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, attr); + if (r != null) { + var v = attr.Properties.FirstOrDefault (p => p.Name == "GenerateJavaPeer"); + if (v.Name == null) { + r.DoNotGenerateAcw = false; + } else if (v.Name == "GenerateJavaPeer") { + r.DoNotGenerateAcw = !(bool) v.Argument.Value; + } + var isKeyProp = attr.Properties.FirstOrDefault (p => p.Name == "IsKeyword"); + var isKeyword = isKeyProp.Name != null && ((bool) isKeyProp.Argument.Value) == true; + var arrRankProp = attr.Properties.FirstOrDefault (p => p.Name == "ArrayRank"); + if (arrRankProp.Name != null && arrRankProp.Argument.Value is int rank) { + r.Name = new string ('[', rank) + (isKeyword ? r.Name : "L" + r.Name + ";"); + } + } + return r; + } + + internal static RegisterAttribute? ToRegisterAttribute (CustomAttribute attr) + { + // attr.Resolve (); + RegisterAttribute? r = null; + if (attr.ConstructorArguments.Count == 1) + r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, attr); + else if (attr.ConstructorArguments.Count == 3) + r = new RegisterAttribute ( + (string) attr.ConstructorArguments [0].Value, + (string) attr.ConstructorArguments [1].Value, + (string) attr.ConstructorArguments [2].Value, + attr); + if (r != null) { + var v = attr.Properties.FirstOrDefault (p => p.Name == "DoNotGenerateAcw"); + r.DoNotGenerateAcw = v.Name == null ? false : (bool) v.Argument.Value; + } + return r; + } + + public static SequencePoint? LookupSource (TypeDefinition type) + { + SequencePoint? candidate = null; + foreach (var method in type.Methods) { + if (!method.HasBody) + continue; + + foreach (var ins in method.Body.Instructions) { + var seq = method.DebugInformation.GetSequencePoint (ins); + if (seq == null) + continue; + + if (Regex.IsMatch (seq.Document.Url, ".+\\.(g|designer)\\..+")) + break; + if (candidate == null || seq.StartLine < candidate.StartLine) + candidate = seq; + break; + } + } + + return candidate; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs index 5d9d2659084..c813c3405b9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs @@ -21,21 +21,19 @@ namespace Xamarin.Android.Tasks; class JCWGeneratorContext { - public bool UseMarshalMethods { get; } public AndroidTargetArch Arch { get; } public TypeDefinitionCache TypeDefinitionCache { get; } public XAAssemblyResolver Resolver { get; } public IList JavaTypes { get; } public ICollection ResolvedAssemblies { get; } - public JCWGeneratorContext (AndroidTargetArch arch, XAAssemblyResolver res, ICollection resolvedAssemblies, List javaTypesForJCW, TypeDefinitionCache tdCache, bool useMarshalMethods) + public JCWGeneratorContext (AndroidTargetArch arch, XAAssemblyResolver res, ICollection resolvedAssemblies, List javaTypesForJCW, TypeDefinitionCache tdCache) { Arch = arch; Resolver = res; ResolvedAssemblies = resolvedAssemblies; JavaTypes = javaTypesForJCW.AsReadOnly (); TypeDefinitionCache = tdCache; - UseMarshalMethods = useMarshalMethods; } } @@ -46,8 +44,6 @@ class JCWGenerator public JavaPeerStyle CodeGenerationTarget { get; set; } = JavaPeerStyle.XAJavaInterop1; - public MarshalMethodsClassifier? Classifier { get; private set; } - public JCWGenerator (TaskLoggingHelper log, JCWGeneratorContext context) { this.log = log; @@ -56,46 +52,17 @@ public JCWGenerator (TaskLoggingHelper log, JCWGeneratorContext context) public List GeneratedJavaFiles { get; } = []; - /// - /// Performs marshal method classification, if marshal methods are used, but does not generate any code. - /// If marshal methods are used, this method will set the property to a valid - /// classifier instance on return. If marshal methods are disabled, this call is a no-op but it will - /// return true. - /// - public bool Classify (string androidSdkPlatform) - { - if (!context.UseMarshalMethods) { - return true; - } - - Classifier = MakeClassifier (); - return ProcessTypes ( - generateCode: false, - androidSdkPlatform, - Classifier, - outputPath: null, - applicationJavaClass: null - ); - } - - public bool GenerateAndClassify (string androidSdkPlatform, string outputPath, string applicationJavaClass) + public bool Generate (string androidSdkPlatform, string outputPath, string applicationJavaClass) { - if (context.UseMarshalMethods) { - Classifier = MakeClassifier (); - } - return ProcessTypes ( generateCode: true, androidSdkPlatform, - Classifier, outputPath, applicationJavaClass ); } - MarshalMethodsClassifier MakeClassifier () => new MarshalMethodsClassifier (context.Arch, context.TypeDefinitionCache, context.Resolver, log); - - bool ProcessTypes (bool generateCode, string androidSdkPlatform, MarshalMethodsClassifier? classifier, string? outputPath, string? applicationJavaClass) + bool ProcessTypes (bool generateCode, string androidSdkPlatform, string? outputPath, string? applicationJavaClass) { if (generateCode && String.IsNullOrEmpty (outputPath)) { throw new ArgumentException ("must not be null or empty", nameof (outputPath)); @@ -111,12 +78,12 @@ bool ProcessTypes (bool generateCode, string androidSdkPlatform, MarshalMethodsC continue; } - CallableWrapperType generator = CreateGenerator (type, classifier, monoInit, hasExportReference, applicationJavaClass); + CallableWrapperType generator = CreateGenerator (type, monoInit, hasExportReference, applicationJavaClass); if (!generateCode) { continue; } - if (!GenerateCode (generator, type, outputPath, hasExportReference, classifier)) { + if (!GenerateCode (generator, type, outputPath, hasExportReference)) { ok = false; } } @@ -124,7 +91,7 @@ bool ProcessTypes (bool generateCode, string androidSdkPlatform, MarshalMethodsC return ok; } - public bool GenerateCode (CallableWrapperType generator, TypeDefinition type, string outputPath, bool hasExportReference, MarshalMethodsClassifier? classifier) + public bool GenerateCode (CallableWrapperType generator, TypeDefinition type, string outputPath, bool hasExportReference) { bool ok = true; using var writer = MemoryStreamPool.Shared.CreateStreamWriter (); @@ -134,11 +101,6 @@ public bool GenerateCode (CallableWrapperType generator, TypeDefinition type, st try { generator.Generate (writer, writer_options); - if (context.UseMarshalMethods) { - if (classifier.FoundDynamicallyRegisteredMethods (type)) { - log.LogWarning ($"Type '{type.GetAssemblyQualifiedName (context.TypeDefinitionCache)}' will register some of its Java override methods dynamically. This may adversely affect runtime performance. See preceding warnings for names of dynamically registered methods."); - } - } writer.Flush (); string path = generator.GetDestinationPath (outputPath); @@ -182,13 +144,12 @@ public bool GenerateCode (CallableWrapperType generator, TypeDefinition type, st return ok; } - CallableWrapperType CreateGenerator (TypeDefinition type, MarshalMethodsClassifier? classifier, string monoInit, bool hasExportReference, string? applicationJavaClass) + CallableWrapperType CreateGenerator (TypeDefinition type, string monoInit, bool hasExportReference, string? applicationJavaClass) { var reader_options = new CallableWrapperReaderOptions { DefaultApplicationJavaClass = applicationJavaClass, DefaultGenerateOnCreateOverrides = false, // this was used only when targetting Android API <= 10, which is no longer supported DefaultMonoRuntimeInitialization = monoInit, - MethodClassifier = classifier, }; return CecilImporter.CreateType (type, context.TypeDefinitionCache, reader_options); @@ -246,8 +207,8 @@ static void EnsureClassifiersMatch (TaskLoggingHelper logger, NativeCodeGenState { logger.LogDebugMessage ($"Ensuring marshal method classifier in architecture '{state.TargetArch}' matches the one in architecture '{templateState.TargetArch}'"); - MarshalMethodsClassifier? templateClassifier = templateState.Classifier; - MarshalMethodsClassifier? classifier = state.Classifier; + MarshalMethodsCollection? templateClassifier = templateState.Classifier; + MarshalMethodsCollection? classifier = state.Classifier; if (templateClassifier == null) { if (classifier != null) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 335cdadfb6e..15ed73c876e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -23,12 +23,12 @@ sealed class AssemblyImports } readonly TaskLoggingHelper log; - readonly MarshalMethodsClassifier classifier; + readonly MarshalMethodsCollection classifier; readonly XAAssemblyResolver resolver; readonly AndroidTargetArch targetArch; readonly ManagedMarshalMethodsLookupInfo? managedMarshalMethodsLookupInfo; - public MarshalMethodsAssemblyRewriter (TaskLoggingHelper log, AndroidTargetArch targetArch, MarshalMethodsClassifier classifier, XAAssemblyResolver resolver, ManagedMarshalMethodsLookupInfo? managedMarshalMethodsLookupInfo) + public MarshalMethodsAssemblyRewriter (TaskLoggingHelper log, AndroidTargetArch targetArch, MarshalMethodsCollection classifier, XAAssemblyResolver resolver, ManagedMarshalMethodsLookupInfo? managedMarshalMethodsLookupInfo) { this.log = log ?? throw new ArgumentNullException (nameof (log)); this.targetArch = targetArch; @@ -60,7 +60,7 @@ public void Rewrite (bool brokenExceptionTransitions) MethodDefinition unmanagedCallersOnlyAttributeCtor = GetUnmanagedCallersOnlyAttributeConstructor (resolver); var assemblyImports = new Dictionary (); - foreach (AssemblyDefinition asm in classifier.Assemblies) { + foreach (AssemblyDefinition asm in classifier.AssembliesWithMarshalMethods) { var imports = new AssemblyImports { MonoUnhandledExceptionMethod = asm.MainModule.ImportReference (monoUnhandledExceptionMethod), SystemException = asm.MainModule.ImportReference (systemException), @@ -121,7 +121,7 @@ public void Rewrite (bool brokenExceptionTransitions) managedMarshalMethodLookupGenerator.Generate (classifier.MarshalMethods.Values); } - foreach (AssemblyDefinition asm in classifier.Assemblies) { + foreach (AssemblyDefinition asm in classifier.AssembliesWithMarshalMethods) { string? path = asm.MainModule.FileName; if (String.IsNullOrEmpty (path)) { throw new InvalidOperationException ($"[{targetArch}] Internal error: assembly '{asm}' does not specify path to its file"); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 211baaf0948..449f5f3caeb 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using Java.Interop.Tools.Cecil; -using Java.Interop.Tools.JavaCallableWrappers; using Java.Interop.Tools.TypeNameMappings; using Microsoft.Android.Build.Tasks; @@ -13,7 +12,17 @@ namespace Xamarin.Android.Tasks { - sealed class MarshalMethodEntry + class MethodEntry + { + public TypeDefinition DeclaringType { get; } + + public MethodEntry (TypeDefinition declaringType) + { + DeclaringType = declaringType ?? throw new ArgumentNullException (nameof (declaringType)); + } + } + + class MarshalMethodEntry : MethodEntry { /// /// The "real" native callback, used if it doesn't contain any non-blittable types in its parameters @@ -27,7 +36,6 @@ sealed class MarshalMethodEntry /// a non-blittable return type or a parameter of a non-blittable type. /// public MethodDefinition? NativeCallbackWrapper { get; set; } - public TypeDefinition DeclaringType { get; } public MethodDefinition? Connector { get; } public MethodDefinition? RegisteredMethod { get; } public MethodDefinition? ImplementedMethod { get; } @@ -43,10 +51,10 @@ sealed class MarshalMethodEntry public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, MethodDefinition connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, FieldDefinition callbackField, string jniTypeName, string jniName, string jniSignature, bool needsBlittableWorkaround) + : base (declaringType) { - DeclaringType = declaringType ?? throw new ArgumentNullException (nameof (declaringType)); nativeCallbackReal = nativeCallback ?? throw new ArgumentNullException (nameof (nativeCallback)); - Connector = connector ?? throw new ArgumentNullException (nameof (connector)); + Connector = connector; // The connector will not exist for converted marshal methods RegisteredMethod = registeredMethod ?? throw new ArgumentNullException (nameof (registeredMethod)); ImplementedMethod = implementedMethod ?? throw new ArgumentNullException (nameof (implementedMethod)); CallbackField = callbackField; // we don't require the callback field to exist @@ -58,8 +66,8 @@ public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition native } public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, string jniTypeName, string jniName, string jniSignature) + : base (declaringType) { - DeclaringType = declaringType ?? throw new ArgumentNullException (nameof (declaringType)); nativeCallbackReal = nativeCallback ?? throw new ArgumentNullException (nameof (nativeCallback)); JniTypeName = EnsureNonEmpty (jniTypeName, nameof (jniTypeName)); JniMethodName = EnsureNonEmpty (jniName, nameof (jniName)); @@ -90,9 +98,37 @@ public string GetStoreMethodKey (TypeDefinitionCache tdCache) } } - class MarshalMethodsClassifier : JavaCallableMethodClassifier + sealed class ConvertedMarshalMethodEntry : MarshalMethodEntry { - sealed class ConnectorInfo + public MethodDefinition ConvertedNativeCallback { get; } + + public ConvertedMarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, MethodDefinition connector, MethodDefinition + registeredMethod, MethodDefinition implementedMethod, FieldDefinition callbackField, string jniTypeName, + string jniName, string jniSignature, bool needsBlittableWorkaround, MethodDefinition convertedNativeCallback) + : base (declaringType, nativeCallback, connector, registeredMethod, implementedMethod, callbackField, jniTypeName, jniName, jniSignature, needsBlittableWorkaround) + { + ConvertedNativeCallback = convertedNativeCallback ?? throw new ArgumentNullException (nameof (convertedNativeCallback)); + } + } + + sealed class DynamicallyRegisteredMarshalMethodEntry : MethodEntry + { + public MethodDefinition ImplementedMethod { get; } + public CustomAttribute RegisterAttribute { get; } + public MethodDefinition RegisteredMethod { get; } + + public DynamicallyRegisteredMarshalMethodEntry (TypeDefinition declaringType, MethodDefinition implementedMethod, MethodDefinition registeredMethod, CustomAttribute registerAttribute) + : base (declaringType) + { + ImplementedMethod = implementedMethod; + RegisteredMethod = registeredMethod; + RegisterAttribute = registerAttribute; + } + } + + class MarshalMethodsClassifier + { + public sealed class ConnectorInfo { public string MethodName { get; } public string TypeName { get; } @@ -258,19 +294,12 @@ static bool TypeMatches (string type, string methodType) TypeDefinitionCache tdCache; IAssemblyResolver resolver; - Dictionary> marshalMethods; - HashSet assemblies; TaskLoggingHelper log; - HashSet typesWithDynamicallyRegisteredMethods; - ulong rejectedMethodCount = 0; - ulong wrappedMethodCount = 0; readonly AndroidTargetArch targetArch; - public IDictionary> MarshalMethods => marshalMethods; - public ICollection Assemblies => assemblies; - public ulong RejectedMethodCount => rejectedMethodCount; - public ulong WrappedMethodCount => wrappedMethodCount; public TypeDefinitionCache TypeDefinitionCache => tdCache; + public TaskLoggingHelper Log => log; + public IAssemblyResolver Resolver => resolver; public MarshalMethodsClassifier (AndroidTargetArch targetArch, TypeDefinitionCache tdCache, IAssemblyResolver res, TaskLoggingHelper log) { @@ -278,9 +307,6 @@ public MarshalMethodsClassifier (AndroidTargetArch targetArch, TypeDefinitionCac this.log = log ?? throw new ArgumentNullException (nameof (log)); this.tdCache = tdCache ?? throw new ArgumentNullException (nameof (tdCache)); resolver = res ?? throw new ArgumentNullException (nameof (tdCache)); - marshalMethods = new Dictionary> (StringComparer.Ordinal); - assemblies = new HashSet (); - typesWithDynamicallyRegisteredMethods = new HashSet (); } public MarshalMethodsClassifier (TypeDefinitionCache tdCache, IAssemblyResolver res, TaskLoggingHelper log) @@ -288,164 +314,20 @@ public MarshalMethodsClassifier (TypeDefinitionCache tdCache, IAssemblyResolver this.log = log ?? throw new ArgumentNullException (nameof (log)); this.tdCache = tdCache ?? throw new ArgumentNullException (nameof (tdCache)); resolver = res ?? throw new ArgumentNullException (nameof (tdCache)); - marshalMethods = new Dictionary> (StringComparer.Ordinal); - assemblies = new HashSet (); - typesWithDynamicallyRegisteredMethods = new HashSet (); - } - - public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute) - { - if (registeredMethod == null) { - throw new ArgumentNullException (nameof (registeredMethod)); - } - - if (implementedMethod == null) { - throw new ArgumentNullException (nameof (registeredMethod)); - } - - if (registerAttribute == null) { - throw new ArgumentNullException (nameof (registerAttribute)); - } - - if (!IsDynamicallyRegistered (topType, registeredMethod, implementedMethod, registerAttribute)) { - return false; - } - - typesWithDynamicallyRegisteredMethods.Add (topType); - return true; } - public bool FoundDynamicallyRegisteredMethods (TypeDefinition type) + public MethodEntry ClassifyMethod (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute) { - return typesWithDynamicallyRegisteredMethods.Contains (type); - } - - void AddTypeManagerSpecialCaseMethods () - { - const string FullTypeName = "Java.Interop.TypeManager+JavaTypeManager, Mono.Android"; - - AssemblyDefinition monoAndroid = resolver.Resolve ("Mono.Android"); - TypeDefinition? typeManager = monoAndroid?.MainModule.FindType ("Java.Interop.TypeManager"); - TypeDefinition? javaTypeManager = typeManager?.GetNestedType ("JavaTypeManager"); - - if (javaTypeManager == null) { - throw new InvalidOperationException ($"Internal error: unable to find the {FullTypeName} type in the Mono.Android assembly"); - } - - MethodDefinition? nActivate_mm = null; - MethodDefinition? nActivate = null; - - foreach (MethodDefinition method in javaTypeManager.Methods) { - if (nActivate_mm == null && IsMatchingMethod (method, "n_Activate_mm")) { - if (method.GetCustomAttributes ("System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute").Any (cattr => cattr != null)) { - nActivate_mm = method; - } else { - log.LogWarning ($"Method '{method.FullName}' isn't decorated with the UnmanagedCallersOnly attribute"); - continue; - } - } - - if (nActivate == null && IsMatchingMethod (method, "n_Activate")) { - nActivate = method; - } - - if (nActivate_mm != null && nActivate != null) { - break; - } - } - - if (nActivate_mm == null) { - ThrowMissingMethod ("nActivate_mm"); - } - - if (nActivate == null) { - ThrowMissingMethod ("nActivate"); - } - - string? jniTypeName = null; - foreach (CustomAttribute cattr in javaTypeManager.GetCustomAttributes ("Android.Runtime.RegisterAttribute")) { - if (cattr.ConstructorArguments.Count != 1) { - log.LogDebugMessage ($"[Register] attribute on type '{FullTypeName}' is expected to have 1 constructor argument, found {cattr.ConstructorArguments.Count}"); - continue; - } - - jniTypeName = (string)cattr.ConstructorArguments[0].Value; - if (!String.IsNullOrEmpty (jniTypeName)) { - break; - } - } - - string? jniMethodName = null; - string? jniSignature = null; - foreach (CustomAttribute cattr in nActivate.GetCustomAttributes ("Android.Runtime.RegisterAttribute")) { - if (cattr.ConstructorArguments.Count != 3) { - log.LogDebugMessage ($"[Register] attribute on method '{nActivate.FullName}' is expected to have 3 constructor arguments, found {cattr.ConstructorArguments.Count}"); - continue; - } - - jniMethodName = (string)cattr.ConstructorArguments[0].Value; - jniSignature = (string)cattr.ConstructorArguments[1].Value; - - if (!String.IsNullOrEmpty (jniMethodName) && !String.IsNullOrEmpty (jniSignature)) { - break; - } - } - - bool missingInfo = false; - if (String.IsNullOrEmpty (jniTypeName)) { - missingInfo = true; - log.LogDebugMessage ($"Failed to obtain Java type name from the [Register] attribute on type '{FullTypeName}'"); - } - - if (String.IsNullOrEmpty (jniMethodName)) { - missingInfo = true; - log.LogDebugMessage ($"Failed to obtain Java method name from the [Register] attribute on method '{nActivate.FullName}'"); - } + topType = topType ?? throw new ArgumentNullException (nameof (topType)); + registeredMethod = registeredMethod ?? throw new ArgumentNullException (nameof (registeredMethod)); + implementedMethod = implementedMethod ?? throw new ArgumentNullException (nameof (implementedMethod)); + registerAttribute = registerAttribute ?? throw new ArgumentNullException (nameof (registerAttribute)); - if (String.IsNullOrEmpty (jniSignature)) { - missingInfo = true; - log.LogDebugMessage ($"Failed to obtain Java method signature from the [Register] attribute on method '{nActivate.FullName}'"); + if (IsDynamicallyRegistered (topType, registeredMethod, implementedMethod, registerAttribute, out var marshalMethodEntry)) { + return new DynamicallyRegisteredMarshalMethodEntry (topType, implementedMethod, registeredMethod, registerAttribute); } - if (missingInfo) { - throw new InvalidOperationException ($"Missing information while constructing marshal method for the '{nActivate_mm.FullName}' method"); - } - - var entry = new MarshalMethodEntry (javaTypeManager, nActivate_mm, jniTypeName, jniMethodName, jniSignature); - marshalMethods.Add (".:!SpEcIaL:Java.Interop.TypeManager+JavaTypeManager::n_Activate_mm", new List { entry }); - - void ThrowMissingMethod (string name) - { - throw new InvalidOperationException ($"Internal error: unable to find the '{name}' method in the '{FullTypeName}' type"); - } - - bool IsMatchingMethod (MethodDefinition method, string name) - { - if (String.Compare (name, method.Name, StringComparison.Ordinal) != 0) { - return false; - } - - if (!method.IsStatic) { - log.LogWarning ($"Method '{method.FullName}' is not static"); - return false; - } - - if (!method.IsPrivate) { - log.LogWarning ($"Method '{method.FullName}' is not private"); - return false; - } - - return true; - } - } - - /// - /// Adds MarshalMethodEntry for each method that won't be returned by the JavaInterop type scanner, mostly - /// used for hand-written methods (e.g. Java.Interop.TypeManager+JavaTypeManager::n_Activate) - /// - public void AddSpecialCaseMethods () - { - AddTypeManagerSpecialCaseMethods (); + return marshalMethodEntry; } string GetAssemblyPathInfo (FieldDefinition? field) => GetAssemblyPathInfo (field?.DeclaringType); @@ -466,8 +348,9 @@ string GetAssemblyPathInfo (AssemblyDefinition? asmdef) return $"[Arch: {targetArch}; Assembly: {path}]"; } - bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute registerAttribute) + bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute registerAttribute, [NotNullWhen (false)] out MarshalMethodEntry? marshalMethodEntry) { + marshalMethodEntry = null; if (registerAttribute.ConstructorArguments.Count != 3) { log.LogWarning ($"Method '{registeredMethod.FullName}' will be registered dynamically, not enough arguments to the [Register] attribute to generate marshal method."); return true; @@ -475,17 +358,18 @@ bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registere var connector = new ConnectorInfo ((string)registerAttribute.ConstructorArguments[2].Value); - if (IsStandardHandler (topType, connector, registeredMethod, implementedMethod, jniName: (string)registerAttribute.ConstructorArguments[0].Value, jniSignature: (string)registerAttribute.ConstructorArguments[1].Value)) { + if (IsStandardHandler (topType, connector, registeredMethod, implementedMethod, jniName: (string)registerAttribute.ConstructorArguments[0].Value, jniSignature: (string)registerAttribute.ConstructorArguments[1].Value, out marshalMethodEntry)) { return false; } log.LogWarning ($"Method '{registeredMethod.FullName}' will be registered dynamically {GetAssemblyPathInfo (registeredMethod)}"); - rejectedMethodCount++; return true; } - bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, string jniName, string jniSignature) + bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, string jniName, string jniSignature, [NotNullWhen (true)] out MarshalMethodEntry? marshalMethodEntry) { + marshalMethodEntry = null; + const string HandlerNameStart = "Get"; const string HandlerNameEnd = "Handler"; @@ -502,29 +386,34 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD string callbackNameCore = connectorName.Substring (HandlerNameStart.Length, connectorName.Length - HandlerNameStart.Length - HandlerNameEnd.Length); string nativeCallbackName = $"n_{callbackNameCore}"; + string nativeConvertedCallbackName = $"n_{callbackNameCore}_mm_wrapper"; string delegateFieldName = $"cb_{Char.ToLowerInvariant (callbackNameCore[0])}{callbackNameCore.Substring (1)}"; TypeDefinition connectorDeclaringType = connector.AssemblyName == null ? registeredMethod.DeclaringType : FindType (resolver.Resolve (connector.AssemblyName), connector.TypeName); - MethodDefinition connectorMethod = FindMethod (connectorDeclaringType, connectorName); - if (connectorMethod == null) { - log.LogWarning ($"Connector method '{connectorName}' not found in type '{connectorDeclaringType.FullName}' {GetAssemblyPathInfo (connectorDeclaringType)}"); + var ncbs = new NativeCallbackSignature (registeredMethod, log, tdCache); + MethodDefinition nativeCallbackMethod = FindMethod (connectorDeclaringType, nativeCallbackName, ncbs); + if (nativeCallbackMethod == null) { + log.LogWarning ($"Unable to find native callback method '{nativeCallbackName}' in type '{connectorDeclaringType.FullName}', matching the '{registeredMethod.FullName}' signature (jniName: '{jniName}') {GetAssemblyPathInfo (connectorDeclaringType)}"); return false; } - if (String.Compare ("System.Delegate", connectorMethod.ReturnType.FullName, StringComparison.Ordinal) != 0) { - log.LogWarning ($"Connector '{connectorName}' in type '{connectorDeclaringType.FullName}' has invalid return type, expected 'System.Delegate', found '{connectorMethod.ReturnType.FullName}' {GetAssemblyPathInfo (connectorDeclaringType)}"); + if (!EnsureIsValidUnmanagedCallersOnlyTarget (nativeCallbackMethod, out bool needsBlittableWorkaround)) { return false; } - var ncbs = new NativeCallbackSignature (registeredMethod, log, tdCache); - MethodDefinition nativeCallbackMethod = FindMethod (connectorDeclaringType, nativeCallbackName, ncbs); - if (nativeCallbackMethod == null) { - log.LogWarning ($"Unable to find native callback method '{nativeCallbackName}' in type '{connectorDeclaringType.FullName}', matching the '{registeredMethod.FullName}' signature (jniName: '{jniName}') {GetAssemblyPathInfo (connectorDeclaringType)}"); + MethodDefinition? nativeConvertedCallbackMethod = FindMethod (connectorDeclaringType, nativeConvertedCallbackName, ncbs); + + MethodDefinition connectorMethod = FindMethod (connectorDeclaringType, connectorName); + + // If the marshal method has already been converted, the connector method will have been removed + if (connectorMethod == null && nativeConvertedCallbackMethod == null) { + log.LogWarning ($"Connector method '{connectorName}' not found in type '{connectorDeclaringType.FullName}' {GetAssemblyPathInfo (connectorDeclaringType)}"); return false; } - if (!EnsureIsValidUnmanagedCallersOnlyTarget (nativeCallbackMethod, out bool needsBlittableWorkaround)) { + if (connectorMethod != null && String.Compare ("System.Delegate", connectorMethod.ReturnType.FullName, StringComparison.Ordinal) != 0) { + log.LogWarning ($"Connector '{connectorName}' in type '{connectorDeclaringType.FullName}' has invalid return type, expected 'System.Delegate', found '{connectorMethod.ReturnType.FullName}' {GetAssemblyPathInfo (connectorDeclaringType)}"); return false; } @@ -568,25 +457,35 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD // method.CallbackField?.DeclaringType == 'null' // method.CallbackField?.DeclaringType.Fields == 'null' - StoreMethod ( - new MarshalMethodEntry ( - topType, - nativeCallbackMethod, - connectorMethod, - registeredMethod, - implementedMethod, - delegateField, - JavaNativeTypeManager.ToJniName (topType, tdCache), - jniName, - jniSignature, - needsBlittableWorkaround - ) - ); - - StoreAssembly (connectorMethod.Module.Assembly); - StoreAssembly (nativeCallbackMethod.Module.Assembly); - if (delegateField != null) { - StoreAssembly (delegateField.Module.Assembly); + if (nativeConvertedCallbackMethod is null) { + marshalMethodEntry = + new MarshalMethodEntry ( + topType, + nativeCallbackMethod, + connectorMethod, + registeredMethod, + implementedMethod, + delegateField, + JavaNativeTypeManager.ToJniName (topType, tdCache), + jniName, + jniSignature, + needsBlittableWorkaround + ); + } else { + marshalMethodEntry = + new ConvertedMarshalMethodEntry ( + topType, + nativeCallbackMethod, + connectorMethod, + registeredMethod, + implementedMethod, + delegateField, + JavaNativeTypeManager.ToJniName (topType, tdCache), + jniName, + jniSignature, + needsBlittableWorkaround, + nativeConvertedCallbackMethod + ); } return true; @@ -606,13 +505,11 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool } TypeReference type; - bool needsWrapper = false; if (String.Compare ("System.Void", method.ReturnType.FullName, StringComparison.Ordinal) != 0) { type = GetRealType (method.ReturnType); if (!IsAcceptable (type)) { needsBlittableWorkaround = true; WarnWhy ($"has a non-blittable return type '{type.FullName}'"); - needsWrapper = true; } } @@ -621,7 +518,7 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool } if (!method.HasParameters) { - return UpdateWrappedCountAndReturn (true); + return true; } foreach (ParameterDefinition pdef in method.Parameters) { @@ -630,20 +527,10 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool if (!IsAcceptable (type)) { needsBlittableWorkaround = true; WarnWhy ($"has a parameter ({pdef.Name}) of non-blittable type '{type.FullName}'"); - needsWrapper = true; } } - return UpdateWrappedCountAndReturn (true); - - bool UpdateWrappedCountAndReturn (bool retval) - { - if (needsWrapper) { - wrappedMethodCount++; - } - - return retval; - } + return true; bool IsAcceptable (TypeReference type) { @@ -751,30 +638,5 @@ FieldDefinition FindField (TypeDefinition type, string fieldName, bool lookForIn return FindField (tdCache.Resolve (type.BaseType), fieldName, lookForInherited); } - - void StoreMethod (MarshalMethodEntry entry) - { - string key = entry.GetStoreMethodKey (tdCache); - - // Several classes can override the same method, we need to generate the marshal method only once, at the same time - // keeping track of overloads - if (!marshalMethods.TryGetValue (key, out IList list) || list == null) { - list = new List (); - marshalMethods.Add (key, list); - } - - string registeredName = $"{entry.DeclaringType.FullName}::{entry.ImplementedMethod.Name}"; - if (list.Count == 0 || !list.Any (me => String.Compare (registeredName, me.ImplementedMethod.FullName, StringComparison.Ordinal) == 0)) { - list.Add (entry); - } - } - - void StoreAssembly (AssemblyDefinition asm) - { - if (assemblies.Contains (asm)) { - return; - } - assemblies.Add (asm); - } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsCollection.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsCollection.cs new file mode 100644 index 00000000000..44e21810574 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsCollection.cs @@ -0,0 +1,303 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Android.Runtime; +using Java.Interop.Tools.Cecil; +using Java.Interop.Tools.Diagnostics; +using Java.Interop.Tools.JavaCallableWrappers; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Mono.Cecil; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks; + +class MarshalMethodsCollection : JavaCallableMethodClassifier +{ + /// + /// Assemblies that contain convertible marshal methods. These assemblies need to + /// have the proper assembly references added to them. + /// + public HashSet AssembliesWithMarshalMethods { get; } = []; + + /// + /// Marshal methods that have already been rewritten as LLVM marshal methods. + /// + public IDictionary> ConvertedMarshalMethods { get; } = new Dictionary> (StringComparer.Ordinal); + + /// + /// Marshal methods that cannot be rewritten and must be registered dynamically. + /// + public List DynamicallyRegisteredMarshalMethods { get; } = []; + + /// + /// Marshal methods that can be rewritten as LLVM marshal methods. + /// + public IDictionary> MarshalMethods { get; } = new Dictionary> (StringComparer.Ordinal); + + readonly MarshalMethodsClassifier classifier; + readonly TaskLoggingHelper log; + readonly IAssemblyResolver resolver; + readonly HashSet typesWithDynamicallyRegisteredMarshalMethods = []; + + public MarshalMethodsCollection (MarshalMethodsClassifier classifier) + { + this.classifier = classifier; + log = classifier.Log; + resolver = classifier.Resolver; + } + + /// + /// Adds MarshalMethodEntry for each method that won't be returned by the JavaInterop type scanner, mostly + /// used for hand-written methods (e.g. Java.Interop.TypeManager+JavaTypeManager::n_Activate) + /// + public void AddSpecialCaseMethods () + { + AddTypeManagerSpecialCaseMethods (); + } + + static void CheckMethod (MarshalMethodsCollection collection, TypeDefinition type, MethodDefinition registeredMethod, MethodDefinition implementedMethod, MarshalMethodsClassifier methodClassifier, TypeDefinitionCache cache) + { + foreach (RegisterAttribute attr in CecilExtensions.GetMethodRegistrationAttributes (registeredMethod)) { + // Check for Kotlin-mangled methods that cannot be overridden + if (attr.Name.Contains ("-impl") || (attr.Name.Length > 7 && attr.Name [attr.Name.Length - 8] == '-')) + continue; + + collection.AddMethod (type, registeredMethod, implementedMethod, attr.OriginAttribute); + } + } + + public static MarshalMethodsCollection FromAssemblies (AndroidTargetArch arch, List assemblies, XAAssemblyResolver resolver, TaskLoggingHelper log) + { + var cache = new TypeDefinitionCache (); + var classifier = new MarshalMethodsClassifier (cache, resolver, log); + var collection = new MarshalMethodsCollection (classifier); + var scanner = new XAJavaTypeScanner (arch, log, cache); + + var javaTypes = scanner.GetJavaTypes (assemblies, resolver, []); + + foreach (var type in javaTypes) { + if (type.IsInterface || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (type, cache)) + continue; + + ScanTypeForMarshalMethods (type, collection, resolver, cache, log, classifier); + } + + return collection; + } + + static void ScanTypeForMarshalMethods (TypeDefinition type, MarshalMethodsCollection collection, XAAssemblyResolver resolver, TypeDefinitionCache cache, TaskLoggingHelper log, MarshalMethodsClassifier classifier) + { + // Methods + foreach (var minfo in type.Methods.Where (m => !m.IsConstructor)) { + var baseRegisteredMethod = CecilExtensions.GetBaseRegisteredMethod (minfo, cache); + + if (baseRegisteredMethod is not null) + CheckMethod (collection, type, baseRegisteredMethod, minfo, classifier, cache); + } + + // Methods from interfaces + foreach (InterfaceImplementation ifaceInfo in type.Interfaces) { + var typeReference = ifaceInfo.InterfaceType; + var typeDefinition = cache.Resolve (typeReference); + + if (typeDefinition is null) { + Diagnostic.Error (4204, + CecilExtensions.LookupSource (type), + Java.Interop.Localization.Resources.JavaCallableWrappers_XA4204, + typeReference.FullName); + } + + if (!CecilExtensions.GetTypeRegistrationAttributes (typeDefinition).Any ()) + continue; + + foreach (MethodDefinition imethod in typeDefinition.Methods) { + if (imethod.IsStatic) + continue; + + CheckMethod (collection, type, imethod, imethod, classifier, cache); + } + } + } + + public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute) + { + var method = AddMethod (topType, registeredMethod, implementedMethod, registerAttribute); + + return method is DynamicallyRegisteredMarshalMethodEntry; + } + + public bool TypeHasDynamicallyRegisteredMethods (TypeDefinition type) + { + return typesWithDynamicallyRegisteredMarshalMethods.Contains (type); + } + + MethodEntry AddMethod (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute) + { + var marshalMethod = classifier.ClassifyMethod (topType, registeredMethod, implementedMethod, registerAttribute); + + if (marshalMethod is DynamicallyRegisteredMarshalMethodEntry dynamicMethod) { + DynamicallyRegisteredMarshalMethods.Add (dynamicMethod); + typesWithDynamicallyRegisteredMarshalMethods.Add (topType); + + return dynamicMethod; + } + + if (marshalMethod is ConvertedMarshalMethodEntry convertedMethod) { + var key = convertedMethod.GetStoreMethodKey (classifier.TypeDefinitionCache); + + if (!ConvertedMarshalMethods.TryGetValue (key, out var list)) { + list = new List (); + ConvertedMarshalMethods.Add (key, list); + } + + list.Add (convertedMethod); + + return convertedMethod; + } + + if (marshalMethod is MarshalMethodEntry marshalMethodEntry) { + var key = marshalMethodEntry.GetStoreMethodKey (classifier.TypeDefinitionCache); + + if (!MarshalMethods.TryGetValue (key, out var list)) { + list = new List (); + MarshalMethods.Add (key, list); + } + + list.Add (marshalMethodEntry); + + AssembliesWithMarshalMethods.Add (marshalMethodEntry.NativeCallback.Module.Assembly); + + if (marshalMethodEntry.Connector is not null) + AssembliesWithMarshalMethods.Add (marshalMethodEntry.Connector.Module.Assembly); + + if (marshalMethodEntry.CallbackField is not null) + AssembliesWithMarshalMethods.Add (marshalMethodEntry.CallbackField.Module.Assembly); + } + + return marshalMethod; + } + + void AddTypeManagerSpecialCaseMethods () + { + const string FullTypeName = "Java.Interop.TypeManager+JavaTypeManager, Mono.Android"; + + AssemblyDefinition? monoAndroid = resolver.Resolve ("Mono.Android"); + TypeDefinition? typeManager = monoAndroid?.MainModule.FindType ("Java.Interop.TypeManager"); + TypeDefinition? javaTypeManager = typeManager?.GetNestedType ("JavaTypeManager"); + + if (javaTypeManager == null) { + throw new InvalidOperationException ($"Internal error: unable to find the {FullTypeName} type in the Mono.Android assembly"); + } + + MethodDefinition? nActivate_mm = null; + MethodDefinition? nActivate = null; + + foreach (MethodDefinition method in javaTypeManager.Methods) { + if (nActivate_mm == null && IsMatchingMethod (method, "n_Activate_mm")) { + if (method.GetCustomAttributes ("System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute").Any (cattr => cattr != null)) { + nActivate_mm = method; + } else { + log.LogWarning ($"Method '{method.FullName}' isn't decorated with the UnmanagedCallersOnly attribute"); + continue; + } + } + + if (nActivate == null && IsMatchingMethod (method, "n_Activate")) { + nActivate = method; + } + + if (nActivate_mm != null && nActivate != null) { + break; + } + } + + if (nActivate_mm == null) { + ThrowMissingMethod ("nActivate_mm"); + } + + if (nActivate == null) { + ThrowMissingMethod ("nActivate"); + } + + string? jniTypeName = null; + foreach (CustomAttribute cattr in javaTypeManager.GetCustomAttributes ("Android.Runtime.RegisterAttribute")) { + if (cattr.ConstructorArguments.Count != 1) { + log.LogDebugMessage ($"[Register] attribute on type '{FullTypeName}' is expected to have 1 constructor argument, found {cattr.ConstructorArguments.Count}"); + continue; + } + + jniTypeName = (string) cattr.ConstructorArguments [0].Value; + if (!String.IsNullOrEmpty (jniTypeName)) { + break; + } + } + + string? jniMethodName = null; + string? jniSignature = null; + foreach (CustomAttribute cattr in nActivate.GetCustomAttributes ("Android.Runtime.RegisterAttribute")) { + if (cattr.ConstructorArguments.Count != 3) { + log.LogDebugMessage ($"[Register] attribute on method '{nActivate.FullName}' is expected to have 3 constructor arguments, found {cattr.ConstructorArguments.Count}"); + continue; + } + + jniMethodName = (string) cattr.ConstructorArguments [0].Value; + jniSignature = (string) cattr.ConstructorArguments [1].Value; + + if (!String.IsNullOrEmpty (jniMethodName) && !String.IsNullOrEmpty (jniSignature)) { + break; + } + } + + bool missingInfo = false; + if (String.IsNullOrEmpty (jniTypeName)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java type name from the [Register] attribute on type '{FullTypeName}'"); + } + + if (String.IsNullOrEmpty (jniMethodName)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java method name from the [Register] attribute on method '{nActivate.FullName}'"); + } + + if (String.IsNullOrEmpty (jniSignature)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java method signature from the [Register] attribute on method '{nActivate.FullName}'"); + } + + if (missingInfo) { + throw new InvalidOperationException ($"Missing information while constructing marshal method for the '{nActivate_mm.FullName}' method"); + } + + var entry = new MarshalMethodEntry (javaTypeManager, nActivate_mm, jniTypeName!, jniMethodName!, jniSignature!); // NRT- Guarded above + MarshalMethods.Add (".:!SpEcIaL:Java.Interop.TypeManager+JavaTypeManager::n_Activate_mm", new List { entry }); + + [DoesNotReturn] + void ThrowMissingMethod (string name) + { + throw new InvalidOperationException ($"Internal error: unable to find the '{name}' method in the '{FullTypeName}' type"); + } + + bool IsMatchingMethod (MethodDefinition method, string name) + { + if (String.Compare (name, method.Name, StringComparison.Ordinal) != 0) { + return false; + } + + if (!method.IsStatic) { + log.LogWarning ($"Method '{method.FullName}' is not static"); + return false; + } + + if (!method.IsPrivate) { + log.LogWarning ($"Method '{method.FullName}' is not private"); + return false; + } + + return true; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs index f89c8a501a1..6a0785dfd94 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs @@ -23,7 +23,7 @@ class NativeCodeGenState /// Classifier used when scanning for Java types in the target architecture's /// assemblies. Will be **null** if marshal methods are disabled. /// - public MarshalMethodsClassifier? Classifier { get; } + public MarshalMethodsCollection? Classifier { get; } /// /// All the Java types discovered in the target architecture's assemblies. @@ -37,7 +37,7 @@ class NativeCodeGenState public ManagedMarshalMethodsLookupInfo? ManagedMarshalMethodsLookupInfo { get; set; } - public NativeCodeGenState (AndroidTargetArch arch, TypeDefinitionCache tdCache, XAAssemblyResolver resolver, List allJavaTypes, List javaTypesForJCW, MarshalMethodsClassifier? classifier) + public NativeCodeGenState (AndroidTargetArch arch, TypeDefinitionCache tdCache, XAAssemblyResolver resolver, List allJavaTypes, List javaTypesForJCW, MarshalMethodsCollection? classifier) { TargetArch = arch; TypeCache = tdCache; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/XAJavaTypeScanner.cs b/src/Xamarin.Android.Build.Tasks/Utilities/XAJavaTypeScanner.cs index 17ce90248aa..9d9380c5aff 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/XAJavaTypeScanner.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/XAJavaTypeScanner.cs @@ -73,6 +73,19 @@ public List GetJavaTypes (ICollection inputAssemblies return types; } + public List GetJavaTypes (AssemblyDefinition assembly) + { + var types = new List (); + + foreach (ModuleDefinition md in assembly.Modules) { + foreach (TypeDefinition td in md.Types) { + AddJavaType (td, types); + } + } + + return types; + } + bool ShouldScan (ITaskItem assembly) { string name = Path.GetFileName (assembly.ItemSpec); diff --git a/src/Xamarin.Android.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs b/src/Xamarin.Android.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs index 819fb1c4a0e..058bb0d85c7 100644 --- a/src/Xamarin.Android.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs +++ b/src/Xamarin.Android.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs @@ -1,5 +1,7 @@ using System; - +#if HAVE_CECIL +using Mono.Cecil; +#endif namespace Android.Runtime { [AttributeUsage (AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Property)] @@ -24,6 +26,22 @@ public RegisterAttribute (string name, string signature, string connector) this.signature = signature; } +#if HAVE_CECIL + public RegisterAttribute (string name, CustomAttribute? originAttribute) + : this (name) + { + OriginAttribute = originAttribute; + } + + public RegisterAttribute (string name, string signature, string connector, CustomAttribute? originAttribute) + : this (name, signature, connector) + { + OriginAttribute = originAttribute; + } + + public CustomAttribute? OriginAttribute { get; } +#endif + public string? Connector { get { return connector; } set { connector = value; }