From c3eb03128f01aab1e655ff6d46b4cc2516fe4984 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 22:32:12 +0200 Subject: [PATCH 1/9] [TrimmableTypeMap] Remove generated array proxy codegen and JavaPeerContainerFactory PR #12030 added runtime-only AOT-safe factories (SafeArrayFactory, SafeJavaCollectionFactory, ValueTypeFactory) that fully replace the old generated array-proxy code path and JavaPeerContainerFactory. That PR explicitly deferred removing the now-dead generator/runtime machinery to a follow-up; this is that follow-up. Runtime (src/Mono.Android): - Remove the JavaArrayProxy attribute base class and JavaPeerProxy.GetContainerFactory(). - Delete JavaPeerContainerFactory.cs (and its Mono.Android.csproj entry). - Remove TrimmableTypeMap.TryGetArrayProxy, the array-proxy cache/sentinel, MissingJavaArrayProxy, dead managed-type-key helpers, and the per-rank array-map Initialize overloads (keep the 2-arg single and []-aggregate ones). - Drop ITypeMap.TryGetArrayProxyType and the per-rank array-map constructors/field from AggregateTypeMap and SingleUniverseTypeMap. - Update PublicAPI.Unshipped.txt for all API levels. Generator (Microsoft.Android.Sdk.TrimmableTypeMap) + MSBuild: - Remove ArrayProxyData/PrimitiveArrayProxyData, MaxArrayRank, AnchorRank, EmitArrayProxyType, __ArrayMapRank emission, and all maxArrayRank plumbing through the generator, the GenerateTrimmableTypeMap task, and the .targets. - Remove now-dead scanner array-entry infrastructure (GenerateArrayEntries, ReferencedTypeNamesByAssembly). Tests: - Remove the legacy TryGetArrayProxy_* device tests and the array-proxy generator/model/scanner unit tests and build-test expectations. Validated: Microsoft.Android.Sdk.TrimmableTypeMap builds and its 581 unit tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e --- .../Generator/Model/TypeMapAssemblyData.cs | 44 -- .../Generator/ModelBuilder.cs | 223 +-------- .../Generator/RootTypeMapAssemblyGenerator.cs | 262 +---------- .../Generator/TypeMapAssemblyEmitter.cs | 195 +------- .../Generator/TypeMapAssemblyGenerator.cs | 5 +- .../Scanner/AssemblyIndex.cs | 29 -- .../Scanner/JavaPeerInfo.cs | 5 - .../Scanner/JavaPeerScanner.cs | 25 - .../TrimmableTypeMapGenerator.cs | 14 +- .../Java.Interop/JavaPeerContainerFactory.cs | 77 --- .../Java.Interop/JavaPeerProxy.cs | 46 +- .../AggregateTypeMap.cs | 12 - .../Microsoft.Android.Runtime/ITypeMap.cs | 7 +- .../SingleUniverseTypeMap.cs | 32 -- .../TrimmableTypeMap.cs | 126 +---- src/Mono.Android/Mono.Android.csproj | 1 - .../PublicAPI/API-35/PublicAPI.Unshipped.txt | 3 - .../API-36.1/PublicAPI.Unshipped.txt | 3 - .../PublicAPI/API-36/PublicAPI.Unshipped.txt | 3 - .../PublicAPI/API-37/PublicAPI.Unshipped.txt | 13 - ...roid.Sdk.TypeMap.Trimmable.CoreCLR.targets | 1 - ...soft.Android.Sdk.TypeMap.Trimmable.targets | 6 - .../Tasks/GenerateTrimmableTypeMap.cs | 8 - .../TrimmableTypeMapBuildTests.cs | 54 +-- .../Xamarin.Android.Common.targets | 1 - .../RootTypeMapAssemblyGeneratorTests.cs | 97 +--- .../Generator/TypeMapModelBuilderTests.cs | 441 ------------------ .../Scanner/JavaPeerScannerTests.cs | 1 - .../TrimmableTypeMapTypeManagerTests.cs | 45 +- 29 files changed, 30 insertions(+), 1749 deletions(-) delete mode 100644 src/Mono.Android/Java.Interop/JavaPeerContainerFactory.cs diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index b6cd7a89f39..bd659cb994b 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -41,16 +41,6 @@ sealed class TypeMapAssemblyData /// public List AliasHolders { get; } = new (); - /// - /// Array proxy types to emit — one per JNI element name and rank. - /// - public List ArrayProxyTypes { get; } = new (); - - /// - /// Maximum array rank for which the generator emits per-rank __ArrayMapRank{N} - /// sentinel TypeDefs and TypeMap entries. 0 disables. - /// - public int MaxArrayRank { get; set; } /// /// Assembly names that need [IgnoresAccessChecksTo] for cross-assembly n_* calls. @@ -91,35 +81,6 @@ sealed record TypeMapAttributeData /// public bool IsUnconditional => TargetTypeReference == null; - /// - /// 1-based array rank when this entry should use a __ArrayMapRank{value} - /// sentinel as its TGroup instead of the default model anchor. - /// - public int? AnchorRank { get; init; } -} - -/// -/// A generated array proxy type used by per-rank array TypeMap entries. -/// -sealed record ArrayProxyData -{ - public required string TypeName { get; init; } - - public string Namespace { get; init; } = "_TypeMap.ArrayProxies"; - - public required TypeRefData ElementType { get; init; } - - public required int Rank { get; init; } - - public PrimitiveArrayProxyData? Primitive { get; init; } -} - -/// -/// Additional primitive array metadata for . -/// -sealed record PrimitiveArrayProxyData -{ - public IReadOnlyList ConcreteArrayTypes { get; init; } = []; } /// @@ -523,11 +484,6 @@ sealed record TypeMapAssociationData /// public required string AliasProxyTypeReference { get; init; } - /// - /// 1-based array rank when this association should use a __ArrayMapRank{value} - /// sentinel as its TGroup instead of the default model anchor. - /// - public int? AnchorRank { get; init; } } /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index b8c8d767bcd..ee55df411e0 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -16,21 +16,6 @@ static class ModelBuilder { const string ProxyTypeSuffix = "_Proxy"; - static readonly PrimitiveArrayProxyInfo [] PrimitiveArrayProxies = [ - new ("Z", "Boolean", "System.Boolean", ["Java.Interop.JavaBooleanArray"]), - new ("B", "SByte", "System.SByte", ["Java.Interop.JavaSByteArray"]), - new ("B", "Byte", "System.Byte", []), - new ("C", "Char", "System.Char", ["Java.Interop.JavaCharArray"]), - new ("S", "Int16", "System.Int16", ["Java.Interop.JavaInt16Array"]), - new ("S", "UInt16", "System.UInt16", []), - new ("I", "Int32", "System.Int32", ["Java.Interop.JavaInt32Array"]), - new ("I", "UInt32", "System.UInt32", []), - new ("J", "Int64", "System.Int64", ["Java.Interop.JavaInt64Array"]), - new ("J", "UInt64", "System.UInt64", []), - new ("F", "Single", "System.Single", ["Java.Interop.JavaSingleArray"]), - new ("D", "Double", "System.Double", ["Java.Interop.JavaDoubleArray"]), - ]; - static readonly HashSet EssentialRuntimeTypes = new (StringComparer.Ordinal) { "java/lang/Object", "java/lang/Class", @@ -51,11 +36,7 @@ static class ModelBuilder /// Scanned Java peer types (typically from a single input assembly). /// Output .dll path — used to derive assembly/module names if not specified. /// Explicit assembly name. If null, derived from . - /// - /// Emit per-rank array TypeMap entries + __ArrayMapRank{N} sentinels - /// for ranks 1... 0 disables array entry emission. - /// - public static TypeMapAssemblyData Build (IReadOnlyList peers, string outputPath, string? assemblyName = null, int maxArrayRank = 0) + public static TypeMapAssemblyData Build (IReadOnlyList peers, string outputPath, string? assemblyName = null) { if (peers is null) { throw new ArgumentNullException (nameof (peers)); @@ -63,16 +44,11 @@ public static TypeMapAssemblyData Build (IReadOnlyList peers, stri if (outputPath is null) { throw new ArgumentNullException (nameof (outputPath)); } - if (maxArrayRank < 0) { - throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank, "Must be >= 0."); - } - assemblyName ??= Path.GetFileNameWithoutExtension (outputPath); var model = new TypeMapAssemblyData { AssemblyName = assemblyName, ModuleName = Path.GetFileName (outputPath), - MaxArrayRank = maxArrayRank, }; // Invoker types are NOT emitted as separate proxies or TypeMap entries. @@ -107,14 +83,6 @@ public static TypeMapAssemblyData Build (IReadOnlyList peers, stri } EmitPeers (model, jniName, peersForName, assemblyName, usedProxyNames); - - if (maxArrayRank > 0) { - EmitArrayEntries (model, jniName, peersForName, maxArrayRank); - } - } - - if (maxArrayRank > 0 && string.Equals (assemblyName, "_Java.Interop.TypeMap", StringComparison.Ordinal)) { - EmitPrimitiveArrayEntries (model, maxArrayRank); } BuildNativeRegistrations (model); @@ -296,15 +264,6 @@ static string ManagedTypeNameToProxyTypeName (string managedTypeName) return builder.ToString (); } - static string ManagedTypeNameToArrayProxyTypeName (string managedTypeName, int rank) - { - var builder = new StringBuilder (managedTypeName.Length + 20); - AppendSafeManagedTypeName (builder, managedTypeName); - builder.Append ("_ArrayProxy"); - builder.Append (rank); - return builder.ToString (); - } - static void AppendSafeManagedTypeName (StringBuilder builder, string managedTypeName) { for (int i = 0; i < managedTypeName.Length; i++) { @@ -553,184 +512,4 @@ static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? pr static string AssemblyQualify (string typeName, string assemblyName) => $"{typeName}, {assemblyName}"; - static string AddArrayRank (string typeReference, int rank) - { - if (rank == 0) { - return typeReference; - } - - int assemblySeparator = typeReference.LastIndexOf (", ", StringComparison.Ordinal); - if (assemblySeparator < 0) { - throw new InvalidOperationException ($"Assembly-qualified type reference '{typeReference}' does not contain an assembly name."); - } - - return typeReference.Substring (0, assemblySeparator) + Brackets (rank) + typeReference.Substring (assemblySeparator); - } - - static string MakeGenericTypeReference (string openTypeName, string openTypeAssembly, string argumentTypeReference) - => $"{openTypeName}[[{argumentTypeReference}]], {openTypeAssembly}"; - - static string MakeNestedJavaObjectArrayTypeReference (string elementTypeReference, int rank) - { - var result = elementTypeReference; - for (int i = 0; i < rank; i++) { - result = MakeGenericTypeReference ("Java.Interop.JavaObjectArray`1", "Java.Interop", result); - } - return result; - } - - static IReadOnlyList GetArrayTypeReferences (ArrayProxyData proxy) - { - var elementType = AssemblyQualify (proxy.ElementType.ManagedTypeName, proxy.ElementType.AssemblyName); - if (proxy.Primitive is null) { - var rankOneTypes = new [] { - MakeGenericTypeReference ("Java.Interop.JavaObjectArray`1", "Java.Interop", elementType), - MakeGenericTypeReference ("Java.Interop.JavaArray`1", "Java.Interop", elementType), - AddArrayRank (elementType, 1), - }; - return ExpandRankOneTypes (rankOneTypes, proxy.Rank); - } - - List rankOnePrimitiveTypes = [ - AddArrayRank (elementType, 1), - MakeGenericTypeReference ("Java.Interop.JavaArray`1", "Java.Interop", elementType), - MakeGenericTypeReference ("Java.Interop.JavaPrimitiveArray`1", "Java.Interop", elementType), - ]; - foreach (var concreteArrayType in proxy.Primitive.ConcreteArrayTypes) { - rankOnePrimitiveTypes.Add (AssemblyQualify (concreteArrayType.ManagedTypeName, concreteArrayType.AssemblyName)); - } - return ExpandRankOneTypes (rankOnePrimitiveTypes, proxy.Rank); - } - - static IReadOnlyList ExpandRankOneTypes (IReadOnlyList rankOneTypes, int rank) - { - if (rank == 1) { - return rankOneTypes; - } - - var result = new List (rankOneTypes.Count * 2); - foreach (var type in rankOneTypes) { - result.Add (MakeNestedJavaObjectArrayTypeReference (type, rank - 1)); - result.Add (AddArrayRank (type, rank - 1)); - } - return result; - } - - static void AddArrayProxyAssociations (TypeMapAssemblyData model, ArrayProxyData proxy, string proxyReference) - { - foreach (var typeReference in GetArrayTypeReferences (proxy)) { - model.Associations.Add (new TypeMapAssociationData { - SourceTypeReference = typeReference, - AliasProxyTypeReference = proxyReference, - AnchorRank = proxy.Rank, - }); - } - } - - static string GetArrayProxyMapKey (TypeRefData elementType) - => AssemblyQualify (elementType.ManagedTypeName, elementType.AssemblyName); - - /// - /// Emits per-rank array TypeMap entries for one peer, anchored to the per-assembly - /// __ArrayMapRank{N} sentinels. Keys are managed element type names (rank is encoded - /// by the sentinel anchor, not by JNI array prefixes). Skips open generics. - /// - static void EmitArrayEntries (TypeMapAssemblyData model, string jniName, List peersForName, int maxArrayRank) - { - if (jniName.Length == 1 && IsJniPrimitiveKeyword (jniName [0])) { - return; - } - - foreach (var peer in peersForName) { - EmitArrayEntriesForPeer (model, peer, maxArrayRank); - } - } - - static void EmitArrayEntriesForPeer (TypeMapAssemblyData model, JavaPeerInfo peer, int maxArrayRank) - { - if (!peer.GenerateArrayEntries) { - return; - } - if (peer.IsGenericDefinition) { - return; - } - - for (int rank = 1; rank <= maxArrayRank; rank++) { - var proxy = new ArrayProxyData { - TypeName = ManagedTypeNameToArrayProxyTypeName (peer.ManagedTypeName, rank), - ElementType = new TypeRefData { - ManagedTypeName = peer.ManagedTypeName, - AssemblyName = peer.AssemblyName, - }, - Rank = rank, - }; - model.ArrayProxyTypes.Add (proxy); - - var proxyReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", model.AssemblyName); - model.Entries.Add (new TypeMapAttributeData { - MapKey = GetArrayProxyMapKey (proxy.ElementType), - ProxyTypeReference = proxyReference, - TargetTypeReference = proxyReference, - AnchorRank = rank, - }); - AddArrayProxyAssociations (model, proxy, proxyReference); - } - } - - static void EmitPrimitiveArrayEntries (TypeMapAssemblyData model, int maxArrayRank) - { - foreach (var primitive in PrimitiveArrayProxies) { - for (int rank = 1; rank <= maxArrayRank; rank++) { - var proxy = new ArrayProxyData { - TypeName = $"Primitive_{primitive.Name}_ArrayProxy{rank}", - ElementType = new TypeRefData { - ManagedTypeName = primitive.ManagedTypeName, - AssemblyName = "System.Runtime", - }, - Rank = rank, - Primitive = new PrimitiveArrayProxyData { - ConcreteArrayTypes = primitive.ConcreteArrayTypeNames.Select (name => new TypeRefData { - ManagedTypeName = name, - AssemblyName = "Java.Interop", - }).ToList (), - }, - }; - model.ArrayProxyTypes.Add (proxy); - var proxyReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", model.AssemblyName); - model.Entries.Add (new TypeMapAttributeData { - MapKey = GetArrayProxyMapKey (proxy.ElementType), - ProxyTypeReference = proxyReference, - TargetTypeReference = proxyReference, - AnchorRank = rank, - }); - AddArrayProxyAssociations (model, proxy, proxyReference); - } - } - } - - static string Brackets (int rank) => rank switch { - 1 => "[]", - 2 => "[][]", - 3 => "[][][]", - _ => BuildBrackets (rank), - }; - - static string BuildBrackets (int rank) - { - var sb = new StringBuilder (rank * 2); - for (int i = 0; i < rank; i++) { - sb.Append ("[]"); - } - return sb.ToString (); - } - - static bool IsJniPrimitiveKeyword (char c) - => c == 'Z' || c == 'B' || c == 'C' || c == 'S' || c == 'I' - || c == 'J' || c == 'F' || c == 'D' || c == 'V'; - - readonly record struct PrimitiveArrayProxyInfo ( - string JniName, - string Name, - string ManagedTypeName, - IReadOnlyList ConcreteArrayTypeNames); } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs index de295c8d4a9..683464daa4c 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs @@ -37,8 +37,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// // Option A: Shared universe /// TrimmableTypeMap.Initialize( /// TypeMapping.GetOrCreateExternalTypeMapping<Java.Lang.Object>(), -/// TypeMapping.GetOrCreateProxyTypeMapping<Java.Lang.Object>(), -/// arrayMapsByAssemblyAndRank); +/// TypeMapping.GetOrCreateProxyTypeMapping<Java.Lang.Object>()); /// /// // Option B: Per-assembly universes (aggregated) /// var typeMaps = new IReadOnlyDictionary<string, Type>[] { @@ -49,7 +48,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// TypeMapping.GetOrCreateProxyTypeMapping<_Mono_Android_TypeMap.__TypeMapAnchor>(), /// TypeMapping.GetOrCreateProxyTypeMapping<_MyApp_TypeMap.__TypeMapAnchor>(), /// }; -/// TrimmableTypeMap.Initialize(typeMaps, proxyMaps, arrayMapsByAssemblyAndRank); +/// TrimmableTypeMap.Initialize(typeMaps, proxyMaps); /// } /// } /// } @@ -75,12 +74,7 @@ public RootTypeMapAssemblyGenerator (Version systemRuntimeVersion) /// Stream to write the output PE to. /// Optional assembly name (defaults to _Microsoft.Android.TypeMaps). /// Optional module name for the PE metadata. - /// - /// Maximum array rank for which per-assembly typemaps emitted __ArrayMapRank{N} - /// sentinels. Must match the value passed to the per-assembly generators. 0 means - /// no array sentinels were emitted; the loader passes null for array maps. - /// - public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, Stream stream, string? assemblyName = null, string? moduleName = null, int maxArrayRank = 0) + public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, Stream stream, string? assemblyName = null, string? moduleName = null) { if (perAssemblyTypeMapNames is null) { throw new ArgumentNullException (nameof (perAssemblyTypeMapNames)); @@ -123,19 +117,18 @@ public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSha } else { EmitPerAssemblyUniverseAssemblyTargetAttributes (pe, perAssemblyTypeMapNames); } - EmitArrayAssemblyTargetAttributes (pe, perAssemblyTypeMapNames, maxArrayRank); // Emit [assembly: IgnoresAccessChecksTo("...")] so TypeMapLoader.Initialize() can access // internal types (TrimmableTypeMap and friends in Mono.Android, and private anchors // in each per-assembly typemap DLL when aggregate universes or array maps are used). var accessTargets = new List { "Mono.Android" }; - if (!useSharedTypemapUniverse || maxArrayRank > 0) { + if (!useSharedTypemapUniverse) { accessTargets.AddRange (perAssemblyTypeMapNames); } pe.EmitIgnoresAccessChecksToAttribute (accessTargets); // Emit TypeMapLoader class with Initialize() method - EmitTypeMapLoader (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse, maxArrayRank, assemblyName); + EmitTypeMapLoader (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse, assemblyName); pe.WritePE (stream); } @@ -161,24 +154,6 @@ static void EmitPerAssemblyUniverseAssemblyTargetAttributes (PEAssemblyBuilder p } } - static void EmitArrayAssemblyTargetAttributes (PEAssemblyBuilder pe, IReadOnlyList perAssemblyTypeMapNames, int maxArrayRank) - { - if (maxArrayRank == 0) { - return; - } - - var openAttrRef = GetTypeMapAssemblyTargetAttributeRef (pe); - foreach (var name in perAssemblyTypeMapNames) { - var asmRef = pe.FindOrAddAssemblyRef (name); - for (int rank = 1; rank <= maxArrayRank; rank++) { - var rankAnchorRef = pe.Metadata.AddTypeReference (asmRef, - default, pe.Metadata.GetOrAddString ($"__ArrayMapRank{rank}")); - var ctorRef = GetTypeMapAssemblyTargetAttributeCtorRef (pe, openAttrRef, rankAnchorRef); - EmitAssemblyTargetAttribute (pe, ctorRef, name); - } - } - } - static TypeReferenceHandle GetTypeMapAssemblyTargetAttributeRef (PEAssemblyBuilder pe) { return pe.Metadata.AddTypeReference (pe.SystemRuntimeInteropServicesRef, @@ -201,7 +176,7 @@ static void EmitAssemblyTargetAttribute (PEAssemblyBuilder pe, MemberReferenceHa pe.Metadata.AddCustomAttribute (EntityHandle.AssemblyDefinition, ctorRef, blobHandle); } - static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, int maxArrayRank, string assemblyName) + static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, string assemblyName) { var metadata = pe.Metadata; @@ -237,90 +212,18 @@ static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHand MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1)); var externalDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); - var externalDictArrayTypeSpec = MakeIReadOnlyDictArrayTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); if (useSharedTypemapUniverse) { - if (maxArrayRank > 0) { - var initializeRef = AddInitializeSingleWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); - EmitInitializeWithSingleTypeMap (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, externalDictArrayTypeSpec, perAssemblyTypeMapNames, maxArrayRank, assemblyName); - } else { - var initializeRef = AddInitializeSingleNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); - EmitInitializeWithSingleTypeMapNoArrays (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, initializeRef, assemblyName); - } + var initializeRef = AddInitializeSingleNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); + EmitInitializeWithSingleTypeMapNoArrays (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, initializeRef, assemblyName); } else { var proxyDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); - if (maxArrayRank > 0) { - var initializeRef = AddInitializeAggregateWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); - EmitInitializeWithAggregateTypeMap (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, proxyDictTypeSpec, externalDictArrayTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, maxArrayRank, assemblyName); - } else { - var initializeRef = AddInitializeAggregateNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); - EmitInitializeWithAggregateTypeMapNoArrays (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, assemblyName); - } + var initializeRef = AddInitializeAggregateNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); + EmitInitializeWithAggregateTypeMapNoArrays (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, + initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, assemblyName); } } - /// - /// Aggregate IL emit. Builds typeMaps[N], proxyMaps[N], and either - /// arrayMapsByAssemblyAndRank[N][maxArrayRank] from per-assembly - /// __ArrayMapRank{N} anchors or null when is 0. - /// - static void EmitInitializeWithAggregateTypeMap (PEAssemblyBuilder pe, - IReadOnlyList perAssemblyTypeMapNames, - MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef, - MemberReferenceHandle initializeRef, - TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle proxyDictTypeSpec, - TypeSpecificationHandle externalDictArrayTypeSpec, - TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, - int maxArrayRank, - string assemblyName) - { - var count = perAssemblyTypeMapNames.Count; - - var getExternalSpecs = new EntityHandle [count]; - var getProxySpecs = new EntityHandle [count]; - for (int i = 0; i < count; i++) { - var asmRef = pe.FindOrAddAssemblyRef (perAssemblyTypeMapNames [i]); - var perAsmAnchorRef = pe.Metadata.AddTypeReference (asmRef, - default, pe.Metadata.GetOrAddString ("__TypeMapAnchor")); - getExternalSpecs [i] = MakeGenericMethodSpec (pe, getExternalMemberRef, perAsmAnchorRef); - getProxySpecs [i] = MakeGenericMethodSpec (pe, getProxyMemberRef, perAsmAnchorRef); - } - - pe.EmitBody ("Initialize", - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), - encoder => { - EmitSetTypeMappingEntryAssembly (pe, encoder, assemblyName); - // var typeMaps = new IReadOnlyDictionary[N]; (loc 0) - EmitNewArrayLocal (encoder, count, externalDictTypeSpec, slot: 0); - EmitFillArrayLocal (encoder, count, getExternalSpecs, slot: 0); - - // var proxyMaps = new IReadOnlyDictionary[N]; (loc 1) - EmitNewArrayLocal (encoder, count, proxyDictTypeSpec, slot: 1); - EmitFillArrayLocal (encoder, count, getProxySpecs, slot: 1); - - // TrimmableTypeMap.Initialize(typeMaps, proxyMaps, arrayMapsByAssemblyAndRank-or-null) - encoder.LoadLocal (0); - encoder.LoadLocal (1); - EmitArrayMapsByAssemblyAndRankOrNull (pe, encoder, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, externalDictTypeSpec, externalDictArrayTypeSpec, maxArrayRank); - encoder.Call (initializeRef, parameterCount: 3); - encoder.Return (); - }, - encodeLocals: localsSig => { - localsSig.WriteByte ((byte) SignatureKind.LocalVariables); - localsSig.WriteCompressedInteger (2); // count - // loc 0: IReadOnlyDictionary[] - localsSig.WriteByte ((byte) SignatureTypeCode.SZArray); - EncodeIReadOnlyDictType (localsSig, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); - // loc 1: IReadOnlyDictionary[] - localsSig.WriteByte ((byte) SignatureTypeCode.SZArray); - EncodeIReadOnlyDictType (localsSig, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); - }); - } - static void EmitNewArrayLocal (TrackedInstructionEncoder encoder, int count, TypeSpecificationHandle elemSpec, int slot) { encoder.LoadConstantI4 (count); @@ -404,58 +307,6 @@ static MemberReferenceHandle AddInitializeAggregateNoArraysRef (PEAssemblyBuilde pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob)); } - /// MemberRef for TrimmableTypeMap.Initialize(typeMaps[], proxyMaps[], arrayMapsByAssemblyAndRank[][]). - static MemberReferenceHandle AddInitializeAggregateWithArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef, - TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) - { - var blob = new BlobBuilder (96); - blob.WriteByte ((byte) SignatureCallingConvention.Default); - blob.WriteCompressedInteger (3); // parameter count - blob.WriteByte ((byte) SignatureTypeCode.Void); - // Param 1: IReadOnlyDictionary[] - blob.WriteByte ((byte) SignatureTypeCode.SZArray); - EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); - // Param 2: IReadOnlyDictionary[] - blob.WriteByte ((byte) SignatureTypeCode.SZArray); - EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); - // Param 3: IReadOnlyDictionary?[][] - blob.WriteByte ((byte) SignatureTypeCode.SZArray); - blob.WriteByte ((byte) SignatureTypeCode.SZArray); - EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); - return pe.Metadata.AddMemberReference (trimmableTypeMapRef, - pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob)); - } - - /// - /// Shared-universe IL emit. Single merged main map (anchored on Java.Lang.Object) - /// plus either arrayMapsByAssemblyAndRank[N][maxArrayRank] from per-assembly - /// __ArrayMapRank{N} anchors or null when is 0. - /// - static void EmitInitializeWithSingleTypeMap (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, - MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef, - MemberReferenceHandle initializeRef, - TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle externalDictArrayTypeSpec, - IReadOnlyList perAssemblyTypeMapNames, - int maxArrayRank, - string assemblyName) - { - var getExternalSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, anchorTypeHandle); - var getProxySpec = MakeGenericMethodSpec (pe, getProxyMemberRef, anchorTypeHandle); - - pe.EmitBody ("Initialize", - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), - encoder => { - EmitSetTypeMappingEntryAssembly (pe, encoder, assemblyName); - // TrimmableTypeMap.Initialize(GetExternal(), GetProxy(), arrayMapsByAssemblyAndRank-or-null) - encoder.Call (getExternalSpec, parameterCount: 0, returnsValue: true); - encoder.Call (getProxySpec, parameterCount: 0, returnsValue: true); - EmitArrayMapsByAssemblyAndRankOrNull (pe, encoder, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, externalDictTypeSpec, externalDictArrayTypeSpec, maxArrayRank); - encoder.Call (initializeRef, parameterCount: 3); - encoder.Return (); - }); - } - /// /// Shared-universe IL emit without array maps. Calls the simpler 2-arg overload: /// TrimmableTypeMap.Initialize(typeMap, proxyMap). @@ -510,85 +361,6 @@ static void EmitSetTypeMappingEntryAssembly (PEAssemblyBuilder pe, TrackedInstru encoder.Call (setDataRef, parameterCount: 2); } - /// MemberRef for TrimmableTypeMap.Initialize(typeMap, proxyMap, arrayMapsByAssemblyAndRank[][]). - static MemberReferenceHandle AddInitializeSingleWithArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef, - TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) - { - var blob = new BlobBuilder (96); - blob.WriteByte ((byte) SignatureCallingConvention.Default); - blob.WriteCompressedInteger (3); // parameter count - blob.WriteByte ((byte) SignatureTypeCode.Void); - EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); - EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); - blob.WriteByte ((byte) SignatureTypeCode.SZArray); - blob.WriteByte ((byte) SignatureTypeCode.SZArray); - EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); - return pe.Metadata.AddMemberReference (trimmableTypeMapRef, - pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob)); - } - - /// - /// Emits IL that pushes either a fresh - /// IReadOnlyDictionary<string, Type>?[assemblyCount][maxArrayRank] - /// (when > 0) or ldnull. - /// - static void EmitArrayMapsByAssemblyAndRankOrNull (PEAssemblyBuilder pe, TrackedInstructionEncoder encoder, - IReadOnlyList perAssemblyTypeMapNames, - MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef, - TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle externalDictArrayTypeSpec, - int maxArrayRank) - { - if (maxArrayRank == 0) { - encoder.OpCode (ILOpCode.Ldnull); - return; - } - - encoder.LoadConstantI4 (perAssemblyTypeMapNames.Count); - encoder.NewArray (externalDictArrayTypeSpec); - for (int i = 0; i < perAssemblyTypeMapNames.Count; i++) { - var asmRef = pe.FindOrAddAssemblyRef (perAssemblyTypeMapNames [i]); - encoder.OpCode (ILOpCode.Dup); - encoder.LoadConstantI4 (i); - EmitArrayMapsByRank (pe, encoder, asmRef, getExternalMemberRef, getProxyMemberRef, externalDictTypeSpec, maxArrayRank); - encoder.OpCode (ILOpCode.Stelem_ref); - } - } - - /// - /// Pushes a fresh IReadOnlyDictionary<string, Type>[maxArrayRank] for a single - /// assembly, where slot r holds the external (JNI-name -> proxy ) - /// array map for the __ArrayMapRank{r + 1} group anchor. At runtime only this external - /// map is consulted (see TrimmableTypeMap.TryGetArrayProxy), which resolves a proxy - /// by JNI name and then reads its array-proxy attribute; the proxy typemap - /// dictionary itself is never indexed. - /// - static void EmitArrayMapsByRank (PEAssemblyBuilder pe, TrackedInstructionEncoder encoder, - AssemblyReferenceHandle assemblyRef, - MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef, TypeSpecificationHandle externalDictTypeSpec, int maxArrayRank) - { - encoder.LoadConstantI4 (maxArrayRank); - encoder.NewArray (externalDictTypeSpec); - for (int r = 0; r < maxArrayRank; r++) { - var rankRef = pe.Metadata.AddTypeReference (assemblyRef, default, - pe.Metadata.GetOrAddString ($"__ArrayMapRank{r + 1}")); - var rankSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, rankRef); - var proxyRankSpec = MakeGenericMethodSpec (pe, getProxyMemberRef, rankRef); - // Store the external map for this rank into the array. - encoder.OpCode (ILOpCode.Dup); - encoder.LoadConstantI4 (r); - encoder.Call (rankSpec, parameterCount: 0, returnsValue: true); - encoder.OpCode (ILOpCode.Stelem_ref); - // Unlike the external map on the line above (stored via Stelem_ref), this - // GetOrCreateProxyTypeMapping<__ArrayMapRank{r + 1}> () call has its result - // immediately Popped: the proxy map for arrays is never stored or indexed at - // runtime. We still emit the call purely for its side effect — requesting the proxy - // typemap group roots that group's per-rank [TypeMapAssociation] entries, so the - // trimmer/ILC keeps the proxy types that the external map above references by JNI name. - encoder.Call (proxyRankSpec, parameterCount: 0, returnsValue: true); - encoder.OpCode (ILOpCode.Pop); - } - } - /// /// Creates a MethodSpec for a generic method instantiation with a specific type argument. /// @@ -631,18 +403,6 @@ static TypeSpecificationHandle MakeIReadOnlyDictTypeSpec (PEAssemblyBuilder pe, return pe.Metadata.AddTypeSpecification (pe.Metadata.GetOrAddBlob (blob)); } - /// - /// Creates a TypeSpec for IReadOnlyDictionary<K, V>[] (for jagged-array newarr). - /// - static TypeSpecificationHandle MakeIReadOnlyDictArrayTypeSpec (PEAssemblyBuilder pe, - TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, bool keyIsString) - { - var blob = new BlobBuilder (32); - blob.WriteByte ((byte) SignatureTypeCode.SZArray); - EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString); - return pe.Metadata.AddTypeSpecification (pe.Metadata.GetOrAddBlob (blob)); - } - static void EncodeIReadOnlyDictType (BlobBuilder blob, TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, bool keyIsString) { blob.WriteByte ((byte) SignatureTypeCode.GenericTypeInstance); diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 2a2c4d04be3..8aeb5c67b7d 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -80,7 +80,6 @@ sealed class TypeMapAssemblyEmitter TypeReferenceHandle _javaPeerProxyRef; TypeReferenceHandle _javaPeerProxyNonGenericRef; - TypeReferenceHandle _javaArrayProxyRef; TypeReferenceHandle _iJavaPeerableRef; TypeReferenceHandle _jniHandleOwnershipRef; TypeReferenceHandle _jniObjectReferenceRef; @@ -89,9 +88,6 @@ sealed class TypeMapAssemblyEmitter TypeReferenceHandle _iAndroidCallableWrapperRef; TypeReferenceHandle _jniEnvRef; TypeReferenceHandle _javaLangObjectRef; - TypeReferenceHandle _javaObjectArrayOpenRef; - TypeReferenceHandle _javaArrayOpenRef; - TypeReferenceHandle _javaPrimitiveArrayOpenRef; TypeReferenceHandle _systemTypeRef; TypeReferenceHandle _systemArrayRef; TypeReferenceHandle _runtimeTypeHandleRef; @@ -119,7 +115,6 @@ sealed class TypeMapAssemblyEmitter BlobHandle _ucoAttrBlobHandle; MemberReferenceHandle _typeMapAttrCtorRef2Arg; MemberReferenceHandle _typeMapAttrCtorRef3Arg; - MemberReferenceHandle _javaArrayProxyCtorRef; MemberReferenceHandle _typeMapAssociationAttrCtorRef; TypeReferenceHandle _typeMapAssociationAttrOpenRef; @@ -147,10 +142,6 @@ sealed class TypeMapAssemblyEmitter ExportMethodDispatchEmitter? _exportMethodDispatchEmitter; - // Per-rank array sentinel TypeDefs, 0-indexed by (rank - 1). Empty when array entries - // aren't emitted. - EntityHandle [] _rankAnchorHandles = []; - // Per-anchor TypeMap ctor refs, lazily built. readonly Dictionary _typeMapAttr2ArgCtorRefByAnchor = new (); readonly Dictionary _typeMapAttr3ArgCtorRefByAnchor = new (); @@ -208,7 +199,6 @@ void EmitCore (TypeMapAssemblyData model, bool useSharedTypemapUniverse) } else { EmitAnchorType (); } - EmitRankSentinels (model); EmitMemberReferences (); // Track wrapper targets → handles for RegisterNatives. @@ -222,10 +212,6 @@ void EmitCore (TypeMapAssemblyData model, bool useSharedTypemapUniverse) EmitAliasHolderType (holder); } - foreach (var arrayProxy in model.ArrayProxyTypes) { - EmitArrayProxyType (arrayProxy); - } - foreach (var entry in model.Entries) { EmitTypeMapAttribute (entry); } @@ -290,8 +276,6 @@ void EmitTypeReferences () metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy`1")); _javaPeerProxyNonGenericRef = metadata.AddTypeReference (_pe.MonoAndroidRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy")); - _javaArrayProxyRef = metadata.AddTypeReference (_pe.MonoAndroidRef, - metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaArrayProxy")); _iJavaPeerableRef = metadata.AddTypeReference (_javaInteropRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("IJavaPeerable")); _jniHandleOwnershipRef = metadata.AddTypeReference (_pe.MonoAndroidRef, @@ -300,12 +284,6 @@ void EmitTypeReferences () metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JNIEnv")); _javaLangObjectRef = metadata.AddTypeReference (_pe.MonoAndroidRef, metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object")); - _javaObjectArrayOpenRef = metadata.AddTypeReference (_javaInteropRef, - metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaObjectArray`1")); - _javaArrayOpenRef = metadata.AddTypeReference (_javaInteropRef, - metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaArray`1")); - _javaPrimitiveArrayOpenRef = metadata.AddTypeReference (_javaInteropRef, - metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPrimitiveArray`1")); _jniObjectReferenceRef = metadata.AddTypeReference (_javaInteropRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniObjectReference")); _jniObjectReferenceTypeRef = metadata.AddTypeReference (_javaInteropRef, @@ -373,31 +351,6 @@ void EmitAnchorType () MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1)); } - /// - /// Emits __ArrayMapRank{N} classes used as group type parameters - /// for array TypeMap<T> entries. Each per-assembly typemap owns its - /// rank anchors so array maps stay scoped to the generated assembly. - /// - void EmitRankSentinels (TypeMapAssemblyData model) - { - if (model.MaxArrayRank <= 0) { - return; - } - - _rankAnchorHandles = new EntityHandle [model.MaxArrayRank]; - var objectRef = _pe.Metadata.AddTypeReference (_pe.SystemRuntimeRef, - _pe.Metadata.GetOrAddString ("System"), _pe.Metadata.GetOrAddString ("Object")); - for (int i = 0; i < model.MaxArrayRank; i++) { - _rankAnchorHandles [i] = _pe.Metadata.AddTypeDefinition ( - TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class, - default, - _pe.Metadata.GetOrAddString ($"__ArrayMapRank{i + 1}"), - objectRef, - MetadataTokens.FieldDefinitionHandle (_pe.Metadata.GetRowCount (TableIndex.Field) + 1), - MetadataTokens.MethodDefinitionHandle (_pe.Metadata.GetRowCount (TableIndex.MethodDef) + 1)); - } - } - void EmitMemberReferences () { _getTypeFromHandleRef = _pe.AddMemberRef (_systemTypeRef, "GetTypeFromHandle", @@ -415,9 +368,6 @@ void EmitMemberReferences () rt => rt.Void (), p => p.AddParameter ().Type ().String ())); - _javaArrayProxyCtorRef = _pe.AddMemberRef (_javaArrayProxyRef, ".ctor", - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { })); - // JniObjectReference..ctor(IntPtr handle, JniObjectReferenceType type) // Note: The C# constructor has a default parameter (type = Invalid), but in IL there is only // the 2-parameter overload. We must emit both parameters explicitly. @@ -829,69 +779,6 @@ void EmitAliasHolderType (AliasHolderData holder) EmitJavaPeerAliasesAttribute (typeDefHandle, holder.AliasKeys); } - void EmitArrayProxyType (ArrayProxyData proxy) - { - var metadata = _pe.Metadata; - var typeDefHandle = metadata.AddTypeDefinition ( - TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, - metadata.GetOrAddString (proxy.Namespace), - metadata.GetOrAddString (proxy.TypeName), - _javaArrayProxyRef, - MetadataTokens.FieldDefinitionHandle (metadata.GetRowCount (TableIndex.Field) + 1), - MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1)); - - var selfAttrCtorDef = _pe.EmitBody (".ctor", - MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }), - encoder => { - encoder.OpCode (ILOpCode.Ldarg_0); - encoder.Call (_javaArrayProxyCtorRef, parameterCount: 0, isInstance: true); - encoder.Return (); - }); - - metadata.AddCustomAttribute (typeDefHandle, selfAttrCtorDef, _pe.BuildAttributeBlob (b => { })); - - EmitArrayProxyGetArrayTypes (proxy); - EmitArrayProxyCreateManagedArray (proxy); - } - - void EmitArrayProxyGetArrayTypes (ArrayProxyData proxy) - { - var arrayTypes = GetArrayProxyTypes (proxy); - _pe.EmitBody ("GetArrayTypes", - MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, - rt => rt.Type ().SZArray ().Type (_systemTypeRef, false), - p => { }), - encoder => { - encoder.LoadConstantI4 (arrayTypes.Count); - encoder.NewArray (_systemTypeRef); - for (int i = 0; i < arrayTypes.Count; i++) { - encoder.OpCode (ILOpCode.Dup); - encoder.LoadConstantI4 (i); - encoder.LoadToken (ResolveRuntimeTypeSpec (arrayTypes [i])); - encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); - encoder.OpCode (ILOpCode.Stelem_ref); - } - encoder.Return (returnsValue: true); - }); - } - - void EmitArrayProxyCreateManagedArray (ArrayProxyData proxy) - { - var elementType = AddSzArrayRank (new NamedRuntimeTypeSpec (proxy.ElementType), proxy.Rank - 1); - _pe.EmitBody ("CreateManagedArray", - MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1, - rt => rt.Type ().Type (_systemArrayRef, false), - p => p.AddParameter ().Type ().Int32 ()), - encoder => { - encoder.LoadArgument (1); - encoder.NewArray (ResolveRuntimeTypeSpec (elementType)); - encoder.Return (returnsValue: true); - }); - } - void EmitJavaPeerAliasesAttributeCtorRef () { // JavaPeerAliasesAttribute(params string[] aliases) — in Mono.Android, Java.Interop namespace @@ -1617,62 +1504,6 @@ void EmitManagedConstructorArgument (TrackedInstructionEncoder encoder, TypeRefD encoder.CastClass (managedTypeHandle); } - IReadOnlyList GetArrayProxyTypes (ArrayProxyData proxy) - { - var elementType = new NamedRuntimeTypeSpec (proxy.ElementType); - if (proxy.Primitive is null) { - var rankOneObjectTypes = new RuntimeTypeSpec [] { - new GenericRuntimeTypeSpec (_javaObjectArrayOpenRef, elementType), - new GenericRuntimeTypeSpec (_javaArrayOpenRef, elementType), - AddSzArrayRank (elementType, 1), - }; - return ExpandRankOneTypes (rankOneObjectTypes, proxy.Rank); - } - - List rankOneTypes = [ - AddSzArrayRank (elementType, 1), - new GenericRuntimeTypeSpec (_javaArrayOpenRef, elementType), - new GenericRuntimeTypeSpec (_javaPrimitiveArrayOpenRef, elementType), - ]; - foreach (var concreteArrayType in proxy.Primitive.ConcreteArrayTypes) { - rankOneTypes.Add (new NamedRuntimeTypeSpec (concreteArrayType)); - } - - return ExpandRankOneTypes (rankOneTypes, proxy.Rank); - } - - IReadOnlyList ExpandRankOneTypes (IReadOnlyList rankOneTypes, int rank) - { - if (rank == 1) { - return rankOneTypes; - } - - var result = new List (rankOneTypes.Count * 2); - foreach (var type in rankOneTypes) { - result.Add (MakeNestedJavaObjectArrayType (type, rank - 1)); - result.Add (AddSzArrayRank (type, rank - 1)); - } - return result; - } - - static RuntimeTypeSpec AddSzArrayRank (RuntimeTypeSpec elementType, int rank) - { - var result = elementType; - for (int i = 0; i < rank; i++) { - result = new SzArrayRuntimeTypeSpec (result); - } - return result; - } - - RuntimeTypeSpec MakeNestedJavaObjectArrayType (RuntimeTypeSpec elementType, int rank) - { - var result = elementType; - for (int i = 0; i < rank; i++) { - result = new GenericRuntimeTypeSpec (_javaObjectArrayOpenRef, result); - } - return result; - } - EntityHandle ResolveRuntimeTypeSpec (RuntimeTypeSpec type) { if (type is NamedRuntimeTypeSpec namedType) { @@ -1965,22 +1796,7 @@ void AddUnmanagedCallersOnlyAttribute (MethodDefinitionHandle handle) void EmitTypeMapAttribute (TypeMapAttributeData entry) { - MemberReferenceHandle ctorRef; - if (entry.AnchorRank is int rank) { - if (entry.IsUnconditional) { - throw new InvalidOperationException ( - $"Rank-anchored TypeMap entries must be conditional (3-arg). Entry '{entry.MapKey}' rank={rank}."); - } - int anchorIndex = rank - 1; - if ((uint)anchorIndex >= (uint)_rankAnchorHandles.Length) { - throw new InvalidOperationException ( - $"No rank-{rank} anchor available for entry '{entry.MapKey}'. " + - $"Ensure TypeMapAssemblyData.MaxArrayRank was >= {rank} before emit."); - } - ctorRef = GetOrAddTypeMapAttr3ArgCtorRef (_rankAnchorHandles [anchorIndex]); - } else { - ctorRef = entry.IsUnconditional ? _typeMapAttrCtorRef2Arg : _typeMapAttrCtorRef3Arg; - } + var ctorRef = entry.IsUnconditional ? _typeMapAttrCtorRef2Arg : _typeMapAttrCtorRef3Arg; var blob = _pe.BuildAttributeBlob (b => { b.WriteSerializedString (entry.MapKey); @@ -1998,15 +1814,6 @@ void EmitTypeMapAttribute (TypeMapAttributeData entry) void EmitTypeMapAssociationAttribute (TypeMapAssociationData assoc) { var ctorRef = _typeMapAssociationAttrCtorRef; - if (assoc.AnchorRank is int rank) { - int anchorIndex = rank - 1; - if ((uint)anchorIndex >= (uint)_rankAnchorHandles.Length) { - throw new InvalidOperationException ( - $"No rank-{rank} anchor available for association '{assoc.SourceTypeReference}'. " + - $"Ensure TypeMapAssemblyData.MaxArrayRank was >= {rank} before emit."); - } - ctorRef = GetOrAddTypeMapAssociationAttrCtorRef (_rankAnchorHandles [anchorIndex]); - } var blob = _pe.BuildAttributeBlob (b => { b.WriteSerializedString (assoc.SourceTypeReference); diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs index ae1e7cce9fe..1e449592a43 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs @@ -27,10 +27,9 @@ public TypeMapAssemblyGenerator (Version systemRuntimeVersion) /// /// When true, uses Java.Lang.Object as the shared anchor type. When false, emits a per-assembly anchor. /// - /// Max rank for per-rank array TypeMap entries. 0 disables. - public void Generate (IReadOnlyList peers, Stream stream, string assemblyName, bool useSharedTypemapUniverse = false, int maxArrayRank = 0) + public void Generate (IReadOnlyList peers, Stream stream, string assemblyName, bool useSharedTypemapUniverse = false) { - var model = ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName, maxArrayRank); + var model = ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName); var emitter = new TypeMapAssemblyEmitter (_systemRuntimeVersion); emitter.Emit (model, stream, useSharedTypemapUniverse); } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs index 3f55ef799d5..f16e61eb2cc 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs @@ -34,11 +34,6 @@ sealed class AssemblyIndex : IDisposable /// public Dictionary AttributesByType { get; } = new (); - /// - /// Type references grouped by referenced assembly name. - /// - public Dictionary> ReferencedTypeNamesByAssembly { get; } = new (StringComparer.OrdinalIgnoreCase); - /// /// Type-forwarded or otherwise exported types declared by this assembly. /// @@ -82,14 +77,6 @@ void Build () // the common case where the attribute is neither imported nor declared here. foreach (var trHandle in Reader.TypeReferences) { var typeReference = Reader.GetTypeReference (trHandle); - if (TryGetTypeReferenceAssemblyName (typeReference, out var assemblyName)) { - if (!ReferencedTypeNamesByAssembly.TryGetValue (assemblyName, out var typeNames)) { - typeNames = new HashSet (StringComparer.Ordinal); - ReferencedTypeNamesByAssembly [assemblyName] = typeNames; - } - typeNames.Add (MetadataTypeNameResolver.GetTypeFromReference (Reader, trHandle, rawTypeKind: 0)); - } - if (IsTypeReferenceMatch (typeReference, Reader, JavaInteropNamespace, JniAddNativeMethodRegistrationAttribute)) { MayUseJniAddNativeMethodRegistrationAttribute = true; } @@ -138,22 +125,6 @@ string GetExportedTypeFullName (ExportedType exportedType) } } - bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen (true)] out string? assemblyName) - { - var scope = typeReference.ResolutionScope; - while (scope.Kind == HandleKind.TypeReference) { - scope = Reader.GetTypeReference ((TypeReferenceHandle) scope).ResolutionScope; - } - if (scope.Kind == HandleKind.AssemblyReference) { - var assemblyReference = Reader.GetAssemblyReference ((AssemblyReferenceHandle) scope); - assemblyName = Reader.GetString (assemblyReference.Name); - return true; - } - - assemblyName = null; - return false; - } - (RegisterInfo? register, TypeAttributeInfo? attrs) ParseAttributes (TypeDefinition typeDef) { RegisterInfo? registerInfo = null; diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs index 6b6f4601c41..5f591978de3 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs @@ -50,11 +50,6 @@ public sealed record JavaPeerInfo /// public bool IsFrameworkAssembly { get; set; } - /// - /// True when per-rank array typemap entries should be generated for this peer. - /// - public bool GenerateArrayEntries { get; set; } = true; - /// /// JNI name of the base Java type, e.g., "android/app/Activity" for a type /// that extends Activity. Null for java/lang/Object or types without a Java base. diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs index f239f509f54..ec471cceef4 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs @@ -186,33 +186,9 @@ public List Scan (IEnumerable assemblies) ScanAssembly (index, resultsByQualifiedName); } ForceUnconditionalCrossReferences (resultsByQualifiedName, assemblyCache); - MarkFrameworkArrayEntryPeers (resultsByQualifiedName.Values); return new List (resultsByQualifiedName.Values); } - void MarkFrameworkArrayEntryPeers (IEnumerable peers) - { - var referencedFrameworkTypes = new HashSet (StringComparer.Ordinal); - foreach (var index in assemblyCache.Values) { - if (frameworkAssemblyNames.Contains (index.AssemblyName)) { - continue; - } - foreach (var referencedTypeNames in index.ReferencedTypeNamesByAssembly) { - if (frameworkAssemblyNames.Contains (referencedTypeNames.Key)) { - referencedFrameworkTypes.UnionWith (referencedTypeNames.Value); - } - } - } - - foreach (var peer in peers) { - if (!peer.IsFrameworkAssembly) { - continue; - } - - peer.GenerateArrayEntries = referencedFrameworkTypes.Contains (peer.ManagedTypeName); - } - } - /// /// Scans all loaded assemblies for assembly-level manifest attributes. /// Must be called after . @@ -394,7 +370,6 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A ManagedTypeShortName = ExtractShortName (fullName), AssemblyName = index.AssemblyName, IsFrameworkAssembly = frameworkAssemblyNames.Contains (index.AssemblyName), - GenerateArrayEntries = !frameworkAssemblyNames.Contains (index.AssemblyName), BaseJavaName = baseJavaName, ImplementedInterfaceJavaNames = implementedInterfaces, IsInterface = isInterface, diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index fee0811ebc4..143060577e4 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -34,16 +34,11 @@ public TrimmableTypeMapResult Execute ( ManifestConfig? manifestConfig = null, XDocument? manifestTemplate = null, string? packageNamingPolicy = null, - int maxArrayRank = 0, bool generateTypeMapAssemblies = true) { _ = assemblies ?? throw new ArgumentNullException (nameof (assemblies)); _ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion)); _ = frameworkAssemblyNames ?? throw new ArgumentNullException (nameof (frameworkAssemblyNames)); - if (maxArrayRank < 0) { - throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank, "Must be >= 0."); - } - var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies, packageNamingPolicy, frameworkAssemblyNames); if (allPeers.Count == 0) { logger.LogNoJavaPeerTypesFound (); @@ -56,7 +51,7 @@ public TrimmableTypeMapResult Execute ( PropagateCannotRegisterToDescendants (allPeers); var generatedAssemblies = generateTypeMapAssemblies - ? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse, maxArrayRank) + ? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse) : []; var jcwPeers = allPeers.Where (ShouldGenerateJcw).ToList (); logger.LogGeneratingJcwFilesInfo (jcwPeers.Count, allPeers.Count); @@ -178,8 +173,7 @@ GeneratedManifest GenerateManifest (List allPeers, AssemblyManifes List GenerateTypeMapAssemblies ( List allPeers, Version systemRuntimeVersion, - bool useSharedTypemapUniverse, - int maxArrayRank) + bool useSharedTypemapUniverse) { List<(string AssemblyName, List Peers)> peersByAssembly; @@ -208,14 +202,14 @@ List GenerateTypeMapAssemblies ( string typeMapAssemblyName = $"_{assemblyName}.TypeMap"; perAssemblyNames.Add (typeMapAssemblyName); var stream = new MemoryStream (); - generator.Generate (peers, stream, typeMapAssemblyName, useSharedTypemapUniverse, maxArrayRank); + generator.Generate (peers, stream, typeMapAssemblyName, useSharedTypemapUniverse); stream.Position = 0; generatedAssemblies.Add (new GeneratedAssembly (typeMapAssemblyName, stream)); logger.LogGeneratedTypeMapAssemblyInfo (typeMapAssemblyName, peers.Count); } var rootStream = new MemoryStream (); var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion); - rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream, maxArrayRank: maxArrayRank); + rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream); rootStream.Position = 0; generatedAssemblies.Add (new GeneratedAssembly ("_Microsoft.Android.TypeMaps", rootStream)); logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count); diff --git a/src/Mono.Android/Java.Interop/JavaPeerContainerFactory.cs b/src/Mono.Android/Java.Interop/JavaPeerContainerFactory.cs deleted file mode 100644 index c17728d5259..00000000000 --- a/src/Mono.Android/Java.Interop/JavaPeerContainerFactory.cs +++ /dev/null @@ -1,77 +0,0 @@ -#nullable enable - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Android.Runtime; - -namespace Java.Interop -{ - /// - /// AOT-safe factory for creating typed containers (lists, collections, dictionaries). - /// Array creation lives in JNIEnv.ArrayCreateInstance. - /// - public abstract class JavaPeerContainerFactory - { - private protected const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; - - /// - /// Creates a typed JavaList<T> from a JNI handle. - /// - internal abstract IList CreateList (IntPtr handle, JniHandleOwnership transfer); - - /// - /// Creates a typed JavaCollection<T> from a JNI handle. - /// - internal abstract ICollection CreateCollection (IntPtr handle, JniHandleOwnership transfer); - - /// - /// Creates a typed JavaDictionary<TKey, TValue> using the visitor pattern. - /// This factory provides the value type; provides the key type. - /// - internal virtual IDictionary? CreateDictionary (JavaPeerContainerFactory keyFactory, IntPtr handle, JniHandleOwnership transfer) - => null; - - /// - /// Visitor callback invoked by the value factory's . - /// Override in to provide both type parameters. - /// - internal virtual IDictionary? CreateDictionaryWithValueFactory<[DynamicallyAccessedMembers (Constructors)] TValue> ( - JavaPeerContainerFactory valueFactory, IntPtr handle, JniHandleOwnership transfer) - => null; - - /// - /// Creates a singleton for the specified type. - /// - public static JavaPeerContainerFactory Create< - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - T - > () - => JavaPeerContainerFactory.Instance; - } - - /// - /// Typed container factory. All creation uses direct new expressions — fully AOT-safe. - /// - /// The container element type. - public sealed class JavaPeerContainerFactory<[DynamicallyAccessedMembers (Constructors)] T> : JavaPeerContainerFactory - { - internal static readonly JavaPeerContainerFactory Instance = new (); - - JavaPeerContainerFactory () { } - - internal override IList CreateList (IntPtr handle, JniHandleOwnership transfer) - => new Android.Runtime.JavaList (handle, transfer); - - internal override ICollection CreateCollection (IntPtr handle, JniHandleOwnership transfer) - => new Android.Runtime.JavaCollection (handle, transfer); - - internal override IDictionary? CreateDictionary (JavaPeerContainerFactory keyFactory, IntPtr handle, JniHandleOwnership transfer) - => keyFactory.CreateDictionaryWithValueFactory (this, handle, transfer); - - internal override IDictionary? CreateDictionaryWithValueFactory<[DynamicallyAccessedMembers (Constructors)] TValue> ( - JavaPeerContainerFactory valueFactory, IntPtr handle, JniHandleOwnership transfer) - => new Android.Runtime.JavaDictionary (handle, transfer); - } -} diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 7c086dfefe8..5904af40ee2 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -70,13 +70,6 @@ protected JavaPeerProxy (string jniName, Type targetType) /// public Type TargetType { get; } - /// - /// Gets a factory for creating containers (arrays, collections) of the target type. - /// Enables AOT-safe creation of generic collections without MakeGenericType(). - /// - /// A factory for creating containers of the target type, or null if not supported. - public virtual JavaPeerContainerFactory? GetContainerFactory () => null; - /// /// Returns when the UCO constructor callback should skip /// activation because a managed peer already exists for the given JNI handle @@ -128,7 +121,7 @@ static bool IsActivationPeer (IJavaPeerable peer) /// /// Generic base for generated proxy types. Provides - /// and automatically from the type parameter. + /// automatically from the type parameter. /// /// The target .NET peer type this proxy represents. [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] @@ -141,42 +134,5 @@ protected JavaPeerProxy (string jniName) : base (jniName, typeof (T)) { } - - public override JavaPeerContainerFactory? GetContainerFactory () - => JavaPeerContainerFactory.Instance; - } - - /// - /// Base attribute class for generated array-proxy types that enable AOT-safe construction - /// of managed arrays for a specific element type and rank, without - /// or other reflection-based allocation. - /// - /// - /// Like , each generated array proxy is applied to its own holder - /// type (self-application pattern), so the runtime can retrieve it via - /// GetCustomAttribute<JavaArrayProxy>() and invoke it without - /// Activator.CreateInstance(). The per-rank TypeMap groups - /// (__ArrayMapRank{N}) map a JNI name to the holder type carrying this attribute; - /// TrimmableTypeMap.TryGetArrayProxy resolves the holder and returns the attribute. - /// - [AttributeUsage (AttributeTargets.Class, Inherited = false, AllowMultiple = false)] - public abstract class JavaArrayProxy : Attribute - { - /// - /// Gets the .NET array and wrapper types associated with this proxy (for example - /// T[], JavaArray<T>, and the matching JavaObjectArray<T> / - /// JavaPrimitiveArray<T> wrappers). Emitting these tokens - /// roots the types so the trimmer/ILC keeps them available for marshaling. - /// - /// The array and wrapper types handled by this proxy. - public abstract Type[] GetArrayTypes (); - - /// - /// Creates a new managed array of this proxy's element type and rank using a rooted - /// newarr, which is AOT-safe unlike . - /// - /// The length of the outermost array dimension. - /// A new array of the proxy's element type with the requested length. - public abstract Array CreateManagedArray (int length); } } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs index 5c5a5b71864..10c2da6e018 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs @@ -41,16 +41,4 @@ public bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? pr proxyType = null; return false; } - - public bool TryGetArrayProxyType (string managedTypeKey, int rankIndex, [NotNullWhen (true)] out Type? proxyType) - { - foreach (var universe in _universes) { - if (universe.TryGetArrayProxyType (managedTypeKey, rankIndex, out proxyType)) { - return true; - } - } - - proxyType = null; - return false; - } } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs index 1aa51fd1362..58c2dc64065 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs @@ -10,7 +10,7 @@ namespace Microsoft.Android.Runtime; /// Abstraction over a generated typemap universe. /// Both Debug (per-assembly universes) and Release (single merged universe) /// go through this interface, so doesn't need -/// to know about aliasing mechanics or per-rank array map storage. +/// to know about aliasing mechanics. /// interface ITypeMap { @@ -24,9 +24,4 @@ interface ITypeMap /// carries the attribute). /// bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? proxyType); - - /// - /// Resolves a managed element type key and 0-based array rank index to a generated array proxy type. - /// - bool TryGetArrayProxyType (string managedTypeKey, int rankIndex, [NotNullWhen (true)] out Type? proxyType); } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs index 2046ec1910e..bce4b41f0c5 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs @@ -18,31 +18,13 @@ sealed class SingleUniverseTypeMap : ITypeMap { readonly IReadOnlyDictionary _typeMap; readonly IReadOnlyDictionary _proxyTypeMap; - readonly IReadOnlyDictionary?[][] _arrayMapsByUniverseAndRank; public SingleUniverseTypeMap (IReadOnlyDictionary typeMap, IReadOnlyDictionary proxyTypeMap) - : this (typeMap, proxyTypeMap, arrayMapsByRank: null) - { - } - - public SingleUniverseTypeMap ( - IReadOnlyDictionary typeMap, - IReadOnlyDictionary proxyTypeMap, - IReadOnlyDictionary?[]? arrayMapsByRank) - : this (typeMap, proxyTypeMap, arrayMapsByRank is null ? null : [arrayMapsByRank]) - { - } - - public SingleUniverseTypeMap ( - IReadOnlyDictionary typeMap, - IReadOnlyDictionary proxyTypeMap, - IReadOnlyDictionary?[][]? arrayMapsByUniverseAndRank) { ArgumentNullException.ThrowIfNull (typeMap); ArgumentNullException.ThrowIfNull (proxyTypeMap); _typeMap = typeMap; _proxyTypeMap = proxyTypeMap; - _arrayMapsByUniverseAndRank = arrayMapsByUniverseAndRank ?? []; } public IEnumerable GetProxyTypes (string jniName) @@ -101,18 +83,4 @@ public bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? pr proxyType = null; return false; } - - public bool TryGetArrayProxyType (string managedTypeKey, int rankIndex, [NotNullWhen (true)] out Type? proxyType) - { - foreach (var arrayMapsByRank in _arrayMapsByUniverseAndRank) { - if ((uint)rankIndex < (uint)arrayMapsByRank.Length && - arrayMapsByRank [rankIndex] is { } dict && - dict.TryGetValue (managedTypeKey, out proxyType)) { - return true; - } - } - - proxyType = null; - return false; - } } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index 4ffbf4d4669..f887629cab6 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -14,14 +14,13 @@ namespace Microsoft.Android.Runtime; /// /// Central type map for the trimmable typemap path. Owns the ITypeMap -/// and provides peer creation, invoker resolution, container factories, and native +/// and provides peer creation, invoker resolution, and native /// method registration. All proxy attribute access is encapsulated here. /// public class TrimmableTypeMap { static readonly Lock s_initLock = new (); static readonly JavaPeerProxy s_noPeerSentinel = new MissingJavaPeerProxy (); - static readonly JavaArrayProxy s_noArrayProxySentinel = new MissingJavaArrayProxy (); static TrimmableTypeMap? s_instance; static bool s_nativeMethodsRegistered; static JniMethodInfo? s_classGetInterfacesMethod; @@ -32,7 +31,6 @@ public class TrimmableTypeMap readonly ITypeMap _typeMap; readonly ConcurrentDictionary _proxyCache = new (); - readonly ConcurrentDictionary _arrayProxyCache = new (); readonly ConcurrentDictionary _jniProxyCache = new (StringComparer.Ordinal); readonly ConcurrentDictionary<(string ClassName, Type TargetType), JavaPeerProxy> _interfaceProxyCache = new (); @@ -47,58 +45,18 @@ public class TrimmableTypeMap public static void Initialize ( IReadOnlyDictionary typeMap, IReadOnlyDictionary proxyMap) - { - Initialize (typeMap, proxyMap, arrayMapsByRank: null); - } - - /// - /// Initializes the singleton with a single merged typemap universe and the per-rank array - /// dictionaries emitted by the current generator. - /// - /// 0-indexed by (rank - 1); retained for compatibility with the current generated typemap format. - public static void Initialize ( - IReadOnlyDictionary typeMap, - IReadOnlyDictionary proxyMap, - IReadOnlyDictionary?[]? arrayMapsByRank) - { - ArgumentNullException.ThrowIfNull (typeMap); - ArgumentNullException.ThrowIfNull (proxyMap); - InitializeCore (new SingleUniverseTypeMap (typeMap, proxyMap, arrayMapsByRank)); - } - - /// - /// Initializes the singleton with a single merged typemap universe and per-assembly array maps. - /// - public static void Initialize ( - IReadOnlyDictionary typeMap, - IReadOnlyDictionary proxyMap, - IReadOnlyDictionary?[][]? arrayMapsByUniverseAndRank) { ArgumentNullException.ThrowIfNull (typeMap); ArgumentNullException.ThrowIfNull (proxyMap); - InitializeCore (new SingleUniverseTypeMap (typeMap, proxyMap, arrayMapsByUniverseAndRank)); + InitializeCore (new SingleUniverseTypeMap (typeMap, proxyMap)); } /// - /// Initializes the singleton with multiple per-assembly typemap universes and optional - /// per-universe array dictionaries. + /// Initializes the singleton with multiple per-assembly typemap universes. /// public static void Initialize ( IReadOnlyDictionary[] typeMaps, IReadOnlyDictionary[] proxyMaps) - { - Initialize (typeMaps, proxyMaps, arrayMapsByUniverseAndRank: null); - } - - /// - /// Initializes the singleton with multiple per-assembly typemap universes and optional - /// per-universe array dictionaries. - /// - /// Array maps indexed by universe, then by 0-based rank. - public static void Initialize ( - IReadOnlyDictionary[] typeMaps, - IReadOnlyDictionary[] proxyMaps, - IReadOnlyDictionary?[][]? arrayMapsByUniverseAndRank) { ArgumentNullException.ThrowIfNull (typeMaps); ArgumentNullException.ThrowIfNull (proxyMaps); @@ -108,13 +66,10 @@ public static void Initialize ( if (typeMaps.Length != proxyMaps.Length) { throw new ArgumentException ($"typeMaps.Length ({typeMaps.Length}) must equal proxyMaps.Length ({proxyMaps.Length}).", nameof (proxyMaps)); } - if (arrayMapsByUniverseAndRank is not null && arrayMapsByUniverseAndRank.Length != typeMaps.Length) { - throw new ArgumentException ($"arrayMapsByUniverseAndRank.Length ({arrayMapsByUniverseAndRank.Length}) must equal typeMaps.Length ({typeMaps.Length}).", nameof (arrayMapsByUniverseAndRank)); - } var universes = new SingleUniverseTypeMap [typeMaps.Length]; for (int i = 0; i < typeMaps.Length; i++) { - universes [i] = new SingleUniverseTypeMap (typeMaps [i], proxyMaps [i], arrayMapsByUniverseAndRank? [i]); + universes [i] = new SingleUniverseTypeMap (typeMaps [i], proxyMaps [i]); } InitializeCore (new AggregateTypeMap (universes)); } @@ -492,72 +447,6 @@ internal static bool TargetTypeMatches (Type targetType, Type proxyTargetType) return targetType.IsAssignableFrom (proxyTargetType); } - /// Lookup of the generated array proxy after adding array rank to the given element type. - internal bool TryGetArrayProxy (Type elementType, int additionalRank, [NotNullWhen (true)] out JavaArrayProxy? arrayProxy) - { - ArgumentOutOfRangeException.ThrowIfNegativeOrZero (additionalRank); - - var leafType = elementType; - int rankIndex = additionalRank - 1; - while (leafType.IsArray) { - if (!leafType.IsSZArray) { - arrayProxy = null; - return false; - } - var next = leafType.GetElementType (); - if (next is null) { - arrayProxy = null; - return false; - } - leafType = next; - rankIndex++; - } - - if (!TryGetManagedTypeKey (leafType, out var managedTypeKey)) { - arrayProxy = null; - return false; - } - - if (_typeMap.TryGetArrayProxyType (managedTypeKey, rankIndex, out var proxyType)) { - var proxy = _arrayProxyCache.GetOrAdd (proxyType, static type => - type.GetCustomAttribute (inherit: false) ?? s_noArrayProxySentinel); - if (!ReferenceEquals (proxy, s_noArrayProxySentinel)) { - arrayProxy = proxy; - return true; - } - } - - arrayProxy = null; - return false; - } - - static bool TryGetManagedTypeKey (Type type, [NotNullWhen (true)] out string? key) - { - var fullName = type.FullName; - if (fullName is null) { - key = null; - return false; - } - - var assemblyName = GetAssemblyNameForManagedTypeKey (type); - if (assemblyName is null) { - key = null; - return false; - } - - key = $"{fullName}, {assemblyName}"; - return true; - } - - static string? GetAssemblyNameForManagedTypeKey (Type type) - { - if (type.IsPrimitive || type == typeof (string)) { - return "System.Runtime"; - } - - return type.Assembly.GetName ().Name; - } - [UnmanagedCallersOnly] static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHandle) { @@ -601,11 +490,4 @@ public MissingJavaPeerProxy () : base ("", typeof (Java.Lang.Object)) public override IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer) => null; } - sealed class MissingJavaArrayProxy : JavaArrayProxy - { - public override Type[] GetArrayTypes () => []; - - public override Array CreateManagedArray (int length) => throw new NotSupportedException (); - } - } diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 25aee38cdf3..e70c2f00ccf 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -319,7 +319,6 @@ - diff --git a/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt index 867f297909b..232ca9037b0 100644 --- a/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt @@ -26,6 +26,3 @@ REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOu Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void Java.Interop.BaseExportAttribute -Java.Interop.JavaArrayProxy -abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array! -abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]! diff --git a/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt index 867f297909b..232ca9037b0 100644 --- a/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt @@ -26,6 +26,3 @@ REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOu Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void Java.Interop.BaseExportAttribute -Java.Interop.JavaArrayProxy -abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array! -abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]! diff --git a/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt index 45e15c9a177..2716042abfc 100644 --- a/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt @@ -4402,6 +4402,3 @@ virtual Java.Util.IdentityHashMap.Replace(Java.Lang.Object? key, Java.Lang.Objec Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void Java.Interop.BaseExportAttribute -Java.Interop.JavaArrayProxy -abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array! -abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]! diff --git a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt index 590554307d8..535fcd940b5 100644 --- a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt @@ -2346,14 +2346,9 @@ Java.Interop.BaseExportAttribute Java.Interop.BaseExportAttribute.BaseExportAttribute() -> void Java.Interop.IAndroidCallableWrapper Java.Interop.IAndroidCallableWrapper.RegisterNatives(Java.Interop.JniType! nativeClass) -> void -Java.Interop.JavaArrayProxy -Java.Interop.JavaArrayProxy.JavaArrayProxy() -> void Java.Interop.JavaPeerAliasesAttribute Java.Interop.JavaPeerAliasesAttribute.Aliases.get -> string![]! Java.Interop.JavaPeerAliasesAttribute.JavaPeerAliasesAttribute(params string![]! aliases) -> void -Java.Interop.JavaPeerContainerFactory -Java.Interop.JavaPeerContainerFactory.JavaPeerContainerFactory() -> void -Java.Interop.JavaPeerContainerFactory Java.Interop.JavaPeerProxy Java.Interop.JavaPeerProxy.JavaPeerProxy(string! jniName, System.Type! targetType) -> void Java.Interop.JavaPeerProxy.JniName.get -> string! @@ -2452,8 +2447,6 @@ abstract Android.OS.Storage.Operations.Sources.OperationSource.ToString() -> str abstract Android.OS.Storage.Operations.Targets.OperationTarget.ToString() -> string! abstract Android.Service.Messaging.AlternativeMessageTransportService.OnMessageUpgradeRequested(Android.Net.Uri! contentUri, Java.Util.Functions.IConsumer! upgradeStatus) -> void abstract Android.Views.BlurRegion.Copy() -> Android.Views.BlurRegion! -abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array! -abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]! abstract Java.Interop.JavaPeerProxy.CreateInstance(nint handle, Android.Runtime.JniHandleOwnership transfer) -> Java.Interop.IJavaPeerable? abstract Java.Util.Spliterators.AbstractSpliterator.TryAdvance(Java.Util.Functions.IConsumer? action) -> bool const Android.App.ActivityManager.AppTask.WindowingLayerNormalApp = Android.App.ActivityManagerAppTaskWindowingLayer.NormalApp -> Android.App.ActivityManagerAppTaskWindowingLayer @@ -4145,7 +4138,6 @@ override Android.Widget.PhotoPicker.PhotoPickerSelectionParams.Builder.JniPeerMe override Android.Widget.PhotoPicker.PhotoPickerSelectionParams.JniPeerMembers.get -> Java.Interop.JniPeerMembers! override Android.Widget.PhotoPicker.PhotoPickerUiCustomizationParams.Builder.JniPeerMembers.get -> Java.Interop.JniPeerMembers! override Android.Widget.PhotoPicker.PhotoPickerUiCustomizationParams.JniPeerMembers.get -> Java.Interop.JniPeerMembers! -override Java.Interop.JavaPeerProxy.GetContainerFactory() -> Java.Interop.JavaPeerContainerFactory? override Java.Lang.IllegalCallerException.JniPeerMembers.get -> Java.Interop.JniPeerMembers! override Java.Lang.IllegalCallerException.ThresholdClass.get -> nint override Java.Lang.IllegalCallerException.ThresholdType.get -> System.Type! @@ -4383,7 +4375,6 @@ static Android.Views.View.RegisterCalledFromWrongThreadListener(Android.Views.Vi static Android.Views.View.UnregisterCalledFromWrongThreadListener(Android.Views.View.ICalledFromWrongThreadListener! listener) -> void static Android.Widget.PhotoPicker.PhotoPickerSelectionParams.Creator.get -> Android.OS.IParcelableCreator! static Android.Widget.PhotoPicker.PhotoPickerUiCustomizationParams.Creator.get -> Android.OS.IParcelableCreator! -static Java.Interop.JavaPeerContainerFactory.Create() -> Java.Interop.JavaPeerContainerFactory! static Java.Interop.JavaPeerProxy.GetActivationPeer(nint jniSelf) -> Java.Interop.IJavaPeerable? static Java.Interop.JavaPeerProxy.MarkActivationPeerReplaceable(nint jniSelf) -> void static Java.Interop.JavaPeerProxy.SetActivationPeerReference(Java.Interop.IJavaPeerable! peer, nint jniSelf) -> void @@ -4453,10 +4444,7 @@ static Javax.Xml.Parsers.DocumentBuilderFactory.NewDefaultNSInstance() -> Javax. static Javax.Xml.Parsers.DocumentBuilderFactory.NewNSInstance() -> Javax.Xml.Parsers.DocumentBuilderFactory? static Javax.Xml.Parsers.DocumentBuilderFactory.NewNSInstance(string? factoryClassName, Java.Lang.ClassLoader? classLoader) -> Javax.Xml.Parsers.DocumentBuilderFactory? static Microsoft.Android.Runtime.TrimmableTypeMap.Initialize(System.Collections.Generic.IReadOnlyDictionary! typeMap, System.Collections.Generic.IReadOnlyDictionary! proxyMap) -> void -static Microsoft.Android.Runtime.TrimmableTypeMap.Initialize(System.Collections.Generic.IReadOnlyDictionary! typeMap, System.Collections.Generic.IReadOnlyDictionary! proxyMap, System.Collections.Generic.IReadOnlyDictionary?[]![]? arrayMapsByUniverseAndRank) -> void -static Microsoft.Android.Runtime.TrimmableTypeMap.Initialize(System.Collections.Generic.IReadOnlyDictionary! typeMap, System.Collections.Generic.IReadOnlyDictionary! proxyMap, System.Collections.Generic.IReadOnlyDictionary?[]? arrayMapsByRank) -> void static Microsoft.Android.Runtime.TrimmableTypeMap.Initialize(System.Collections.Generic.IReadOnlyDictionary![]! typeMaps, System.Collections.Generic.IReadOnlyDictionary![]! proxyMaps) -> void -static Microsoft.Android.Runtime.TrimmableTypeMap.Initialize(System.Collections.Generic.IReadOnlyDictionary![]! typeMaps, System.Collections.Generic.IReadOnlyDictionary![]! proxyMaps, System.Collections.Generic.IReadOnlyDictionary?[]![]? arrayMapsByUniverseAndRank) -> void virtual Android.App.Activity.OnHandoffActivityDataRequested(Android.App.HandoffActivityDataRequestInfo! requestInfo) -> Android.App.HandoffActivityData? virtual Android.App.ActivityManager.AppTask.MoveTaskTo(Android.App.TaskLocation! location, Java.Util.Concurrent.IExecutor! executor, Android.OS.IOutcomeReceiver! callback) -> void virtual Android.App.ActivityManager.AppTask.RequestWindowingLayer(int layer, Java.Util.Concurrent.IExecutor! executor, Android.OS.IOutcomeReceiver! callback) -> void @@ -4702,7 +4690,6 @@ virtual Android.Views.WindowManagerLayoutParams.SetKeyboardCaptureEnabled(bool e virtual Android.Webkit.WebChromeClient.FileChooserParams.PermissionMode.get -> int virtual Android.Widget.RemoteViews.SetAppWidgetEventTag(int viewId, int tag) -> void virtual Android.Widget.RemoteViews.SetViewPadding(int viewId, float left, float top, float right, float bottom, int units) -> void -virtual Java.Interop.JavaPeerProxy.GetContainerFactory() -> Java.Interop.JavaPeerContainerFactory? virtual Java.Nio.CharBuffer.GetChars(int srcBegin, int srcEnd, char[]! dst, int dstBegin) -> void virtual Javax.Net.Ssl.ExtendedSSLSession.StatusResponses.get -> System.Collections.Generic.IList? virtual Javax.Net.Ssl.SSLParameters.GetNamedGroups() -> string![]? diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets index fbeb48e59f6..37bb53c617e 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets @@ -81,7 +81,6 @@ JavaSourceInputDirectory="$(_TypeMapJavaOutputDirectory)" TargetFrameworkVersion="$(TargetFrameworkVersion)" PackageNamingPolicy="$(_TrimmableTypeMapPackageNamingPolicy)" - MaxArrayRank="$(_AndroidTrimmableTypeMapMaxArrayRank)" GenerateTypeMapAssemblies="false" CleanJavaSourceOutputDirectory="true" AcwMapOutputFile="$(IntermediateOutputPath)acw-map.txt" diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets index b66b2e0208d..60be0f3ccf7 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets @@ -50,11 +50,6 @@ <_GenerateTrimmableTypeMapDependsOn>$(_GenerateTrimmableTypeMapDependsOn);_GetLibraryImports - - <_AndroidTrimmableTypeMapMaxArrayRank Condition=" '$(_AndroidTrimmableTypeMapMaxArrayRank)' == '' and ('$(PublishAot)' == 'true' or '$(DynamicCodeSupport)' == 'false') ">3 - <_AndroidTrimmableTypeMapMaxArrayRank Condition=" '$(_AndroidTrimmableTypeMapMaxArrayRank)' == '' ">0 _RecordTrimmableTypeMapFileWrites; $(IncrementalCleanDependsOn); @@ -150,7 +145,6 @@ NeedsInternet="$(AndroidNeedsInternetPermission)" EmbedAssemblies="$(EmbedAssembliesIntoApk)" PackageNamingPolicy="$(_TrimmableTypeMapPackageNamingPolicy)" - MaxArrayRank="$(_AndroidTrimmableTypeMapMaxArrayRank)" ManifestPlaceholders="$(AndroidManifestPlaceholders)" CheckedBuild="$(_AndroidCheckedBuild)" ApplicationJavaClass="$(AndroidApplicationJavaClass)" diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs index b1dcd144167..c12cba01d5e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs @@ -102,13 +102,6 @@ public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeN public bool EmbedAssemblies { get; set; } public string? PackageNamingPolicy { get; set; } - /// - /// Maximum array rank for which the generator emits per-rank __ArrayMapRank{N} - /// sentinels and TypeMap entries. 0 disables. Set via - /// $(_AndroidTrimmableTypeMapMaxArrayRank). - /// - public int MaxArrayRank { get; set; } - public string? ManifestPlaceholders { get; set; } public string? CheckedBuild { get; set; } public string? ApplicationJavaClass { get; set; } @@ -214,7 +207,6 @@ public override bool RunTask () manifestConfig: manifestConfig, manifestTemplate: manifestTemplate, packageNamingPolicy: PackageNamingPolicy, - maxArrayRank: MaxArrayRank, generateTypeMapAssemblies: GenerateTypeMapAssemblies); if (GenerateTypeMapAssemblies) { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs index e62f30cad4f..c9f5807bfdd 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs @@ -5,7 +5,6 @@ using System.Security.Cryptography; using System.Text.Json; using System.Text.RegularExpressions; -using Mono.Cecil; using NUnit.Framework; using Xamarin.Android.AssemblyStore; using Xamarin.Android.Tasks; @@ -310,31 +309,6 @@ public void Build_WithTrimmableTypeMap_PublishTrimmed_PostTrimJavaGenerationIsIn builder.Output.AssertTargetIsSkipped ("_GenerateJavaStubs"); } - [Test] - public void Build_WithTrimmableTypeMap_ArrayRankChangeRegeneratesTypeMap () - { - if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: true)) { - return; - } - - var proj = new XamarinAndroidApplicationProject { - IsRelease = true, - }; - proj.SetRuntime (AndroidRuntime.CoreCLR); - proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); - proj.SetProperty ("_AndroidTrimmableTypeMapMaxArrayRank", "0"); - - using var builder = CreateApkBuilder (); - Assert.IsTrue (builder.Build (proj), "First build should have succeeded."); - builder.Output.AssertTargetIsNotSkipped ("_GenerateTrimmableTypeMap"); - - Assert.IsTrue (builder.Build (proj, doNotCleanupOnUpdate: true), "Second build should have succeeded."); - builder.Output.AssertTargetIsSkipped ("_GenerateTrimmableTypeMap"); - - proj.SetProperty ("_AndroidTrimmableTypeMapMaxArrayRank", "3"); - Assert.IsTrue (builder.Build (proj, doNotCleanupOnUpdate: true), "Array rank change build should have succeeded."); - builder.Output.AssertTargetIsNotSkipped ("_GenerateTrimmableTypeMap"); - } [Test] public void Build_WithTrimmableTypeMap_DoesNotHitCopyIfChangedMismatch ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime) @@ -526,9 +500,6 @@ public void ReleaseCoreClrTrimmableTypeMap_SupportsExplicitDynamicCodeSupportOff Assert.IsFalse ( dynamicCodeSupportProperty.GetBoolean (), "trimmable typemap builds should honor explicit DynamicCodeSupport=false."); - Assert.IsTrue ( - dynamicCodeDisabledTrimmable.LinkedTypeMapAssembliesContainArrayRankSentinels, - "trimmable typemap builds should emit array typemap sentinels when dynamic code is disabled."); } [Test] @@ -872,10 +843,7 @@ DynamicCodeSupportProfile BuildDynamicCodeSupportProfile (string typemapImplemen Assert.IsTrue (builder.Build (proj), $"{typemapImplementation} build should have succeeded."); var runtimeConfigPath = FindOutputFile (builder, proj, $"{proj.ProjectName}.runtimeconfig.json"); - var linkedAssemblyDirectory = builder.Output.GetIntermediaryPath (Path.Combine ("android-arm64", "linked")); - return new DynamicCodeSupportProfile ( - File.ReadAllText (runtimeConfigPath), - TypeMapAssembliesContainType (linkedAssemblyDirectory, "__ArrayMapRank1")); + return new DynamicCodeSupportProfile (File.ReadAllText (runtimeConfigPath)); } ISet ReadPackagedManagedAssemblyNames (string apkPath, AndroidTargetArch targetArch) @@ -930,22 +898,6 @@ string FindOutputFile (ProjectBuilder builder, XamarinAndroidApplicationProject return files [0]; } - bool TypeMapAssembliesContainType (string directory, string typeName) - { - if (!Directory.Exists (directory)) { - return false; - } - - foreach (var file in Directory.EnumerateFiles (directory, "*.dll", SearchOption.TopDirectoryOnly).Where (IsTypeMapAssemblyPath)) { - using var assembly = AssemblyDefinition.ReadAssembly (file); - if (assembly.Modules.SelectMany (m => m.Types).Any (type => type.Name == typeName)) { - return true; - } - } - - return false; - } - bool IsTypeMapAssemblyPath (string file) { return IsTypeMapAssemblyName (Path.GetFileName (file)); @@ -973,8 +925,6 @@ static byte [] ComputeHash (Stream stream) return SHA256.HashData (stream); } - sealed record DynamicCodeSupportProfile ( - string RuntimeConfig, - bool LinkedTypeMapAssembliesContainArrayRankSentinels); + sealed record DynamicCodeSupportProfile (string RuntimeConfig); } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 8e0b5004150..fc405081523 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -997,7 +997,6 @@ because xbuild doesn't support framework reference assemblies. <_PropertyCacheItems Include="ProjectFullPath=$(MSBuildProjectFullPath)" /> <_PropertyCacheItems Include="AndroidUseDesignerAssembly=$(AndroidUseDesignerAssembly)" /> <_PropertyCacheItems Include="_AndroidTypeMapImplementation=$(_AndroidTypeMapImplementation)" /> - <_PropertyCacheItems Include="_AndroidTrimmableTypeMapMaxArrayRank=$(_AndroidTrimmableTypeMapMaxArrayRank)" /> <_PropertyCacheItems Include="_AndroidUseMarshalMethods=$(_AndroidUseMarshalMethods)" /> <_PropertyCacheItems Include="_AndroidJcwCodegenTarget=$(_AndroidJcwCodegenTarget)" /> <_PropertyCacheItems Include="_AndroidAssemblyStoreCompressionLevel=$(_AndroidAssemblyStoreCompressionLevel)" /> diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs index 2ad242d096a..b609e77deb5 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs @@ -11,12 +11,11 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap.Tests; public class RootTypeMapAssemblyGeneratorTests : FixtureTestBase { - - static MemoryStream GenerateRootAssembly (IReadOnlyList perAssemblyNames, bool useSharedTypemapUniverse = false, string? assemblyName = null, int maxArrayRank = 0) + static MemoryStream GenerateRootAssembly (IReadOnlyList perAssemblyNames, bool useSharedTypemapUniverse = false, string? assemblyName = null) { var stream = new MemoryStream (); var generator = new RootTypeMapAssemblyGenerator (new Version (11, 0, 0, 0)); - generator.Generate (perAssemblyNames, useSharedTypemapUniverse, stream, assemblyName, maxArrayRank: maxArrayRank); + generator.Generate (perAssemblyNames, useSharedTypemapUniverse, stream, assemblyName); stream.Position = 0; return stream; } @@ -180,24 +179,6 @@ public void Generate_MergedMode_TargetAttributesUseSharedAnchor () }, targetAttributes); } - [Theory] - [InlineData (true)] - [InlineData (false)] - public void Generate_WithArrays_TargetAttributesUsePerAssemblyRankAnchors (bool useSharedTypemapUniverse) - { - var targets = new [] { "_App.TypeMap", "_Mono.Android.TypeMap" }; - using var stream = GenerateRootAssembly (targets, useSharedTypemapUniverse, maxArrayRank: 2); - using var pe = new PEReader (stream); - var reader = pe.GetMetadataReader (); - - var targetAttributes = GetTypeMapAssemblyTargetAttributeTargetTypes (reader); - - Assert.Contains (("_App.TypeMap", "_App.TypeMap", "__ArrayMapRank1"), targetAttributes); - Assert.Contains (("_App.TypeMap", "_App.TypeMap", "__ArrayMapRank2"), targetAttributes); - Assert.Contains (("_Mono.Android.TypeMap", "_Mono.Android.TypeMap", "__ArrayMapRank1"), targetAttributes); - Assert.Contains (("_Mono.Android.TypeMap", "_Mono.Android.TypeMap", "__ArrayMapRank2"), targetAttributes); - Assert.DoesNotContain (targetAttributes, t => t.GenericArgumentTypeName == "__ArrayMapRank3"); - } static List GetTypeMapAssemblyTargetAttributes (MetadataReader reader) { @@ -234,25 +215,6 @@ static List GetTypeMapAssemblyTargetAttributes (MetadataReader return result; } - static List<(string TargetName, string GenericArgumentScope, string GenericArgumentTypeName)> GetTypeMapAssemblyTargetAttributeTargetTypes (MetadataReader reader) - { - var result = new List<(string TargetName, string GenericArgumentScope, string GenericArgumentTypeName)> (); - foreach (var attr in GetTypeMapAssemblyTargetAttributes (reader)) { - var targetName = GetTypeMapAssemblyTargetName (reader, attr); - var memberRef = reader.GetMemberReference ((MemberReferenceHandle)attr.Constructor); - var typeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle)memberRef.Parent); - var blob = reader.GetBlobReader (typeSpec.Signature); - Assert.Equal (0x15, blob.ReadByte ()); // ELEMENT_TYPE_GENERICINST - Assert.Equal (0x12, blob.ReadByte ()); // ELEMENT_TYPE_CLASS - blob.ReadCompressedInteger (); // TypeMapAssemblyTargetAttribute`1 type - Assert.Equal (1, blob.ReadCompressedInteger ()); - Assert.Equal (0x12, blob.ReadByte ()); // ELEMENT_TYPE_CLASS - var targetType = DecodeTypeDefOrRefOrSpec (blob.ReadCompressedInteger ()); - result.Add ((targetName, GetResolutionScopeName (reader, targetType), GetTypeName (reader, targetType))); - } - return result; - } - static string GetTypeMapAssemblyTargetName (MetadataReader reader, CustomAttribute attr) { var blob = reader.GetBlobReader (attr.Value); @@ -294,17 +256,6 @@ static string GetResolutionScopeName (MetadataReader reader, EntityHandle handle }; } - static string GetTypeName (MetadataReader reader, EntityHandle handle) - { - if (handle.Kind == HandleKind.TypeDefinition) { - return reader.GetString (reader.GetTypeDefinition ((TypeDefinitionHandle)handle).Name); - } - if (handle.Kind != HandleKind.TypeReference) { - throw new InvalidOperationException ($"Unexpected type handle kind: {handle.Kind}"); - } - return reader.GetString (reader.GetTypeReference ((TypeReferenceHandle)handle).Name); - } - [Theory] [InlineData (true)] [InlineData (false)] @@ -437,50 +388,6 @@ static List GetLoadStringOperands (PEReader pe, MetadataReader reader, s return result; } - [Fact] - public void Generate_MergedMode_WithArrays_ProducesValidPEAssembly () - { - using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"], - useSharedTypemapUniverse: true, maxArrayRank: 3); - using var pe = new PEReader (stream); - Assert.True (pe.HasMetadata); - } - - [Theory] - [InlineData (true)] - [InlineData (false)] - public void Generate_WithArrays_ReferencesPerAsmRankSentinels (bool useSharedTypemapUniverse) - { - using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"], - useSharedTypemapUniverse: useSharedTypemapUniverse, maxArrayRank: 2); - using var pe = new PEReader (stream); - var reader = pe.GetMetadataReader (); - - var rankRefsByAssembly = reader.TypeReferences - .Select (h => reader.GetTypeReference (h)) - .Where (t => reader.GetString (t.Name).StartsWith ("__ArrayMapRank", StringComparison.Ordinal)) - .Select (t => ( - Name: reader.GetString (t.Name), - Assembly: reader.GetString (reader.GetAssemblyReference ((AssemblyReferenceHandle) t.ResolutionScope).Name))) - .ToList (); - Assert.Contains (("__ArrayMapRank1", "_App.TypeMap"), rankRefsByAssembly); - Assert.Contains (("__ArrayMapRank2", "_App.TypeMap"), rankRefsByAssembly); - Assert.Contains (("__ArrayMapRank1", "_Mono.Android.TypeMap"), rankRefsByAssembly); - Assert.Contains (("__ArrayMapRank2", "_Mono.Android.TypeMap"), rankRefsByAssembly); - Assert.DoesNotContain (rankRefsByAssembly, r => r.Name == "__ArrayMapRank3"); - } - [Fact] - public void Generate_MergedMode_WithArrays_HasIgnoresAccessChecksToArrayAssemblies () - { - using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"], - useSharedTypemapUniverse: true, maxArrayRank: 3); - using var pe = new PEReader (stream); - var reader = pe.GetMetadataReader (); - var accessAttrs = GetIgnoresAccessChecksToValues (reader); - Assert.Contains ("Mono.Android", accessAttrs); - Assert.Contains ("_App.TypeMap", accessAttrs); - Assert.Contains ("_Mono.Android.TypeMap", accessAttrs); - } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs index 7bca6e7f0a3..00f2d1cbd34 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs @@ -16,12 +16,6 @@ static TypeMapAssemblyData BuildModel (IReadOnlyList peers, string return ModelBuilder.Build (peers, outputPath, assemblyName); } - static TypeMapAssemblyData BuildModelWithArrays (IReadOnlyList peers, string? assemblyName = null, int maxArrayRank = 3) - { - var outputPath = Path.Combine (Path.GetTempPath (), (assemblyName ?? "TestTypeMap") + ".dll"); - return ModelBuilder.Build (peers, outputPath, assemblyName, maxArrayRank); - } - public class BasicStructure { [Fact] @@ -981,413 +975,7 @@ public void Build_SameInput_ProducesDeterministicOutput () } } - public class ArrayEntries - { - [Fact] - public void Build_DefaultEmitArrayEntriesFalse_NoArrayEntries () - { - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var model = BuildModel (new [] { peer }); - - Assert.Equal (0, model.MaxArrayRank); - Assert.DoesNotContain (model.Entries, e => e.AnchorRank is not null); - } - - [Fact] - public void Build_EmitArrayEntries_SetsMaxArrayRank () - { - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var model = BuildModelWithArrays (new [] { peer }); - - Assert.Equal (3, model.MaxArrayRank); - } - - [Fact] - public void Build_EmitArrayEntries_HonoursMaxArrayRank () - { - // Caller can ask for fewer or more ranks than the default. Verifies the - // $(_AndroidTrimmableTypeMapMaxArrayRank) MSBuild property's effect. - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - - var model5 = BuildModelWithArrays (new [] { peer }, maxArrayRank: 5); - Assert.Equal (5, model5.MaxArrayRank); - var rank5Entries = model5.Entries.Where (e => e.AnchorRank is not null).ToList (); - Assert.Equal (5, rank5Entries.Count); - Assert.Equal ("_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy5, TestTypeMap", rank5Entries.Single (e => e.AnchorRank == 5).ProxyTypeReference); - Assert.Equal ("_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy5, TestTypeMap", rank5Entries.Single (e => e.AnchorRank == 5).TargetTypeReference); - - var model1 = BuildModelWithArrays (new [] { peer }, maxArrayRank: 1); - Assert.Equal (1, model1.MaxArrayRank); - Assert.Single (model1.Entries, e => e.AnchorRank is not null); - } - - [Fact] - public void Build_EmitArrayEntries_EmitsRanks1Through3 () - { - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var model = BuildModelWithArrays (new [] { peer }); - - var arrayEntries = model.Entries.Where (e => e.AnchorRank is not null).ToList (); - Assert.Equal (3, arrayEntries.Count); - Assert.Equal (new int? [] { 1, 2, 3 }, arrayEntries.Select (e => e.AnchorRank).ToArray ()); - Assert.All (arrayEntries, e => Assert.Equal ("Foo.Bar, App", e.MapKey)); - } - - [Fact] - public void Build_EmitArrayEntries_KeyIsManagedElementTypeName () - { - // No managed->JNI lookup is needed at runtime — the key is the managed element type name - // and rank is encoded by which sentinel anchor (TGroup) the entry uses. - var peer = MakeMcwPeer ("java/lang/String", "System.String", "System.Runtime"); - var model = BuildModelWithArrays (new [] { peer }); - - var arrayEntries = model.Entries.Where (e => e.AnchorRank is not null).ToList (); - Assert.All (arrayEntries, e => Assert.Equal ("System.String, System.Runtime", e.MapKey)); - Assert.All (arrayEntries, e => Assert.False (e.MapKey.StartsWith ("[", StringComparison.Ordinal))); - } - - [Fact] - public void Build_EmitArrayEntries_MapToGeneratedArrayProxy () - { - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var model = BuildModelWithArrays (new [] { peer }); - - var rank1 = model.Entries.Single (e => e.AnchorRank == 1); - Assert.Equal ("_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy1, TestTypeMap", rank1.ProxyTypeReference); - Assert.Equal ("_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy1, TestTypeMap", rank1.TargetTypeReference); - var rank2 = model.Entries.Single (e => e.AnchorRank == 2); - Assert.Equal ("_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy2, TestTypeMap", rank2.ProxyTypeReference); - Assert.Equal ("_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy2, TestTypeMap", rank2.TargetTypeReference); - var rank3 = model.Entries.Single (e => e.AnchorRank == 3); - Assert.Equal ("_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy3, TestTypeMap", rank3.ProxyTypeReference); - Assert.Equal ("_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy3, TestTypeMap", rank3.TargetTypeReference); - - Assert.Equal (3, model.ArrayProxyTypes.Count); - Assert.Equal ("Foo_Bar_ArrayProxy1", model.ArrayProxyTypes [0].TypeName); - Assert.Equal ("Foo_Bar_ArrayProxy2", model.ArrayProxyTypes [1].TypeName); - Assert.Equal ("Foo_Bar_ArrayProxy3", model.ArrayProxyTypes [2].TypeName); - } - - [Fact] - public void Build_EmitArrayEntries_AssociationsMatchGetArrayTypes () - { - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var model = BuildModelWithArrays (new [] { peer }); - - var rank1Proxy = "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy1, TestTypeMap"; - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Java.Interop.JavaObjectArray`1[[Foo.Bar, App]], Java.Interop" && - a.AliasProxyTypeReference == rank1Proxy); - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Java.Interop.JavaArray`1[[Foo.Bar, App]], Java.Interop" && - a.AliasProxyTypeReference == rank1Proxy); - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Foo.Bar[], App" && - a.AliasProxyTypeReference == rank1Proxy); - - var rank2Proxy = "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy2, TestTypeMap"; - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Java.Interop.JavaObjectArray`1[[Java.Interop.JavaObjectArray`1[[Foo.Bar, App]], Java.Interop]], Java.Interop" && - a.AliasProxyTypeReference == rank2Proxy); - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Java.Interop.JavaArray`1[[Foo.Bar, App]][], Java.Interop" && - a.AliasProxyTypeReference == rank2Proxy); - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Foo.Bar[][], App" && - a.AliasProxyTypeReference == rank2Proxy); - } - [Fact] - public void Build_EmitArrayEntries_AllConditional () - { - // 2-arg unconditional makes no sense for arrays — the trim conditioning on the - // generated array proxy is the whole point. - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var model = BuildModelWithArrays (new [] { peer }); - - foreach (var entry in model.Entries.Where (e => e.AnchorRank is not null)) { - Assert.False (entry.IsUnconditional); - Assert.NotNull (entry.TargetTypeReference); - } - } - - [Fact] - public void Build_EmitArrayEntries_OpenGenericPeer_Skipped () - { - // typeof(JavaList<>[]) is not a valid IL token. - var openGeneric = MakeMcwPeer ("java/util/ArrayList", "Android.Runtime.JavaList`1", "Mono.Android") - with { IsGenericDefinition = true }; - var model = BuildModelWithArrays (new [] { openGeneric }); - - Assert.DoesNotContain (model.Entries, e => e.AnchorRank is not null); - } - - [Fact] - public void Build_EmitArrayEntries_FrameworkPeer_Skipped () - { - var frameworkPeer = MakeMcwPeer ("android/widget/Button", "Android.Widget.Button", "Mono.Android") - with { IsFrameworkAssembly = true, GenerateArrayEntries = false }; - var model = BuildModelWithArrays (new [] { frameworkPeer }); - - Assert.DoesNotContain (model.Entries, e => e.AnchorRank is not null); - } - - [Fact] - public void Build_EmitArrayEntries_ReferencedFrameworkPeer_Emitted () - { - var frameworkPeer = MakeMcwPeer ("android/widget/Button", "Android.Widget.Button", "Mono.Android") - with { IsFrameworkAssembly = true, GenerateArrayEntries = true }; - var model = BuildModelWithArrays (new [] { frameworkPeer }); - - var arrayEntries = model.Entries.Where (e => e.AnchorRank is not null).ToList (); - Assert.Equal (3, arrayEntries.Count); - Assert.All (arrayEntries, e => Assert.Equal ("Android.Widget.Button, Mono.Android", e.MapKey)); - } - - [Fact] - public void Build_EmitArrayEntries_AliasGroup_EmitsPerManagedType () - { - var peers = new List { - MakeMcwPeer ("java/lang/Object", "Java.Interop.JavaObject", "Java.Interop") - with { IsFrameworkAssembly = true, GenerateArrayEntries = false }, - MakeMcwPeer ("java/lang/Object", "Java.Lang.Object", "Mono.Android") - with { IsFrameworkAssembly = true, GenerateArrayEntries = true }, - }; - var model = BuildModelWithArrays (peers); - - var arrayEntries = model.Entries.Where (e => e.AnchorRank is not null).ToList (); - Assert.Equal (3, arrayEntries.Count); - Assert.Contains (arrayEntries, e => - e.MapKey == "Java.Lang.Object, Mono.Android" && - e.AnchorRank == 1 && - e.ProxyTypeReference == "_TypeMap.ArrayProxies.Java_Lang_Object_ArrayProxy1, TestTypeMap"); - Assert.Contains (arrayEntries, e => - e.MapKey == "Java.Lang.Object, Mono.Android" && - e.AnchorRank == 2 && - e.ProxyTypeReference == "_TypeMap.ArrayProxies.Java_Lang_Object_ArrayProxy2, TestTypeMap"); - Assert.DoesNotContain (arrayEntries, e => e.MapKey == "Java.Interop.JavaObject, Java.Interop"); - } - - [Theory] - [InlineData ("Z")] - [InlineData ("B")] - [InlineData ("C")] - [InlineData ("S")] - [InlineData ("I")] - [InlineData ("J")] - [InlineData ("F")] - [InlineData ("D")] - public void Build_EmitArrayEntries_PrimitiveJniKeyword_Skipped (string jniKeyword) - { - // Primitive JNI keyword keys are handled by the legacy - // JniRuntime.JniTypeManager.GetPrimitiveArrayTypesForSimpleReference path. - // Emitting array entries here would shadow that built-in handling. - var peer = MakeMcwPeer (jniKeyword, "FakePrimitive.Wrapper", "App"); - var model = BuildModelWithArrays (new [] { peer }); - - Assert.DoesNotContain (model.Entries, e => e.AnchorRank is not null); - } - - [Fact] - public void Build_EmitArrayEntries_PrimitiveEntries_SynthesizedForJavaInteropAssembly () - { - var peer = MakeMcwPeer ("java/lang/Object", "Java.Lang.Object", "Java.Interop"); - var model = BuildModelWithArrays (new [] { peer }, assemblyName: "_Java.Interop.TypeMap"); - - var primitiveEntries = model.Entries - .Where (e => e.MapKey.StartsWith ("System.", StringComparison.Ordinal) && e.AnchorRank is not null) - .ToList (); - Assert.Equal (36, primitiveEntries.Count); - - var sbyteRank1 = primitiveEntries.Single (e => e.MapKey == "System.SByte, System.Runtime" && e.AnchorRank == 1); - Assert.Equal ("_TypeMap.ArrayProxies.Primitive_SByte_ArrayProxy1, _Java.Interop.TypeMap", sbyteRank1.ProxyTypeReference); - Assert.Equal ("_TypeMap.ArrayProxies.Primitive_SByte_ArrayProxy1, _Java.Interop.TypeMap", sbyteRank1.TargetTypeReference); - Assert.False (sbyteRank1.IsUnconditional); - - var sbyteRank2 = primitiveEntries.Single (e => e.MapKey == "System.SByte, System.Runtime" && e.AnchorRank == 2); - Assert.Equal ("_TypeMap.ArrayProxies.Primitive_SByte_ArrayProxy2, _Java.Interop.TypeMap", sbyteRank2.TargetTypeReference); - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Java.Interop.JavaArray`1[[System.SByte, System.Runtime]], Java.Interop" && - a.AliasProxyTypeReference == sbyteRank1.ProxyTypeReference); - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Java.Interop.JavaPrimitiveArray`1[[System.SByte, System.Runtime]], Java.Interop" && - a.AliasProxyTypeReference == sbyteRank1.ProxyTypeReference); - Assert.Contains (model.Associations, a => - a.SourceTypeReference == "Java.Interop.JavaSByteArray, Java.Interop" && - a.AliasProxyTypeReference == sbyteRank1.ProxyTypeReference); - - foreach (var (mapKey, proxyName, arrayTypeReference, concreteArrayTypeReference) in new [] { - ("System.Byte, System.Runtime", "Byte", "System.Byte[], System.Runtime", "Java.Interop.JavaSByteArray, Java.Interop"), - ("System.UInt16, System.Runtime", "UInt16", "System.UInt16[], System.Runtime", "Java.Interop.JavaInt16Array, Java.Interop"), - ("System.UInt32, System.Runtime", "UInt32", "System.UInt32[], System.Runtime", "Java.Interop.JavaInt32Array, Java.Interop"), - ("System.UInt64, System.Runtime", "UInt64", "System.UInt64[], System.Runtime", "Java.Interop.JavaInt64Array, Java.Interop"), - }) { - var rank1 = primitiveEntries.Single (e => e.MapKey == mapKey && e.AnchorRank == 1); - Assert.Equal ($"_TypeMap.ArrayProxies.Primitive_{proxyName}_ArrayProxy1, _Java.Interop.TypeMap", rank1.ProxyTypeReference); - var rank2 = primitiveEntries.Single (e => e.MapKey == mapKey && e.AnchorRank == 2); - Assert.Equal ($"_TypeMap.ArrayProxies.Primitive_{proxyName}_ArrayProxy2, _Java.Interop.TypeMap", rank2.TargetTypeReference); - Assert.Contains (model.Associations, a => - a.SourceTypeReference == arrayTypeReference && - a.AliasProxyTypeReference == rank1.ProxyTypeReference); - Assert.DoesNotContain (model.Associations, a => - a.SourceTypeReference == concreteArrayTypeReference && - a.AliasProxyTypeReference == rank1.ProxyTypeReference); - } - } - - [Fact] - public void Build_EmitArrayEntries_PrimitiveEntries_NotDuplicatedInOtherAssemblies () - { - var peer = MakeMcwPeer ("java/lang/Object", "Java.Lang.Object", "Java.Interop"); - var model = BuildModelWithArrays (new [] { peer }, assemblyName: "_Mono.Android.TypeMap"); - - Assert.DoesNotContain (model.Entries, e => e.MapKey.StartsWith ("System.", StringComparison.Ordinal) && e.AnchorRank is not null); - Assert.DoesNotContain (model.Associations, a => a.SourceTypeReference == "System.SByte[], System.Runtime"); - } - - [Fact] - public void Build_EmitArrayEntries_MultiplePeers_GetIndependentTrios () - { - var peers = new List { - MakeMcwPeer ("foo/A", "Foo.A", "App"), - MakeMcwPeer ("foo/B", "Foo.B", "App"), - }; - var model = BuildModelWithArrays (peers); - - var arrayEntries = model.Entries.Where (e => e.AnchorRank is not null).ToList (); - Assert.Equal (6, arrayEntries.Count); // 2 peers × 3 ranks - - foreach (var managedKey in new [] { "Foo.A, App", "Foo.B, App" }) { - var perPeer = arrayEntries.Where (e => e.MapKey == managedKey).OrderBy (e => e.AnchorRank).ToList (); - Assert.Equal (3, perPeer.Count); - Assert.Equal (new int? [] { 1, 2, 3 }, perPeer.Select (e => e.AnchorRank).ToArray ()); - } - } - } - - public class ArrayEntriesPeBlob - { - [Fact] - public void FullPipeline_ArrayEntries_DefinesInternalRankAnchors () - { - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var outputPath = Path.Combine (Path.GetTempPath (), "ArrSentinels.dll"); - var model = ModelBuilder.Build (new [] { peer }, outputPath, "ArrSentinels", maxArrayRank: 3); - Assert.Equal (3, model.MaxArrayRank); - - EmitAndVerify (model, "ArrSentinels", (pe, reader) => { - var typeDefNames = reader.TypeDefinitions - .Select (h => reader.GetString (reader.GetTypeDefinition (h).Name)) - .ToHashSet (StringComparer.Ordinal); - Assert.Contains ("__ArrayMapRank1", typeDefNames); - Assert.Contains ("__ArrayMapRank2", typeDefNames); - Assert.Contains ("__ArrayMapRank3", typeDefNames); - - var rankTypeDefs = reader.TypeDefinitions - .Select (h => reader.GetTypeDefinition (h)) - .Where (t => reader.GetString (t.Name).StartsWith ("__ArrayMapRank", StringComparison.Ordinal)) - .ToList (); - Assert.All (rankTypeDefs, t => Assert.Equal ( - System.Reflection.TypeAttributes.NotPublic, - t.Attributes & System.Reflection.TypeAttributes.VisibilityMask)); - - var rankTypeRefs = reader.TypeReferences - .Select (h => reader.GetTypeReference (h)) - .Where (t => reader.GetString (t.Name).StartsWith ("__ArrayMapRank", StringComparison.Ordinal)) - .Select (t => reader.GetString (t.Name)) - .ToHashSet (StringComparer.Ordinal); - Assert.Empty (rankTypeRefs); - }); - } - - [Fact] - public void FullPipeline_NoArrayEntries_DoesNotReferenceRankAnchors () - { - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var outputPath = Path.Combine (Path.GetTempPath (), "NoArrSentinels.dll"); - var model = ModelBuilder.Build (new [] { peer }, outputPath, "NoArrSentinels"); - Assert.Equal (0, model.MaxArrayRank); - - EmitAndVerify (model, "NoArrSentinels", (pe, reader) => { - var typeDefNames = reader.TypeDefinitions - .Select (h => reader.GetString (reader.GetTypeDefinition (h).Name)) - .ToHashSet (StringComparer.Ordinal); - Assert.DoesNotContain ("__ArrayMapRank1", typeDefNames); - Assert.DoesNotContain ("__ArrayMapRank2", typeDefNames); - Assert.DoesNotContain ("__ArrayMapRank3", typeDefNames); - - var typeRefNames = reader.TypeReferences - .Select (h => reader.GetString (reader.GetTypeReference (h).Name)) - .ToHashSet (StringComparer.Ordinal); - Assert.DoesNotContain ("__ArrayMapRank1", typeRefNames); - Assert.DoesNotContain ("__ArrayMapRank2", typeRefNames); - Assert.DoesNotContain ("__ArrayMapRank3", typeRefNames); - }); - } - - [Fact] - public void FullPipeline_PrimitiveAliasArrayEntries_EmitWithoutConcreteArrayType () - { - var peer = MakeMcwPeer ("java/lang/Object", "Java.Lang.Object", "Java.Interop"); - var model = BuildModelWithArrays (new [] { peer }, assemblyName: "_Java.Interop.TypeMap"); - - EmitAndVerify (model, "_Java.Interop.TypeMap", (pe, reader) => { - var typeDefNames = reader.TypeDefinitions - .Select (h => reader.GetString (reader.GetTypeDefinition (h).Name)) - .ToHashSet (StringComparer.Ordinal); - Assert.Contains ("Primitive_Byte_ArrayProxy1", typeDefNames); - Assert.Contains ("Primitive_Byte_ArrayProxy2", typeDefNames); - Assert.Contains ("Primitive_UInt32_ArrayProxy1", typeDefNames); - - var assocAttrs = ReadAllTypeMapAssociationAttributeBlobs (reader); - Assert.Contains (assocAttrs, a => - a.sourceRef == "System.Byte[], System.Runtime" && - a.proxyRef == "_TypeMap.ArrayProxies.Primitive_Byte_ArrayProxy1, _Java.Interop.TypeMap"); - Assert.Contains (assocAttrs, a => - a.sourceRef == "System.UInt32[], System.Runtime" && - a.proxyRef == "_TypeMap.ArrayProxies.Primitive_UInt32_ArrayProxy1, _Java.Interop.TypeMap"); - Assert.DoesNotContain (assocAttrs, a => - a.sourceRef == "Java.Interop.JavaSByteArray, Java.Interop" && - a.proxyRef is not null && - a.proxyRef.Contains ("Primitive_Byte", StringComparison.Ordinal)); - }); - } - - [Fact] - public void FullPipeline_ArrayEntries_AttributeBlobsRoundTrip () - { - var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); - var outputPath = Path.Combine (Path.GetTempPath (), "ArrBlobs.dll"); - var model = ModelBuilder.Build (new [] { peer }, outputPath, "ArrBlobs", maxArrayRank: 3); - - EmitAndVerify (model, "ArrBlobs", (pe, reader) => { - var arrayAttrs = ReadAllTypeMapAttributeBlobs (reader) - .Select (a => (managedName: a.mapKey, a.proxyRef, a.targetRef)) - .ToList (); - - // Three array entries should round-trip with the same managed key + generated array proxy refs. - Assert.Contains (arrayAttrs, a => a.managedName == "Foo.Bar, App" && - a.proxyRef == "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy1, ArrBlobs" && - a.targetRef == "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy1, ArrBlobs"); - Assert.Contains (arrayAttrs, a => a.managedName == "Foo.Bar, App" && - a.proxyRef == "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy2, ArrBlobs" && - a.targetRef == "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy2, ArrBlobs"); - Assert.Contains (arrayAttrs, a => a.managedName == "Foo.Bar, App" && - a.proxyRef == "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy3, ArrBlobs" && - a.targetRef == "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy3, ArrBlobs"); - - var assocAttrs = ReadAllTypeMapAssociationAttributeBlobs (reader); - Assert.Contains (assocAttrs, a => - a.groupName.Contains ("__ArrayMapRank1", StringComparison.Ordinal) && - a.sourceRef == "Java.Interop.JavaArray`1[[Foo.Bar, App]], Java.Interop" && - a.proxyRef == "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy1, ArrBlobs"); - Assert.Contains (assocAttrs, a => - a.groupName.Contains ("__ArrayMapRank1", StringComparison.Ordinal) && - a.sourceRef == "Java.Interop.JavaObjectArray`1[[Foo.Bar, App]], Java.Interop" && - a.proxyRef == "_TypeMap.ArrayProxies.Foo_Bar_ArrayProxy1, ArrBlobs"); - }); - } - } static void EmitAndVerify (TypeMapAssemblyData model, string assemblyName, Action verify) { @@ -1469,35 +1057,6 @@ static void EmitAndVerify (TypeMapAssemblyData model, string assemblyName, Actio return result; } - static List<(string groupName, string? sourceRef, string? proxyRef)> ReadAllTypeMapAssociationAttributeBlobs (MetadataReader reader) - { - var result = new List<(string, string?, string?)> (); - var asmAttrs = reader.GetCustomAttributes (EntityHandle.AssemblyDefinition); - foreach (var attrHandle in asmAttrs) { - var attr = reader.GetCustomAttribute (attrHandle); - if (attr.Constructor.Kind != HandleKind.MemberReference) - continue; - - var ctor = reader.GetMemberReference ((MemberReferenceHandle) attr.Constructor); - if (ctor.Parent.Kind != HandleKind.TypeSpecification) - continue; - - var parent = reader.GetTypeSpecification ((TypeSpecificationHandle) ctor.Parent); - var parentName = parent.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null); - if (!parentName.StartsWith ("System.Runtime.InteropServices.TypeMapAssociationAttribute`1", StringComparison.Ordinal)) { - continue; - } - - var blobReader = reader.GetBlobReader (attr.Value); - ushort prolog = blobReader.ReadUInt16 (); - if (prolog != 1) - continue; - - result.Add ((parentName, blobReader.ReadSerializedString (), blobReader.ReadSerializedString ())); - } - return result; - } - public class UcoMethods { [Fact] diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs index 00c8be4e5bb..f8c9192146b 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs @@ -57,7 +57,6 @@ public void Scan_MarksFrameworkAssemblyPeers () Assert.NotEmpty (peers); Assert.All (peers, p => Assert.True (p.IsFrameworkAssembly, $"{p.ManagedTypeName} should be marked as a framework peer.")); - Assert.All (peers, p => Assert.False (p.GenerateArrayEntries, $"{p.ManagedTypeName} should not emit array entries unless referenced from a non-framework assembly.")); } [Fact] diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs index e2abf5489b1..92a87a99a62 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs @@ -265,44 +265,8 @@ public void TrimmableJavaProxyObject_ObjectMethodsUseJavaIdentitySemantics () } } - [Test] - public void TryGetArrayProxy_ObjectLeaf_ReturnsAllRankTypes () - { - AssumeTrimmableTypeMapEnabled (); - AssumeGeneratedArrayProxiesEnabled (); - - Assert.IsTrue (TrimmableTypeMap.Instance.TryGetArrayProxy (typeof (Java.Lang.Object), additionalRank: 1, out var objectArrayProxy)); - CollectionAssert.Contains (objectArrayProxy.GetArrayTypes (), typeof (JavaObjectArray)); - CollectionAssert.Contains (objectArrayProxy.GetArrayTypes (), typeof (Java.Interop.JavaArray)); - CollectionAssert.Contains (objectArrayProxy.GetArrayTypes (), typeof (Java.Lang.Object[])); - - Assert.IsTrue (TrimmableTypeMap.Instance.TryGetArrayProxy (typeof (Java.Lang.Object), additionalRank: 2, out var jaggedObjectArrayProxy)); - CollectionAssert.Contains (jaggedObjectArrayProxy.GetArrayTypes (), typeof (JavaObjectArray>)); - CollectionAssert.Contains (jaggedObjectArrayProxy.GetArrayTypes (), typeof (Java.Interop.JavaArray[])); - CollectionAssert.Contains (jaggedObjectArrayProxy.GetArrayTypes (), typeof (Java.Lang.Object[][])); - } - - [Test] - public void TryGetArrayProxy_PrimitiveLeaf_ReturnsAllRankTypes () - { - AssumeTrimmableTypeMapEnabled (); - AssumeGeneratedArrayProxiesEnabled (); - - Assert.IsTrue (TrimmableTypeMap.Instance.TryGetArrayProxy (typeof (sbyte), additionalRank: 1, out var sbyteArrayProxy)); - CollectionAssert.Contains (sbyteArrayProxy.GetArrayTypes (), typeof (sbyte[])); - CollectionAssert.Contains (sbyteArrayProxy.GetArrayTypes (), typeof (Java.Interop.JavaArray)); - CollectionAssert.Contains (sbyteArrayProxy.GetArrayTypes (), typeof (JavaPrimitiveArray)); - CollectionAssert.Contains (sbyteArrayProxy.GetArrayTypes (), typeof (JavaSByteArray)); - - Assert.IsTrue (TrimmableTypeMap.Instance.TryGetArrayProxy (typeof (sbyte), additionalRank: 2, out var jaggedSbyteArrayProxy)); - CollectionAssert.Contains (jaggedSbyteArrayProxy.GetArrayTypes (), typeof (sbyte[][])); - CollectionAssert.Contains (jaggedSbyteArrayProxy.GetArrayTypes (), typeof (JavaObjectArray>)); - CollectionAssert.Contains (jaggedSbyteArrayProxy.GetArrayTypes (), typeof (JavaObjectArray)); - } - // Regression: the runtime replacement (BuildRuntimeArrayTypes) must return the full set of array - // and wrapper types, not just T[]. The TryGetArrayProxy_* tests above only cover the legacy - // generated-proxy path, and the marshaling round-trip tests only need T[]. This is a pure + // and wrapper types, not just T[]. The marshaling round-trip tests only need T[]. This is a pure // function, so it runs on every config. [Test] public void BuildRuntimeArrayTypes_ReferenceLeaf_ReturnsProxyContract () @@ -392,13 +356,6 @@ static IReadOnlyList GetStaticMethodFallbackTypes (TestableTrimmableType return fallbacks ?? throw new InvalidOperationException ("Expected fallback types."); } - static void AssumeGeneratedArrayProxiesEnabled () - { - if (!RuntimeFeature.IsNativeAotRuntime && System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported) { - Assert.Ignore ("Generated array proxies are only emitted when dynamic code is unavailable."); - } - } - static void AssumeTrimmableTypeMapEnabled () { if (!RuntimeFeature.TrimmableTypeMap) { From b90b1b9cb77f5f860b04e77e0dd3318915061f63 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 22:41:18 +0200 Subject: [PATCH 2/9] [TrimmableTypeMap] Drop DynamicallyAccessedMembers on JavaPeerProxy With GetContainerFactory() removed, JavaPeerProxy no longer constructs T (it only passes typeof(T) to the base), so the constructor DynamicallyAccessedMembers annotation on T is no longer needed. Remove the Constructors const and the now-unused System.Diagnostics.CodeAnalysis using. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e --- src/Mono.Android/Java.Interop/JavaPeerProxy.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 5904af40ee2..01e52b172cd 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -1,7 +1,6 @@ #nullable enable using System; -using System.Diagnostics.CodeAnalysis; using Android.Runtime; namespace Java.Interop @@ -125,11 +124,9 @@ static bool IsActivationPeer (IJavaPeerable peer) /// /// The target .NET peer type this proxy represents. [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] - public abstract class JavaPeerProxy<[DynamicallyAccessedMembers (Constructors)] T> + public abstract class JavaPeerProxy : JavaPeerProxy where T : class, IJavaPeerable { - const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; - protected JavaPeerProxy (string jniName) : base (jniName, typeof (T)) { From 65cab6e9ddb5c80c3f59cce90289bfbe2f05e61d Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 22:48:24 +0200 Subject: [PATCH 3/9] [TrimmableTypeMap] Remove JavaPeerProxy, use non-generic base for all proxies JavaPeerProxy only existed to auto-populate TargetType from typeof(T) and (previously) to expose GetContainerFactory()/root T's constructors. With GetContainerFactory() and the DynamicallyAccessedMembers annotation on T gone, the generic base carries no remaining value: peers are always activated through the generated CreateInstance / InvokerType, never by reflecting over T. - Remove the JavaPeerProxy class (and its PublicAPI entries). - Generator now emits every proxy (concrete class, interface, open generic) deriving from the non-generic JavaPeerProxy base with the (string, Type) ctor, removing the JavaPeerProxy`1 type reference and the base-type branch. - Update model/test comments and the base-ctor-chain assertions accordingly. Validated: Microsoft.Android.Sdk.TrimmableTypeMap builds; 581 unit tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e --- .../Generator/Model/TypeMapAssemblyData.cs | 6 +- .../Generator/TypeMapAssemblyEmitter.cs | 64 +++++-------------- .../Java.Interop/JavaPeerProxy.cs | 15 ----- .../PublicAPI/API-37/PublicAPI.Unshipped.txt | 2 - .../TypeMapAssemblyGeneratorTests.cs | 51 +++++---------- .../Java.Interop/JavaPeerProxyTests.cs | 19 ------ 6 files changed, 35 insertions(+), 122 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index bd659cb994b..163aa66e994 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -135,11 +135,7 @@ sealed class JavaPeerProxyData public bool IsGenericDefinition { get; init; } /// - /// True if the proxied peer type is a Java interface. Interfaces have no constructors, so - /// the proxy must derive from the non-generic JavaPeerProxy base instead of - /// JavaPeerProxy<T>: closing the generic (whose T is annotated with - /// [DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)]) over an - /// interface makes ILC fail to load the type (TypeLoadException). Instances are still created + /// True if the proxied peer type is a Java interface. Instances are created /// from in CreateInstance. /// public bool IsInterface { get; init; } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 8aeb5c67b7d..68ff7dc961a 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -78,7 +78,6 @@ sealed class TypeMapAssemblyEmitter AssemblyReferenceHandle _javaInteropRef; - TypeReferenceHandle _javaPeerProxyRef; TypeReferenceHandle _javaPeerProxyNonGenericRef; TypeReferenceHandle _iJavaPeerableRef; TypeReferenceHandle _jniHandleOwnershipRef; @@ -272,8 +271,6 @@ void Visit (JavaPeerProxyData proxy) void EmitTypeReferences () { var metadata = _pe.Metadata; - _javaPeerProxyRef = metadata.AddTypeReference (_pe.MonoAndroidRef, - metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy`1")); _javaPeerProxyNonGenericRef = metadata.AddTypeReference (_pe.MonoAndroidRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy")); _iJavaPeerableRef = metadata.AddTypeReference (_javaInteropRef, @@ -663,40 +660,19 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary` with an open T would force the CLR to resolve a generic - // argument that isn't available via the TypeMapLazyDictionary loader, and using a - // placeholder like `Java.Lang.Object` leaks an incorrect TargetType into the typemap. - // The non-generic base takes `targetType` as a ctor parameter, so we can pass the real - // open-generic type token (a TypeRef, not a closed TypeSpec) and keep TargetType correct. - // - // Interface peers also use the non-generic base: `JavaPeerProxy` annotates T with - // [DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)], and closing it - // over an interface (which has no constructors) makes ILC fail to load the type - // (TypeLoadException: "Failed to load type 'JavaPeerProxy`1'"). The peer is - // still activated from its InvokerType in CreateInstance, so behaviour is unchanged. - bool useNonGenericBase = proxy.IsGenericDefinition || proxy.IsInterface; - EntityHandle proxyBaseType; - MemberReferenceHandle baseCtorRef; - if (useNonGenericBase) { - proxyBaseType = _javaPeerProxyNonGenericRef; - baseCtorRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, ".ctor", - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, - rt => rt.Void (), - p => { - p.AddParameter ().Type ().String (); - p.AddParameter ().Type ().Type (_systemTypeRef, false); - })); - } else { - var genericProxyBase = _pe.MakeGenericTypeSpec (_javaPeerProxyRef, targetTypeRef); - proxyBaseType = genericProxyBase; - baseCtorRef = _pe.AddMemberRef (genericProxyBase, ".ctor", - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1, - rt => rt.Void (), - p => { - p.AddParameter ().Type ().String (); - })); - } + // All proxies derive from the non-generic `JavaPeerProxy` abstract base, which takes + // `targetType` as a ctor parameter. This keeps TargetType correct for every shape: + // concrete peers, open generic definitions (whose T isn't available via the + // TypeMapLazyDictionary loader), and interfaces (which have no constructors). Peers are + // activated from their generated CreateInstance / InvokerType, never by reflecting over T. + var proxyBaseType = (EntityHandle) _javaPeerProxyNonGenericRef; + var baseCtorRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, ".ctor", + sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, + rt => rt.Void (), + p => { + p.AddParameter ().Type ().String (); + p.AddParameter ().Type ().Type (_systemTypeRef, false); + })); var typeDefHandle = metadata.AddTypeDefinition ( TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, @@ -710,22 +686,16 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }), encoder => { encoder.OpCode (ILOpCode.Ldarg_0); encoder.LoadString (metadata.GetOrAddUserString (proxy.JniName)); - if (useNonGenericBase) { - // Non-generic base ctor signature: (string, Type). Push the - // target type (open-generic definition or interface) as the second argument. - encoder.LoadToken (targetTypeRef); - encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); - } - encoder.Call (baseCtorRef, parameterCount: useNonGenericBase ? 2 : 1, isInstance: true); + encoder.LoadToken (targetTypeRef); + encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (baseCtorRef, parameterCount: 2, isInstance: true); encoder.Return (); }); diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 01e52b172cd..f6d966df235 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -117,19 +117,4 @@ static bool IsActivationPeer (IJavaPeerable peer) || (state & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable; } } - - /// - /// Generic base for generated proxy types. Provides - /// automatically from the type parameter. - /// - /// The target .NET peer type this proxy represents. - [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] - public abstract class JavaPeerProxy - : JavaPeerProxy where T : class, IJavaPeerable - { - protected JavaPeerProxy (string jniName) - : base (jniName, typeof (T)) - { - } - } } diff --git a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt index 535fcd940b5..7fdf466ee6b 100644 --- a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt @@ -2353,8 +2353,6 @@ Java.Interop.JavaPeerProxy Java.Interop.JavaPeerProxy.JavaPeerProxy(string! jniName, System.Type! targetType) -> void Java.Interop.JavaPeerProxy.JniName.get -> string! Java.Interop.JavaPeerProxy.TargetType.get -> System.Type! -Java.Interop.JavaPeerProxy -Java.Interop.JavaPeerProxy.JavaPeerProxy(string! jniName) -> void Java.Lang.ICharSequence.GetChars(int srcBegin, int srcEnd, char[]! dst, int dstBegin) -> void Java.Lang.IllegalCallerException Java.Lang.IllegalCallerException.IllegalCallerException() -> void diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index 100fc5a28c6..d82f6106eed 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -187,7 +187,7 @@ public void Generate_ProxyType_HasCtorAndCreateInstance () } [Fact] - public void Generate_ProxyType_UsesGenericJavaPeerProxyBase () + public void Generate_ProxyType_UsesNonGenericJavaPeerProxyBase () { var peers = ScanFixtures (); using var stream = GenerateAssembly (peers); @@ -199,39 +199,23 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase () .ToList (); Assert.NotEmpty (proxyTypes); + // Every proxy — concrete class, interface, or open generic definition — derives from + // the non-generic `JavaPeerProxy` base (a plain TypeReference), taking `targetType` + // as a ctor parameter. Assert.All (proxyTypes, proxyType => { - switch (proxyType.BaseType.Kind) { - case HandleKind.TypeSpecification: - // Concrete (constructible) target types derive from the closed `JavaPeerProxy`. - var baseTypeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) proxyType.BaseType); - var baseTypeName = baseTypeSpec.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null); - Assert.StartsWith ("Java.Interop.JavaPeerProxy`1<", baseTypeName, StringComparison.Ordinal); - break; - case HandleKind.TypeReference: - // Open generic definitions and interfaces derive from the non-generic `JavaPeerProxy`. - var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) proxyType.BaseType); - Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace)); - Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name)); - break; - default: - Assert.Fail ($"Unexpected BaseType handle kind: {proxyType.BaseType.Kind}"); - break; - } + Assert.Equal (HandleKind.TypeReference, proxyType.BaseType.Kind); + var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) proxyType.BaseType); + Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace)); + Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name)); }); - - var objectProxy = proxyTypes.First (t => reader.GetString (t.Name) == "Java_Lang_Object_Proxy"); - var objectProxyBaseType = reader.GetTypeSpecification ((TypeSpecificationHandle) objectProxy.BaseType); - Assert.Equal ("Java.Interop.JavaPeerProxy`1", - objectProxyBaseType.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null)); } [Fact] public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase () { - // JavaPeerProxy annotates T with [DynamicallyAccessedMembers(Constructors)]. Closing it - // over an interface (which has no constructors) makes ILC fail to load the closed generic - // type ("Failed to load type 'JavaPeerProxy`1'"). Interface proxies must - // therefore derive from the non-generic JavaPeerProxy base (a plain TypeReference). + // Interface proxies derive from the non-generic JavaPeerProxy base (a plain + // TypeReference), like every other proxy. Interfaces have no constructors and are + // activated from their InvokerType in CreateInstance. var peers = ScanFixtures (); using var stream = GenerateAssembly (peers); using var pe = new PEReader (stream); @@ -249,14 +233,13 @@ public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase () } // Regression test: decode the emitted proxy `.ctor` *body* (not just member presence) - // and verify it chains to the correct base constructor. Concrete (constructible) proxies - // derive from `JavaPeerProxy` and call the single-arg base ctor `(string)`; interface - // and open-generic proxies derive from the non-generic `JavaPeerProxy` and call the - // two-arg base ctor `(string, Type)` — pushing the target type via `Type.GetTypeFromHandle`. - // A base-ctor arity/token mismatch here would pass metadata inspection but blow up as an - // InvalidProgramException/TypeLoadException on the CoreCLR device legs, so assert it directly. + // and verify it chains to the correct base constructor. Every proxy derives from the + // non-generic `JavaPeerProxy` and calls the two-arg base ctor `(string, Type)` — pushing + // the target type via `Type.GetTypeFromHandle`. A base-ctor arity/token mismatch here would + // pass metadata inspection but blow up as an InvalidProgramException/TypeLoadException on the + // CoreCLR device legs, so assert it directly. [Theory] - [InlineData ("Java_Lang_Object_Proxy", 1, false)] // concrete class -> generic base (string) + [InlineData ("Java_Lang_Object_Proxy", 2, true)] // concrete class -> non-generic base (string, Type) [InlineData ("Android_Views_IOnClickListener_Proxy", 2, true)] // interface -> non-generic base (string, Type) [InlineData ("MyApp_Generic_GenericHolder_1_Proxy", 2, true)] // open generic -> non-generic base (string, Type) public void Generate_ProxyCtor_ChainsToExpectedBaseConstructor (string proxyTypeName, int expectedBaseCtorArity, bool expectsGetTypeFromHandle) diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaPeerProxyTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaPeerProxyTests.cs index b83138acb70..bedd63871ff 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaPeerProxyTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaPeerProxyTests.cs @@ -19,15 +19,6 @@ public void Constructor_StoresJniNameAndTargetType () Assert.AreEqual ("custom/ExplicitName", proxy.JniName); Assert.AreEqual (typeof (ProxyTestPeer), proxy.TargetType); } - - [Test] - public void GenericConstructor_StoresJniNameAndTargetType () - { - var proxy = new GenericProxy (); - - Assert.AreEqual ("custom/GenericProxy", proxy.JniName); - Assert.AreEqual (typeof (ProxyTestPeer), proxy.TargetType); - } } [Register ("test/ProxyTestPeer", DoNotGenerateAcw = true)] @@ -52,14 +43,4 @@ public ExplicitNameProxy () public override IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer) => null; } - - sealed class GenericProxy : JavaPeerProxy - { - public GenericProxy () - : base ("custom/GenericProxy") - { - } - - public override IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer) => null; - } } From 08f188364ad21843dadfb09ab65e48c8f69d3ccb Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 22:58:49 +0200 Subject: [PATCH 4/9] [TrimmableTypeMap] Fix stale MapKey doc comment referencing array proxy entries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e --- .../Generator/Model/TypeMapAssemblyData.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index 163aa66e994..642e7a0f88a 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -58,8 +58,8 @@ sealed class TypeMapAssemblyData sealed record TypeMapAttributeData { /// - /// Type map key, e.g., "android/app/Activity" for peer entries or - /// "Android.App.Activity, Mono.Android" for array proxy entries. + /// Type map key — the JNI name, e.g., "android/app/Activity", or an indexed alias + /// key such as "java/util/Collection[0]". /// public required string MapKey { get; init; } From 6014addad8882a3ad16eda3a31d773ac4c2eaf31 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 23:06:22 +0200 Subject: [PATCH 5/9] [TrimmableTypeMap] Drop "non-generic" wording for the JavaPeerProxy base There is only one JavaPeerProxy base class and the generic variant was never shipped, so comments, the emitter field name (_javaPeerProxyNonGenericRef -> _javaPeerProxyRef), and test names/InlineData no longer need to describe the base as "non-generic". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e --- .../Generator/TypeMapAssemblyEmitter.cs | 26 +++++++++--------- .../TypeMapAssemblyGeneratorTests.cs | 27 +++++++++---------- .../Generator/TypeMapModelBuilderTests.cs | 4 +-- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 68ff7dc961a..367760fb337 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -78,7 +78,7 @@ sealed class TypeMapAssemblyEmitter AssemblyReferenceHandle _javaInteropRef; - TypeReferenceHandle _javaPeerProxyNonGenericRef; + TypeReferenceHandle _javaPeerProxyRef; TypeReferenceHandle _iJavaPeerableRef; TypeReferenceHandle _jniHandleOwnershipRef; TypeReferenceHandle _jniObjectReferenceRef; @@ -271,7 +271,7 @@ void Visit (JavaPeerProxyData proxy) void EmitTypeReferences () { var metadata = _pe.Metadata; - _javaPeerProxyNonGenericRef = metadata.AddTypeReference (_pe.MonoAndroidRef, + _javaPeerProxyRef = metadata.AddTypeReference (_pe.MonoAndroidRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy")); _iJavaPeerableRef = metadata.AddTypeReference (_javaInteropRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("IJavaPeerable")); @@ -414,17 +414,17 @@ void EmitMemberReferences () })); // JavaPeerProxy.ShouldSkipActivation(IntPtr) -> bool (static method) - _shouldSkipActivationRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "ShouldSkipActivation", + _shouldSkipActivationRef = _pe.AddMemberRef (_javaPeerProxyRef, "ShouldSkipActivation", sig => sig.MethodSignature ().Parameters (1, rt => rt.Type ().Boolean (), p => { p.AddParameter ().Type ().IntPtr (); })); - _getActivationPeerRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "GetActivationPeer", + _getActivationPeerRef = _pe.AddMemberRef (_javaPeerProxyRef, "GetActivationPeer", sig => sig.MethodSignature ().Parameters (1, rt => rt.Type ().Type (_iJavaPeerableRef, false), p => { p.AddParameter ().Type ().IntPtr (); })); - _setActivationPeerReferenceRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "SetActivationPeerReference", + _setActivationPeerReferenceRef = _pe.AddMemberRef (_javaPeerProxyRef, "SetActivationPeerReference", sig => sig.MethodSignature ().Parameters (2, rt => rt.Void (), p => { @@ -432,7 +432,7 @@ void EmitMemberReferences () p.AddParameter ().Type ().IntPtr (); })); - _markActivationPeerReplaceableRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "MarkActivationPeerReplaceable", + _markActivationPeerReplaceableRef = _pe.AddMemberRef (_javaPeerProxyRef, "MarkActivationPeerReplaceable", sig => sig.MethodSignature ().Parameters (1, rt => rt.Void (), p => p.AddParameter ().Type ().IntPtr ())); @@ -660,13 +660,13 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary sig.MethodSignature (isInstanceMethod: true).Parameters (2, rt => rt.Void (), p => { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index d82f6106eed..d448de126f5 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -187,7 +187,7 @@ public void Generate_ProxyType_HasCtorAndCreateInstance () } [Fact] - public void Generate_ProxyType_UsesNonGenericJavaPeerProxyBase () + public void Generate_ProxyType_UsesJavaPeerProxyBase () { var peers = ScanFixtures (); using var stream = GenerateAssembly (peers); @@ -200,8 +200,7 @@ public void Generate_ProxyType_UsesNonGenericJavaPeerProxyBase () Assert.NotEmpty (proxyTypes); // Every proxy — concrete class, interface, or open generic definition — derives from - // the non-generic `JavaPeerProxy` base (a plain TypeReference), taking `targetType` - // as a ctor parameter. + // the `JavaPeerProxy` base (a plain TypeReference), taking `targetType` as a ctor parameter. Assert.All (proxyTypes, proxyType => { Assert.Equal (HandleKind.TypeReference, proxyType.BaseType.Kind); var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) proxyType.BaseType); @@ -211,11 +210,11 @@ public void Generate_ProxyType_UsesNonGenericJavaPeerProxyBase () } [Fact] - public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase () + public void Generate_InterfaceProxyType_UsesJavaPeerProxyBase () { - // Interface proxies derive from the non-generic JavaPeerProxy base (a plain - // TypeReference), like every other proxy. Interfaces have no constructors and are - // activated from their InvokerType in CreateInstance. + // Interface proxies derive from the `JavaPeerProxy` base (a plain TypeReference), like + // every other proxy. Interfaces have no constructors and are activated from their + // InvokerType in CreateInstance. var peers = ScanFixtures (); using var stream = GenerateAssembly (peers); using var pe = new PEReader (stream); @@ -233,15 +232,15 @@ public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase () } // Regression test: decode the emitted proxy `.ctor` *body* (not just member presence) - // and verify it chains to the correct base constructor. Every proxy derives from the - // non-generic `JavaPeerProxy` and calls the two-arg base ctor `(string, Type)` — pushing - // the target type via `Type.GetTypeFromHandle`. A base-ctor arity/token mismatch here would - // pass metadata inspection but blow up as an InvalidProgramException/TypeLoadException on the + // and verify it chains to the correct base constructor. Every proxy derives from + // `JavaPeerProxy` and calls the two-arg base ctor `(string, Type)` — pushing the target + // type via `Type.GetTypeFromHandle`. A base-ctor arity/token mismatch here would pass + // metadata inspection but blow up as an InvalidProgramException/TypeLoadException on the // CoreCLR device legs, so assert it directly. [Theory] - [InlineData ("Java_Lang_Object_Proxy", 2, true)] // concrete class -> non-generic base (string, Type) - [InlineData ("Android_Views_IOnClickListener_Proxy", 2, true)] // interface -> non-generic base (string, Type) - [InlineData ("MyApp_Generic_GenericHolder_1_Proxy", 2, true)] // open generic -> non-generic base (string, Type) + [InlineData ("Java_Lang_Object_Proxy", 2, true)] // concrete class -> base ctor (string, Type) + [InlineData ("Android_Views_IOnClickListener_Proxy", 2, true)] // interface -> base ctor (string, Type) + [InlineData ("MyApp_Generic_GenericHolder_1_Proxy", 2, true)] // open generic -> base ctor (string, Type) public void Generate_ProxyCtor_ChainsToExpectedBaseConstructor (string proxyTypeName, int expectedBaseCtorArity, bool expectsGetTypeFromHandle) { var peers = ScanFixtures (); diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs index 00f2d1cbd34..f66eb72f608 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs @@ -719,8 +719,8 @@ public void Fixture_GenericHolder_HasAssociation () { // Generic definitions must still get a TypeMapAssociation entry so managed→proxy // lookup works for the open generic definition. Their proxy derives from the - // non-generic `JavaPeerProxy` base, so the CLR can load the proxy without - // resolving an open generic argument. + // `JavaPeerProxy` base and receives the target type as a ctor argument, so the CLR + // can load the proxy without resolving an open generic argument. var peer = FindFixtureByJavaName ("my/app/GenericHolder"); Assert.True (peer.IsGenericDefinition); From d8e3eeb9fa2d40d0c9b943aeb6c4b4c5d529ddcc Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 23:16:51 +0200 Subject: [PATCH 6/9] [TrimmableTypeMap] Remove now-dead JavaPeerProxyData.IsInterface Collapsing every proxy onto the single JavaPeerProxy base removed the only reader of the JavaPeerProxyData.IsInterface model property (it previously selected the base type). It is now write-only, so drop the property and its assignment in ModelBuilder. JavaPeerInfo.IsInterface (the scanner model) is unaffected and still drives ACW gating and JCW generation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e --- .../Generator/Model/TypeMapAssemblyData.cs | 6 ------ .../Generator/ModelBuilder.cs | 1 - 2 files changed, 7 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index 642e7a0f88a..054a44f1795 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -134,12 +134,6 @@ sealed class JavaPeerProxyData /// public bool IsGenericDefinition { get; init; } - /// - /// True if the proxied peer type is a Java interface. Instances are created - /// from in CreateInstance. - /// - public bool IsInterface { get; init; } - /// /// True when the Java stub must not call RegisterNatives from a static initializer because /// the type can be instantiated before the runtime is fully ready (for example Application diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index ee55df411e0..ca25d1962d7 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -300,7 +300,6 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash }, IsAcw = isAcw, IsGenericDefinition = peer.IsGenericDefinition, - IsInterface = peer.IsInterface, CannotRegisterInStaticConstructor = peer.CannotRegisterInStaticConstructor, }; From 8ff49150a735ea58bb2740147c6a72addbeb91d3 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 23:24:05 +0200 Subject: [PATCH 7/9] [TrimmableTypeMap] Remove dead per-anchor TypeMap ctor-ref caches With AnchorRank/per-rank array-map entries gone, every TypeMap and TypeMapAssociation attribute is emitted against the single default anchor. The per-anchor ctor-ref caches (_typeMapAttr2ArgCtorRefByAnchor, _typeMapAttr3ArgCtorRefByAnchor, _typeMapAssociationAttrCtorRefByAnchor) and their GetOrAdd* helpers had no remaining callers, so remove them; the default-anchor ctor refs are still built directly in the *CtorRef emitters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e --- .../Generator/TypeMapAssemblyEmitter.cs | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 367760fb337..9673bd91813 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -141,11 +141,6 @@ sealed class TypeMapAssemblyEmitter ExportMethodDispatchEmitter? _exportMethodDispatchEmitter; - // Per-anchor TypeMap ctor refs, lazily built. - readonly Dictionary _typeMapAttr2ArgCtorRefByAnchor = new (); - readonly Dictionary _typeMapAttr3ArgCtorRefByAnchor = new (); - readonly Dictionary _typeMapAssociationAttrCtorRefByAnchor = new (); - // Cached open TypeMapAttribute`1 ref shared across closed TypeSpecs. TypeReferenceHandle _typeMapAttrOpenRef; @@ -528,23 +523,9 @@ void EmitTypeMapAttributeCtorRef () // 2-arg: TypeMap(string jniName, Type proxyType) — unconditional. _typeMapAttrCtorRef2Arg = AddTypeMapAttr2ArgCtorRef (_anchorTypeHandle); - _typeMapAttr2ArgCtorRefByAnchor [_anchorTypeHandle] = _typeMapAttrCtorRef2Arg; // 3-arg: TypeMap(string jniName, Type proxyType, Type targetType) — trimmable. - // Cache by anchor so rank-anchored entries can build their own closed ctor on demand. _typeMapAttrCtorRef3Arg = AddTypeMapAttr3ArgCtorRef (_anchorTypeHandle); - _typeMapAttr3ArgCtorRefByAnchor [_anchorTypeHandle] = _typeMapAttrCtorRef3Arg; - } - - /// Cached 2-arg TypeMap<TGroup> ctor ref for the given anchor, built on first use. - MemberReferenceHandle GetOrAddTypeMapAttr2ArgCtorRef (EntityHandle anchor) - { - if (_typeMapAttr2ArgCtorRefByAnchor.TryGetValue (anchor, out var cached)) { - return cached; - } - var ctorRef = AddTypeMapAttr2ArgCtorRef (anchor); - _typeMapAttr2ArgCtorRefByAnchor [anchor] = ctorRef; - return ctorRef; } MemberReferenceHandle AddTypeMapAttr2ArgCtorRef (EntityHandle anchor) @@ -559,16 +540,6 @@ MemberReferenceHandle AddTypeMapAttr2ArgCtorRef (EntityHandle anchor) })); } - MemberReferenceHandle GetOrAddTypeMapAttr3ArgCtorRef (EntityHandle anchor) - { - if (_typeMapAttr3ArgCtorRefByAnchor.TryGetValue (anchor, out var cached)) { - return cached; - } - var ctorRef = AddTypeMapAttr3ArgCtorRef (anchor); - _typeMapAttr3ArgCtorRefByAnchor [anchor] = ctorRef; - return ctorRef; - } - MemberReferenceHandle AddTypeMapAttr3ArgCtorRef (EntityHandle anchor) { var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (_typeMapAttrOpenRef, anchor); @@ -597,24 +568,6 @@ void EmitTypeMapAssociationAttributeCtorRef () p.AddParameter ().Type ().Type (_systemTypeRef, false); p.AddParameter ().Type ().Type (_systemTypeRef, false); })); - _typeMapAssociationAttrCtorRefByAnchor [_anchorTypeHandle] = _typeMapAssociationAttrCtorRef; - } - - MemberReferenceHandle GetOrAddTypeMapAssociationAttrCtorRef (EntityHandle anchor) - { - if (_typeMapAssociationAttrCtorRefByAnchor.TryGetValue (anchor, out var cached)) { - return cached; - } - var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (_typeMapAssociationAttrOpenRef, anchor); - var ctorRef = _pe.AddMemberRef (closedAttrTypeSpec, ".ctor", - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, - rt => rt.Void (), - p => { - p.AddParameter ().Type ().Type (_systemTypeRef, false); - p.AddParameter ().Type ().Type (_systemTypeRef, false); - })); - _typeMapAssociationAttrCtorRefByAnchor [anchor] = ctorRef; - return ctorRef; } ExportMethodDispatchEmitterContext CreateExportMethodDispatchEmitterContext () From bfc643b5e42427935e28223be952358383d00733 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 23:26:31 +0200 Subject: [PATCH 8/9] [TrimmableTypeMap] Drop vestigial anchor parameter from TypeMap ctor-ref helpers After removing the per-anchor caches, AddTypeMapAttr2ArgCtorRef and AddTypeMapAttr3ArgCtorRef each have a single caller that always passes _anchorTypeHandle. Drop the parameter and reference _anchorTypeHandle directly, matching EmitTypeMapAssociationAttributeCtorRef. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e --- .../Generator/TypeMapAssemblyEmitter.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 9673bd91813..17865148a4e 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -522,15 +522,15 @@ void EmitTypeMapAttributeCtorRef () metadata.GetOrAddString ("TypeMapAttribute`1")); // 2-arg: TypeMap(string jniName, Type proxyType) — unconditional. - _typeMapAttrCtorRef2Arg = AddTypeMapAttr2ArgCtorRef (_anchorTypeHandle); + _typeMapAttrCtorRef2Arg = AddTypeMapAttr2ArgCtorRef (); // 3-arg: TypeMap(string jniName, Type proxyType, Type targetType) — trimmable. - _typeMapAttrCtorRef3Arg = AddTypeMapAttr3ArgCtorRef (_anchorTypeHandle); + _typeMapAttrCtorRef3Arg = AddTypeMapAttr3ArgCtorRef (); } - MemberReferenceHandle AddTypeMapAttr2ArgCtorRef (EntityHandle anchor) + MemberReferenceHandle AddTypeMapAttr2ArgCtorRef () { - var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (_typeMapAttrOpenRef, anchor); + var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (_typeMapAttrOpenRef, _anchorTypeHandle); return _pe.AddMemberRef (closedAttrTypeSpec, ".ctor", sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, rt => rt.Void (), @@ -540,9 +540,9 @@ MemberReferenceHandle AddTypeMapAttr2ArgCtorRef (EntityHandle anchor) })); } - MemberReferenceHandle AddTypeMapAttr3ArgCtorRef (EntityHandle anchor) + MemberReferenceHandle AddTypeMapAttr3ArgCtorRef () { - var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (_typeMapAttrOpenRef, anchor); + var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (_typeMapAttrOpenRef, _anchorTypeHandle); return _pe.AddMemberRef (closedAttrTypeSpec, ".ctor", sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3, rt => rt.Void (), From 51b8ba426df22e697f9d4fd5cb729c714d8de158 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 16 Jul 2026 10:41:58 +0200 Subject: [PATCH 9/9] [Tests] Fix JavaListTest under NativeAOTTrimmable NUnit instantiates JavaListTest reflectively from the [TestFixture(typeof(...))] arguments, so the trimmable/NativeAOT typemap never sees the concrete `new T ()` in Setup and trims the parameterless constructors of JavaList / JavaList (the trimmable typemap only roots the (IntPtr, JniHandleOwnership) activation constructor of Java peers). That made all 15 JavaListTest cases fail on the NativeAOTTrimmable flavor with: System.MissingMethodException : MissingConstructor_Name, Android.Runtime.JavaList Since there are exactly two fixtures, switch on typeof(T) and call the constructors directly instead of relying on the `new()` constraint, which cannot be honored under NativeAOT reflection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f9ec4f09-ffc5-4f66-99bf-ec40ea2728e8 --- .../Java.Interop/JavaListTest.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaListTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaListTest.cs index 5b096ad8cbc..38739c37cc7 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaListTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaListTest.cs @@ -13,7 +13,19 @@ namespace Java.InteropTests JavaList list; [SetUp] - public void Setup () => list = new T (); + public void Setup () + { + // Note: originally this was just `list = new T ();` but this doesn't work with NativeAOT due to how + // NUnit creates the `JavaListTest` instance via reflection. The `new()` constraint cannot be respected + // under NativeAOT and so this would fail. Given we have just 2 cases, we can simply switch on them and + // call the ctors directly. + list = typeof (T) switch + { + Type t when t == typeof (JavaList) => new JavaList (), + Type t when t == typeof (JavaList) => new JavaList (), + _ => throw new NotSupportedException ($"Unexpected fixture type '{typeof (T)}'."), + }; + } [Test] public void Add ()