diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs new file mode 100644 index 00000000000..d742d120d8c --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs @@ -0,0 +1,5 @@ +using System.Reflection.PortableExecutable; + +namespace Microsoft.Android.Sdk.TrimmableTypeMap; + +public readonly record struct AssemblyInput (string Name, string Path, PEReader Reader); diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs index 82b914900a3..2eb6ba5f3c1 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs @@ -323,7 +323,7 @@ internal void LoadManagedArgument (TrackedInstructionEncoder encoder, TypeRefDat { string managedTypeName = managedType.ManagedTypeName; - ThrowIfUnsupportedManagedType (managedTypeName); + ThrowIfUnsupportedManagedType (managedType); if (TryEmitExportParameterArgument (encoder, exportKind, argumentIndex)) { return; @@ -410,8 +410,10 @@ void ConvertManagedReturnValue (TrackedInstructionEncoder encoder, TypeRefData m encoder.Call (_context.JniEnvToLocalJniHandleRef, parameterCount: 1, returnsValue: true); } - void ThrowIfUnsupportedManagedType (string managedTypeName) + void ThrowIfUnsupportedManagedType (TypeRefData managedType) { + string managedTypeName = managedType.ManagedTypeName; + if (managedTypeName.EndsWith ("&", StringComparison.Ordinal) || managedTypeName.EndsWith ("*", StringComparison.Ordinal)) { throw new NotSupportedException ($"[Export] methods with by-ref or pointer signature types are not supported: '{managedTypeName}'."); } @@ -421,8 +423,8 @@ void ThrowIfUnsupportedManagedType (string managedTypeName) nonArrayTypeName = nonArrayTypeName.Substring (0, nonArrayTypeName.Length - 2); } - if (nonArrayTypeName.StartsWith ("!", StringComparison.Ordinal) || nonArrayTypeName.IndexOf ('<') >= 0) { - throw new NotSupportedException ($"[Export] methods with generic signature types are not supported: '{managedTypeName}'."); + if (nonArrayTypeName.StartsWith ("!", StringComparison.Ordinal) || managedType.GenericArguments.Count > 0 || nonArrayTypeName.IndexOf ('<') >= 0) { + throw new NotSupportedException ($"[Export] methods with generic signature types are not supported: '{managedType.DisplayName}'."); } } @@ -571,7 +573,7 @@ void EncodeManagedType (SignatureTypeEncoder encoder, TypeRefData managedType) { string managedTypeName = managedType.ManagedTypeName; - ThrowIfUnsupportedManagedType (managedTypeName); + ThrowIfUnsupportedManagedType (managedType); if (managedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { EncodeManagedType (encoder.SZArray (), managedType with { ManagedTypeName = managedTypeName.Substring (0, managedTypeName.Length - 2), @@ -598,7 +600,7 @@ void EncodeManagedType (SignatureTypeEncoder encoder, TypeRefData managedType) } var typeHandle = ResolveManagedTypeHandle (managedType); - encoder.Type (typeHandle, isValueType: managedType.IsEnum); + encoder.Type (typeHandle, isValueType: managedType.EncodeAsValueType); } void AddUnmanagedCallersOnlyAttribute (MethodDefinitionHandle handle) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs index 93f8ce5c5fc..c606912621c 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs @@ -38,10 +38,24 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data) } foreach (var proxy in data.ProxyTypes) { writer.Write (proxy.TypeName); - writer.Write (proxy.TargetType.ManagedTypeName); - writer.Write (proxy.TargetType.AssemblyName); + writer.WriteTypeRef (proxy.TargetType); writer.Write ((byte)(proxy.ActivationCtor?.Style ?? 0)); + if (proxy.ActivationCtor is not null) { + writer.WriteTypeRef (proxy.ActivationCtor.DeclaringType); + } writer.Write ((byte)(proxy.InvokerActivationCtorStyle ?? 0)); + writer.Write (proxy.UcoMethods.Count); + foreach (var method in proxy.UcoMethods) { + writer.WriteUcoMethod (method); + } + writer.Write (proxy.UcoConstructors.Count); + foreach (var constructor in proxy.UcoConstructors) { + writer.WriteUcoConstructor (constructor); + } + writer.Write (proxy.NativeRegistrations.Count); + foreach (var registration in proxy.NativeRegistrations) { + writer.WriteNativeRegistration (registration); + } } foreach (var assoc in data.Associations) { writer.Write (assoc.SourceTypeReference); @@ -50,4 +64,68 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data) writer.Flush (); return sha.ComputeHash (stream.ToArray ()); } + + static void WriteTypeRef (this System.IO.BinaryWriter writer, TypeRefData type) + { + writer.Write (type.ManagedTypeName); + writer.Write (type.AssemblyName); + writer.Write (type.IsValueType ? (byte) 1 : (byte) 0); + writer.Write (type.IsEnum ? (byte) 1 : (byte) 0); + writer.Write (type.GenericArguments.Count); + foreach (var argument in type.GenericArguments) { + writer.WriteTypeRef (argument); + } + } + + static void WriteUcoMethod (this System.IO.BinaryWriter writer, UcoMethodData method) + { + writer.Write (method.WrapperName); + writer.Write (method.CallbackMethodName); + writer.WriteTypeRef (method.CallbackType); + writer.Write (method.JniSignature); + writer.WriteExportMethodDispatch (method.ExportMethodDispatch); + } + + static void WriteExportMethodDispatch (this System.IO.BinaryWriter writer, ExportMethodDispatchData? dispatch) + { + writer.Write (dispatch is not null); + if (dispatch is null) { + return; + } + + writer.Write (dispatch.ManagedMethodName); + writer.Write (dispatch.ParameterTypes.Count); + foreach (var parameterType in dispatch.ParameterTypes) { + writer.WriteTypeRef (parameterType); + } + writer.Write (dispatch.ParameterKinds.Count); + foreach (var parameterKind in dispatch.ParameterKinds) { + writer.Write ((int) parameterKind); + } + writer.WriteTypeRef (dispatch.ReturnType); + writer.Write ((int) dispatch.ReturnKind); + writer.Write (dispatch.IsStatic); + } + + static void WriteUcoConstructor (this System.IO.BinaryWriter writer, UcoConstructorData constructor) + { + writer.Write (constructor.WrapperName); + writer.WriteTypeRef (constructor.TargetType); + writer.Write (constructor.JniSignature); + writer.Write (constructor.HasMatchingManagedCtor); + writer.Write (constructor.ManagedParameterTypes.Count); + foreach (var parameterType in constructor.ManagedParameterTypes) { + writer.WriteTypeRef (parameterType); + } + } + + static void WriteNativeRegistration (this System.IO.BinaryWriter writer, NativeRegistrationData registration) + { + writer.Write (registration.JniMethodName); + writer.Write (registration.JniSignature); + writer.Write (registration.WrapperMethodName); + writer.Write (registration.WrapperTarget.TypeNamespace); + writer.Write (registration.WrapperTarget.TypeName); + writer.Write (registration.WrapperTarget.MethodName); + } } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index a90f0d7dbeb..f7d2403f6e3 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Android.Sdk.TrimmableTypeMap; @@ -186,11 +187,75 @@ sealed record TypeRefData public required string AssemblyName { get; init; } /// - /// True if this type — or, for array types, the element type — is an enum. + /// Generic arguments for a constructed generic type. Empty for non-generic + /// types and open generic definitions. + /// + public IReadOnlyList GenericArguments { get; init; } = []; + + /// + /// True if this type — or, for array types, the element type — is a value type. /// Used by the IL emitter to encode the type as ELEMENT_TYPE_VALUETYPE /// rather than ELEMENT_TYPE_CLASS in member references and signatures. /// + public bool IsValueType { get; init; } + + /// + /// True if this type — or, for array types, the element type — is an enum. + /// Used by JNI signature generation to map enum values to their underlying + /// primitive ABI type. + /// public bool IsEnum { get; init; } + + public bool EncodeAsValueType => IsValueType || IsEnum; + + public string DisplayName { + get { + if (ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { + return $"{(this with { ManagedTypeName = ManagedTypeName.Substring (0, ManagedTypeName.Length - 2) }).DisplayName}[]"; + } + return GenericArguments.Count == 0 + ? ManagedTypeName + : $"{ManagedTypeName}<{string.Join (",", GenericArguments.Select (t => t.DisplayName))}>"; + } + } + + public bool Equals (TypeRefData? other) + { + if (ReferenceEquals (this, other)) { + return true; + } + if (other is null) { + return false; + } + if (!string.Equals (ManagedTypeName, other.ManagedTypeName, StringComparison.Ordinal) || + !string.Equals (AssemblyName, other.AssemblyName, StringComparison.Ordinal) || + IsValueType != other.IsValueType || + IsEnum != other.IsEnum || + GenericArguments.Count != other.GenericArguments.Count) { + return false; + } + for (int i = 0; i < GenericArguments.Count; i++) { + if (!GenericArguments [i].Equals (other.GenericArguments [i])) { + return false; + } + } + return true; + } + + public override int GetHashCode () + { + unchecked { + int hash = 17; + hash = hash * 31 + StringComparer.Ordinal.GetHashCode (ManagedTypeName); + hash = hash * 31 + StringComparer.Ordinal.GetHashCode (AssemblyName); + hash = hash * 31 + IsValueType.GetHashCode (); + hash = hash * 31 + IsEnum.GetHashCode (); + foreach (var argument in GenericArguments) { + hash = hash * 31 + argument.GetHashCode (); + } + return hash; + } + } } /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index 4671394e936..ceba650aaff 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -304,10 +304,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash if (peer.ActivationCtor != null) { bool isOnLeaf = string.Equals (peer.ActivationCtor.DeclaringTypeName, peer.ManagedTypeName, StringComparison.Ordinal); proxy.ActivationCtor = new ActivationCtorData { - DeclaringType = new TypeRefData { - ManagedTypeName = peer.ActivationCtor.DeclaringTypeName, - AssemblyName = peer.ActivationCtor.DeclaringAssemblyName, - }, + DeclaringType = peer.ActivationCtor.DeclaringType, IsOnLeafType = isOnLeaf, Style = peer.ActivationCtor.Style, }; @@ -333,7 +330,7 @@ static void BuildUcoMethods (JavaPeerInfo peer, JavaPeerProxyData proxy) proxy.UcoMethods.Add (new UcoMethodData { WrapperName = $"n_{mm.JniName}_uco_{ucoIndex}", CallbackMethodName = mm.NativeCallbackName, - CallbackType = new TypeRefData { + CallbackType = mm.DeclaringType ?? new TypeRefData { ManagedTypeName = !mm.DeclaringTypeName.IsNullOrEmpty () ? mm.DeclaringTypeName : peer.ManagedTypeName, AssemblyName = !mm.DeclaringAssemblyName.IsNullOrEmpty () ? mm.DeclaringAssemblyName : peer.AssemblyName, }, diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs index 57e57b7a835..cd5ff6b2b84 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -23,7 +24,7 @@ sealed class PEAssemblyBuilder static readonly byte [] MonoAndroidPublicKeyToken = { 0x84, 0xe0, 0x4f, 0xf9, 0xcf, 0xb7, 0x90, 0x65 }; readonly Dictionary _asmRefCache = new (StringComparer.OrdinalIgnoreCase); - readonly Dictionary<(string Assembly, string Type), EntityHandle> _typeRefCache = new (); + readonly Dictionary _typeRefCache = new (); // Reusable scratch BlobBuilders — avoids allocating a new one per method body / attribute / member ref. // Each is Clear()'d before use. Safe because all emission is single-threaded and non-reentrant. @@ -148,16 +149,95 @@ public MemberReferenceHandle AddMemberRef (EntityHandle parent, string name, Act /// public EntityHandle ResolveTypeRef (TypeRefData typeRef) { - var cacheKey = (typeRef.AssemblyName, typeRef.ManagedTypeName); + var cacheKey = GetTypeRefCacheKey (typeRef); if (_typeRefCache.TryGetValue (cacheKey, out var cached)) { return cached; } var asmRef = FindOrAddAssemblyRef (typeRef.AssemblyName); - var result = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName); + EntityHandle result; + if (typeRef.GenericArguments.Count > 0) { + var openType = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName); + var blob = new BlobBuilder (64); + WriteGenericInstantiationSignature (blob, openType, typeRef); + result = Metadata.AddTypeSpecification (Metadata.GetOrAddBlob (blob)); + } else { + result = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName); + } _typeRefCache [cacheKey] = result; return result; } + static string GetTypeRefCacheKey (TypeRefData typeRef) + { + var typeKind = typeRef.EncodeAsValueType ? "valuetype" : "class"; + if (typeRef.GenericArguments.Count == 0) { + return $"{typeKind}:{typeRef.AssemblyName}:{typeRef.ManagedTypeName}"; + } + return $"{typeKind}:{typeRef.AssemblyName}:{typeRef.ManagedTypeName}<{string.Join (",", typeRef.GenericArguments.Select (GetTypeRefCacheKey))}>"; + } + + void WriteGenericInstantiationSignature (BlobBuilder blob, EntityHandle openType, TypeRefData typeRef) + { + blob.WriteByte (0x15); // ELEMENT_TYPE_GENERICINST + blob.WriteByte (typeRef.EncodeAsValueType ? (byte) 0x11 : (byte) 0x12); // VALUETYPE or CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (openType)); + blob.WriteCompressedInteger (typeRef.GenericArguments.Count); + foreach (var argument in typeRef.GenericArguments) { + WriteTypeSignature (blob, argument); + } + } + + public void WriteTypeSignature (BlobBuilder blob, TypeRefData typeRef) + { + if (typeRef.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { + blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY + WriteTypeSignature (blob, typeRef with { + ManagedTypeName = typeRef.ManagedTypeName.Substring (0, typeRef.ManagedTypeName.Length - 2), + }); + return; + } + + switch (typeRef.ManagedTypeName) { + case "System.Boolean": blob.WriteByte (0x02); return; + case "System.Char": blob.WriteByte (0x03); return; + case "System.SByte": blob.WriteByte (0x04); return; + case "System.Byte": blob.WriteByte (0x05); return; + case "System.Int16": blob.WriteByte (0x06); return; + case "System.UInt16": blob.WriteByte (0x07); return; + case "System.Int32": blob.WriteByte (0x08); return; + case "System.UInt32": blob.WriteByte (0x09); return; + case "System.Int64": blob.WriteByte (0x0A); return; + case "System.UInt64": blob.WriteByte (0x0B); return; + case "System.Single": blob.WriteByte (0x0C); return; + case "System.Double": blob.WriteByte (0x0D); return; + case "System.String": blob.WriteByte (0x0E); return; + case "System.Object": blob.WriteByte (0x1C); return; + case "System.IntPtr": blob.WriteByte (0x18); return; + } + + if (typeRef.GenericArguments.Count > 0) { + var asmRef = FindOrAddAssemblyRef (typeRef.AssemblyName); + var openType = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName); + WriteGenericInstantiationSignature (blob, openType, typeRef); + return; + } + + if (typeRef.ManagedTypeName.StartsWith ("!!", StringComparison.Ordinal)) { + blob.WriteByte (0x1E); // ELEMENT_TYPE_MVAR + blob.WriteCompressedInteger (int.Parse (typeRef.ManagedTypeName.Substring (2), System.Globalization.CultureInfo.InvariantCulture)); + return; + } + if (typeRef.ManagedTypeName.StartsWith ("!", StringComparison.Ordinal)) { + blob.WriteByte (0x13); // ELEMENT_TYPE_VAR + blob.WriteCompressedInteger (int.Parse (typeRef.ManagedTypeName.Substring (1), System.Globalization.CultureInfo.InvariantCulture)); + return; + } + + var typeHandle = ResolveTypeRef (typeRef); + blob.WriteByte (typeRef.EncodeAsValueType ? (byte) 0x11 : (byte) 0x12); // VALUETYPE or CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeHandle)); + } + TypeReferenceHandle MakeTypeRefForManagedName (EntityHandle scope, string managedTypeName) { int plusIndex = managedTypeName.IndexOf ('+'); diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 8ab18800ea5..cc507f2ee3e 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -1059,7 +1059,7 @@ MemberReferenceHandle AddManagedCtorRef (EntityHandle declaringTypeRef, IReadOnl blob.WriteCompressedInteger (parameterTypes.Count); blob.WriteByte (0x01); // ELEMENT_TYPE_VOID foreach (var parameterType in parameterTypes) { - WriteManagedTypeSignature (blob, parameterType.ManagedTypeName, parameterType.AssemblyName); + _pe.WriteTypeSignature (blob, parameterType); } return _pe.Metadata.AddMemberReference (declaringTypeRef, _pe.Metadata.GetOrAddString (".ctor"), _pe.Metadata.GetOrAddBlob (blob)); } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs index 64ca498c814..52ff5b3a9f8 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs @@ -17,6 +17,7 @@ sealed class AssemblyIndex : IDisposable public MetadataReader Reader { get; } public string AssemblyName { get; } + public string AssemblyPath { get; } /// /// Maps full managed type name (e.g., "Android.App.Activity") to its TypeDefinitionHandle. @@ -48,18 +49,19 @@ sealed class AssemblyIndex : IDisposable /// public bool MayUseJniAddNativeMethodRegistrationAttribute { get; private set; } - AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName) + AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName, string assemblyPath) { this.peReader = peReader; this.customAttributeTypeProvider = new CustomAttributeTypeProvider (reader); Reader = reader; AssemblyName = assemblyName; + AssemblyPath = assemblyPath; } - public static AssemblyIndex Create (PEReader peReader, string assemblyName) + public static AssemblyIndex Create (PEReader peReader, string assemblyName, string assemblyPath = "") { var reader = peReader.GetMetadataReader (); - var index = new AssemblyIndex (peReader, reader, assemblyName); + var index = new AssemblyIndex (peReader, reader, assemblyName, assemblyPath); index.Build (); return index; } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs index 7382a4ed60a..0307ac79638 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs @@ -200,6 +200,12 @@ public sealed record MarshalMethodInfo /// public string DeclaringAssemblyName { get; init; } = ""; + /// + /// Exact type that declares the managed method. This can be a constructed + /// generic base type, e.g. BaseAdapter<object>. + /// + internal TypeRefData? DeclaringType { get; init; } + /// /// The native callback method name, e.g., "n_onCreate". /// This is the Java/JNI-visible native method name that the generated JCW calls. @@ -376,6 +382,15 @@ public sealed record ActivationCtorInfo /// public required string DeclaringAssemblyName { get; init; } + /// + /// Exact type that declares the activation constructor. This can be a + /// constructed generic base type. + /// + internal TypeRefData DeclaringType { get; init; } = new () { + ManagedTypeName = "", + AssemblyName = "", + }; + /// /// The style of activation constructor found. /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs index 08316ff1787..b4a17a52f3a 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -50,35 +51,6 @@ bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHan return false; } - /// - /// Resolves a TypeReferenceHandle to (fullName, assemblyName), correctly handling - /// nested types whose ResolutionScope is another TypeReference. - /// - static (string fullName, string assemblyName) ResolveTypeReference (TypeReferenceHandle handle, AssemblyIndex index) - { - var typeRef = index.Reader.GetTypeReference (handle); - var name = index.Reader.GetString (typeRef.Name); - var ns = index.Reader.GetString (typeRef.Namespace); - - var scope = typeRef.ResolutionScope; - switch (scope.Kind) { - case HandleKind.AssemblyReference: { - var asmRef = index.Reader.GetAssemblyReference ((AssemblyReferenceHandle)scope); - var fullName = MetadataTypeNameResolver.JoinNamespaceAndName (ns, name); - return (fullName, index.Reader.GetString (asmRef.Name)); - } - case HandleKind.TypeReference: { - // Nested type: recurse to get the declaring type's full name and assembly - var (parentFullName, assemblyName) = ResolveTypeReference ((TypeReferenceHandle)scope, index); - return (MetadataTypeNameResolver.JoinNestedTypeName (parentFullName, name), assemblyName); - } - default: { - var fullName = MetadataTypeNameResolver.JoinNamespaceAndName (ns, name); - return (fullName, index.AssemblyName); - } - } - } - /// /// Looks up the [Register] JNI name for a type identified by name + assembly. /// @@ -96,9 +68,12 @@ bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHan /// Phase 2: Scan all types and produce JavaPeerInfo records. /// public List Scan (IReadOnlyList<(string Name, PEReader Reader)> assemblies) + => Scan (assemblies.Select (a => new AssemblyInput (a.Name, "", a.Reader))); + + public List Scan (IEnumerable assemblies) { - foreach (var (name, reader) in assemblies) { - var index = AssemblyIndex.Create (reader, name); + foreach (var assembly in assemblies) { + var index = AssemblyIndex.Create (assembly.Reader, assembly.Name, assembly.Path); assemblyCache [index.AssemblyName] = index; } @@ -525,7 +500,8 @@ void CollectInterfaceMethodImplementations (TypeDefinition typeDef, AssemblyInde continue; } - var (ifaceTypeName, ifaceAssemblyName) = resolved.Value; + var ifaceTypeName = resolved.ManagedTypeName; + var ifaceAssemblyName = resolved.AssemblyName; if (!TryResolveType (ifaceTypeName, ifaceAssemblyName, out var ifaceHandle, out var ifaceIndex)) { continue; } @@ -669,7 +645,7 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index, // Check if this ctor's params are already covered by a base registered ctor bool alreadyCovered = false; foreach (var baseCtor in baseRegisteredCtors) { - if (HaveIdenticalParameterTypes (methodDef, baseCtor.Method)) { + if (HaveIdenticalParameterTypes (methodDef, index, baseCtor.Method, baseCtor.Index, baseCtor.DeclaringType)) { alreadyCovered = true; break; } @@ -859,7 +835,7 @@ TypeRefData EnrichTypeRefWithEnumInfo (TypeRefData type) return type; } - return IsEnumOrEnumArray (type.ManagedTypeName, type.AssemblyName) ? type with { IsEnum = true } : type; + return IsEnumOrEnumArray (type.ManagedTypeName, type.AssemblyName) ? type with { IsEnum = true, IsValueType = true } : type; } static bool IsEnumType (TypeDefinition typeDef, AssemblyIndex index) @@ -904,7 +880,8 @@ List CollectBaseRegisteredCtors (TypeDefinition typeDef, AssemblyI var currentTypeDef = typeDef; var currentIndex = index; - while (TryResolveBaseType (currentTypeDef, currentIndex, out var baseTypeDef, out var baseHandle, out var baseIndex, out _, out _)) { + TypeRefData? currentTypeRef = null; + while (TryResolveBaseType (currentTypeDef, currentIndex, currentTypeRef, out var baseTypeDef, out var baseHandle, out var baseIndex, out _, out _, out var baseTypeRef)) { foreach (var methodHandle in baseTypeDef.GetMethods ()) { var methodDef = baseIndex.Reader.GetMethodDefinition (methodHandle); var name = baseIndex.Reader.GetString (methodDef.Name); @@ -914,7 +891,7 @@ List CollectBaseRegisteredCtors (TypeDefinition typeDef, AssemblyI if (TryGetMethodRegisterInfo (methodDef, baseIndex, out var registerInfo, out _) && registerInfo is not null && registerInfo.Signature is not null) { - result.Add (new BaseCtorInfo (methodDef, baseIndex, registerInfo)); + result.Add (new BaseCtorInfo (methodDef, baseIndex, registerInfo, baseTypeRef)); } } @@ -926,6 +903,7 @@ List CollectBaseRegisteredCtors (TypeDefinition typeDef, AssemblyI currentTypeDef = baseTypeDef; currentIndex = baseIndex; + currentTypeRef = baseTypeRef; } return result; @@ -937,20 +915,32 @@ List CollectBaseRegisteredCtors (TypeDefinition typeDef, AssemblyI /// bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, out TypeDefinition baseTypeDef, out TypeDefinitionHandle baseHandle, [NotNullWhen (true)] out AssemblyIndex? baseIndex, - out string baseTypeName, out string baseAssemblyName) + out string baseTypeName, out string baseAssemblyName, out TypeRefData baseTypeRef) + => TryResolveBaseType (typeDef, index, currentTypeRef: null, out baseTypeDef, out baseHandle, out baseIndex, out baseTypeName, out baseAssemblyName, out baseTypeRef); + + bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, TypeRefData? currentTypeRef, + out TypeDefinition baseTypeDef, out TypeDefinitionHandle baseHandle, [NotNullWhen (true)] out AssemblyIndex? baseIndex, + out string baseTypeName, out string baseAssemblyName, out TypeRefData baseTypeRef) { baseTypeDef = default; baseHandle = default; baseIndex = null; baseTypeName = ""; baseAssemblyName = ""; + baseTypeRef = new TypeRefData { + ManagedTypeName = "", + AssemblyName = "", + }; var baseInfo = GetBaseTypeInfo (typeDef, index); if (baseInfo is null) { return false; } - (baseTypeName, baseAssemblyName) = baseInfo.Value; + baseTypeRef = currentTypeRef is null ? baseInfo : SubstituteGenericArguments (baseInfo, currentTypeRef); + baseTypeName = baseTypeRef.ManagedTypeName; + baseAssemblyName = baseTypeRef.AssemblyName; + if (!TryResolveType (baseTypeName, baseAssemblyName, out baseHandle, out baseIndex)) { return false; } @@ -959,7 +949,38 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, return true; } - readonly record struct BaseCtorInfo (MethodDefinition Method, AssemblyIndex Index, RegisterInfo RegisterInfo); + static TypeRefData SubstituteGenericArguments (TypeRefData type, TypeRefData context) + { + if (type.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { + var elementType = SubstituteGenericArguments (type with { + ManagedTypeName = type.ManagedTypeName.Substring (0, type.ManagedTypeName.Length - 2), + }, context); + return elementType with { + ManagedTypeName = $"{elementType.ManagedTypeName}[]", + }; + } + + if (type.ManagedTypeName.StartsWith ("!", StringComparison.Ordinal) && + !type.ManagedTypeName.StartsWith ("!!", StringComparison.Ordinal) && + int.TryParse (type.ManagedTypeName.Substring (1), out int parameterIndex) && + (uint) parameterIndex < (uint) context.GenericArguments.Count) { + return context.GenericArguments [parameterIndex]; + } + + if (type.GenericArguments.Count == 0) { + return type; + } + + var arguments = new TypeRefData [type.GenericArguments.Count]; + for (int i = 0; i < arguments.Length; i++) { + arguments [i] = SubstituteGenericArguments (type.GenericArguments [i], context); + } + return type with { + GenericArguments = arguments, + }; + } + + readonly record struct BaseCtorInfo (MethodDefinition Method, AssemblyIndex Index, RegisterInfo RegisterInfo, TypeRefData DeclaringType); /// /// Walks the base type hierarchy looking for a method with [Register] that matches @@ -967,10 +988,10 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, /// info along with the declaring type's full name and assembly name (needed so /// UCO wrappers call n_* on the correct base type). /// - (RegisterInfo Info, string DeclaringTypeName, string DeclaringAssemblyName)? FindBaseRegisteredMethodInfo ( - TypeDefinition typeDef, AssemblyIndex index, string methodName, MethodDefinition derivedMethod) + (RegisterInfo Info, TypeRefData DeclaringType)? FindBaseRegisteredMethodInfo ( + TypeDefinition typeDef, AssemblyIndex index, string methodName, MethodDefinition derivedMethod, AssemblyIndex derivedIndex, TypeRefData? currentTypeRef = null) { - if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out var baseAssemblyName)) { + if (!TryResolveBaseType (typeDef, index, currentTypeRef, out var baseTypeDef, out _, out var baseIndex, out _, out _, out var baseTypeRef)) { return null; } @@ -988,7 +1009,7 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, continue; } - if (!HaveIdenticalParameterTypes (derivedMethod, baseMethodDef)) { + if (!HaveIdenticalParameterTypes (derivedMethod, derivedIndex, baseMethodDef, baseIndex, baseTypeRef)) { continue; } @@ -997,19 +1018,19 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, // derived override must NOT inherit a base [Export] registration — // only [Register]-driven entries propagate through inheritance. if (TryGetMethodRegisterInfo (baseMethodDef, baseIndex, out var registerInfo, out var exportInfo) && registerInfo is not null && exportInfo is null) { - return (registerInfo, baseTypeName, baseAssemblyName); + return (registerInfo, baseTypeRef); } } // Keep walking the full base hierarchy so overrides can inherit [Register] // metadata declared above an intermediate MCW base type. - return FindBaseRegisteredMethodInfo (baseTypeDef, baseIndex, methodName, derivedMethod); + return FindBaseRegisteredMethodInfo (baseTypeDef, baseIndex, methodName, derivedMethod, derivedIndex, baseTypeRef); } MarshalMethodInfo? FindBaseRegisteredMethod (TypeDefinition typeDef, AssemblyIndex index, string methodName, MethodDefinition derivedMethod) { - var result = FindBaseRegisteredMethodInfo (typeDef, index, methodName, derivedMethod); + var result = FindBaseRegisteredMethodInfo (typeDef, index, methodName, derivedMethod, index); if (result is null || result.Value.Info.Signature is null) { return null; } @@ -1023,8 +1044,9 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, ManagedMethodName = methodName, NativeCallbackName = GetNativeCallbackName (registerInfo.Connector, methodName, isConstructor), IsConstructor = isConstructor, - DeclaringTypeName = result.Value.DeclaringTypeName, - DeclaringAssemblyName = result.Value.DeclaringAssemblyName, + DeclaringTypeName = result.Value.DeclaringType.ManagedTypeName, + DeclaringAssemblyName = result.Value.DeclaringType.AssemblyName, + DeclaringType = result.Value.DeclaringType, }; } @@ -1033,9 +1055,9 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, /// matches the given getter name and has a compatible signature. /// MarshalMethodInfo? FindBaseRegisteredProperty (TypeDefinition typeDef, AssemblyIndex index, - string getterName, MethodDefinition derivedGetter) + string getterName, MethodDefinition derivedGetter, TypeRefData? currentTypeRef = null) { - if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out var baseAssemblyName)) { + if (!TryResolveBaseType (typeDef, index, currentTypeRef, out var baseTypeDef, out _, out var baseIndex, out _, out _, out var baseTypeRef)) { return null; } @@ -1068,31 +1090,33 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, ManagedMethodName = getterName, NativeCallbackName = GetNativeCallbackName (propRegister.Connector, getterName, false), IsConstructor = false, - DeclaringTypeName = baseTypeName, - DeclaringAssemblyName = baseAssemblyName, + DeclaringTypeName = baseTypeRef.ManagedTypeName, + DeclaringAssemblyName = baseTypeRef.AssemblyName, + DeclaringType = baseTypeRef, }; } } // Keep walking the full base hierarchy so property overrides can inherit // [Register] metadata declared above an intermediate MCW base type. - return FindBaseRegisteredProperty (baseTypeDef, baseIndex, getterName, derivedGetter); + return FindBaseRegisteredProperty (baseTypeDef, baseIndex, getterName, derivedGetter, baseTypeRef); } /// /// Checks if two methods have identical parameter types by comparing their decoded signatures. /// - static bool HaveIdenticalParameterTypes (MethodDefinition method1, MethodDefinition method2) + static bool HaveIdenticalParameterTypes (MethodDefinition derivedMethod, AssemblyIndex derivedIndex, MethodDefinition baseMethod, AssemblyIndex baseIndex, TypeRefData baseTypeRef) { - var sig1 = method1.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); - var sig2 = method2.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); + var derivedSig = derivedMethod.DecodeSignature (TypeRefSignatureTypeProvider.Instance, genericContext: derivedIndex); + var baseSig = baseMethod.DecodeSignature (TypeRefSignatureTypeProvider.Instance, genericContext: baseIndex); - if (sig1.ParameterTypes.Length != sig2.ParameterTypes.Length) { + if (derivedSig.ParameterTypes.Length != baseSig.ParameterTypes.Length) { return false; } - for (int i = 0; i < sig1.ParameterTypes.Length; i++) { - if (!string.Equals (sig1.ParameterTypes [i], sig2.ParameterTypes [i], StringComparison.Ordinal)) { + for (int i = 0; i < derivedSig.ParameterTypes.Length; i++) { + var baseParameterType = SubstituteGenericArguments (baseSig.ParameterTypes [i], baseTypeRef); + if (!derivedSig.ParameterTypes [i].Equals (baseParameterType)) { return false; } } @@ -1195,6 +1219,10 @@ static bool SupportsDirectManagedMethodCall (MethodSignature manage static bool SupportsDirectManagedMethodCall (TypeRefData type) { + if (type.GenericArguments.Count > 0) { + return false; + } + var typeName = type.ManagedTypeName; if (typeName.EndsWith ("&", StringComparison.Ordinal) || typeName.EndsWith ("*", StringComparison.Ordinal)) { return false; @@ -1204,7 +1232,7 @@ static bool SupportsDirectManagedMethodCall (TypeRefData type) typeName = typeName.Substring (0, typeName.Length - 2); } - return !typeName.StartsWith ("!", StringComparison.Ordinal) && typeName.IndexOf ('<') < 0; + return !typeName.StartsWith ("!", StringComparison.Ordinal); } static string GetJavaAccess (MethodAttributes access) @@ -1219,7 +1247,7 @@ static string GetJavaAccess (MethodAttributes access) string? ResolveBaseJavaName (TypeDefinition typeDef, AssemblyIndex index, Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> results) { - if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out _)) { + if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out _, out _)) { return null; } @@ -1264,7 +1292,7 @@ List ResolveImplementedInterfaceJavaNames (TypeDefinition typeDef, Assem string? ResolveInterfaceJniName (EntityHandle interfaceHandle, AssemblyIndex index) { var resolved = ResolveEntityHandle (interfaceHandle, index); - return resolved is not null ? ResolveRegisterJniName (resolved.Value.typeName, resolved.Value.assemblyName) : null; + return resolved is not null ? ResolveRegisterJniName (resolved.ManagedTypeName, resolved.AssemblyName) : null; } bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index, out RegisterInfo? registerInfo, out ExportInfo? exportInfo) @@ -1564,9 +1592,9 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI }; } - ActivationCtorInfo? ResolveActivationCtor (string typeName, TypeDefinition typeDef, AssemblyIndex index) + ActivationCtorInfo? ResolveActivationCtor (string typeName, TypeDefinition typeDef, AssemblyIndex index, TypeRefData? currentTypeRef = null) { - var cacheKey = (typeName, index.AssemblyName); + var cacheKey = (currentTypeRef?.DisplayName ?? typeName, index.AssemblyName); if (activationCtorCache.TryGetValue (cacheKey, out var cached)) { return cached; } @@ -1574,7 +1602,20 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI // Check this type's constructors var ownCtor = FindActivationCtorOnType (typeDef, index); if (ownCtor is not null) { - var info = new ActivationCtorInfo { DeclaringTypeName = typeName, DeclaringAssemblyName = index.AssemblyName, Style = ownCtor.Value }; + var info = new ActivationCtorInfo { + DeclaringTypeName = typeName, + DeclaringAssemblyName = index.AssemblyName, + DeclaringType = new TypeRefData { + ManagedTypeName = typeName, + AssemblyName = index.AssemblyName, + }, + Style = ownCtor.Value, + }; + if (currentTypeRef is not null) { + info = info with { + DeclaringType = currentTypeRef, + }; + } activationCtorCache [cacheKey] = info; return info; } @@ -1582,10 +1623,12 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI // Walk base type hierarchy var baseInfo = GetBaseTypeInfo (typeDef, index); if (baseInfo is not null) { - var (baseTypeName, baseAssemblyName) = baseInfo.Value; + baseInfo = currentTypeRef is null ? baseInfo : SubstituteGenericArguments (baseInfo, currentTypeRef); + var baseTypeName = baseInfo.ManagedTypeName; + var baseAssemblyName = baseInfo.AssemblyName; if (TryResolveType (baseTypeName, baseAssemblyName, out var baseHandle, out var baseIndex)) { var baseTypeDef = baseIndex.Reader.GetTypeDefinition (baseHandle); - var result = ResolveActivationCtor (baseTypeName, baseTypeDef, baseIndex); + var result = ResolveActivationCtor (baseTypeName, baseTypeDef, baseIndex, baseInfo); if (result is not null) { activationCtorCache [cacheKey] = result; } @@ -1630,53 +1673,28 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI /// Resolves a TypeSpecificationHandle (generic instantiation) to the underlying /// type's (fullName, assemblyName) by reading the raw signature blob. /// - static (string fullName, string assemblyName)? ResolveTypeSpecification (TypeSpecificationHandle specHandle, AssemblyIndex index) + TypeRefData? ResolveTypeSpecification (TypeSpecificationHandle specHandle, AssemblyIndex index) { var typeSpec = index.Reader.GetTypeSpecification (specHandle); - var blobReader = index.Reader.GetBlobReader (typeSpec.Signature); - - // Generic instantiation blob: GENERICINST (CLASS|VALUETYPE) coded-token count args... - var elementType = blobReader.ReadByte (); - if (elementType != 0x15) { // ELEMENT_TYPE_GENERICINST - return null; - } - - var classOrValueType = blobReader.ReadByte (); - if (classOrValueType != 0x12 && classOrValueType != 0x11) { // CLASS or VALUETYPE - return null; - } - - // TypeDefOrRefOrSpec coded index: 2 tag bits (0=TypeDef, 1=TypeRef, 2=TypeSpec) - var codedToken = blobReader.ReadCompressedInteger (); - var tag = codedToken & 0x3; - var row = codedToken >> 2; - - switch (tag) { - case 0: { // TypeDef - var handle = MetadataTokens.TypeDefinitionHandle (row); - var baseDef = index.Reader.GetTypeDefinition (handle); - return (MetadataTypeNameResolver.GetFullName (baseDef, index.Reader), index.AssemblyName); - } - case 1: // TypeRef - return ResolveTypeReference (MetadataTokens.TypeReferenceHandle (row), index); - default: - return null; - } + return typeSpec.DecodeSignature (TypeRefSignatureTypeProvider.Instance, index); } /// /// Resolves an EntityHandle (TypeDef, TypeRef, or TypeSpec) to (typeName, assemblyName). /// Shared by base type resolution, interface resolution, and any handle-to-name lookup. /// - (string typeName, string assemblyName)? ResolveEntityHandle (EntityHandle handle, AssemblyIndex index) + TypeRefData? ResolveEntityHandle (EntityHandle handle, AssemblyIndex index) { switch (handle.Kind) { case HandleKind.TypeDefinition: { var td = index.Reader.GetTypeDefinition ((TypeDefinitionHandle)handle); - return (MetadataTypeNameResolver.GetFullName (td, index.Reader), index.AssemblyName); + return new TypeRefData { + ManagedTypeName = MetadataTypeNameResolver.GetFullName (td, index.Reader), + AssemblyName = index.AssemblyName, + }; } case HandleKind.TypeReference: - return ResolveTypeReference ((TypeReferenceHandle)handle, index); + return MetadataTypeNameResolver.GetTypeRefFromReference (index.Reader, (TypeReferenceHandle)handle, index.AssemblyName, rawTypeKind: 0); case HandleKind.TypeSpecification: return ResolveTypeSpecification ((TypeSpecificationHandle)handle, index); default: @@ -1684,7 +1702,7 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI } } - (string typeName, string assemblyName)? GetBaseTypeInfo (TypeDefinition typeDef, AssemblyIndex index) + TypeRefData? GetBaseTypeInfo (TypeDefinition typeDef, AssemblyIndex index) { return typeDef.BaseType.IsNil ? null : ResolveEntityHandle (typeDef.BaseType, index); } @@ -1769,7 +1787,8 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index) return false; } - var (baseTypeName, baseAssemblyName) = baseInfo.Value; + var baseTypeName = baseInfo.ManagedTypeName; + var baseAssemblyName = baseInfo.AssemblyName; if (!TryResolveType (baseTypeName, baseAssemblyName, out var baseHandle, out var baseIndex)) { return false; @@ -2031,7 +2050,7 @@ List BuildJavaConstructors (List marshal bool unsupportedParam = false; foreach (var p in sig.ParameterTypes) { var paramTypeName = p.ManagedTypeName; - if (paramTypeName.IndexOf ('<') >= 0 || paramTypeName.EndsWith ("&", StringComparison.Ordinal) || paramTypeName.EndsWith ("*", StringComparison.Ordinal)) { + if (p.GenericArguments.Count > 0 || paramTypeName.EndsWith ("&", StringComparison.Ordinal) || paramTypeName.EndsWith ("*", StringComparison.Ordinal)) { unsupportedParam = true; break; } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs index 468fef15f25..fa13b8c5094 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs @@ -8,6 +8,8 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// static class MetadataTypeNameResolver { + const byte ElementTypeValueType = 0x11; + internal static string JoinNamespaceAndName (string ns, string name) { return ns.Length > 0 ? $"{ns}.{name}" : name; @@ -40,6 +42,7 @@ public static TypeRefData GetTypeRefFromDefinition (MetadataReader reader, TypeD return new TypeRefData { ManagedTypeName = GetTypeFromDefinition (reader, handle, rawTypeKind), AssemblyName = assemblyName, + IsValueType = rawTypeKind == ElementTypeValueType, }; } @@ -64,6 +67,7 @@ public static TypeRefData GetTypeRefFromReference (MetadataReader reader, TypeRe return new TypeRefData { ManagedTypeName = managedTypeName, AssemblyName = assemblyName, + IsValueType = rawTypeKind == ElementTypeValueType, }; } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs index 779e4f76f70..1639d582573 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs @@ -108,9 +108,7 @@ public TypeRefData GetPointerType (TypeRefData elementType) => elementType with public TypeRefData GetGenericInstantiation (TypeRefData genericType, ImmutableArray typeArguments) { - return genericType with { - ManagedTypeName = $"{genericType.ManagedTypeName}<{string.Join (",", typeArguments.Select (t => t.ManagedTypeName))}>", - }; + return genericType with { GenericArguments = typeArguments.ToArray () }; } public TypeRefData GetGenericTypeParameter (AssemblyIndex genericContext, int index) => new () { diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index 95d0446b205..416c461fe28 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -27,7 +27,7 @@ public TrimmableTypeMapGenerator (ITrimmableTypeMapLogger logger) /// No file IO is performed — all results are returned in memory. /// public TrimmableTypeMapResult Execute ( - IReadOnlyList<(string Name, PEReader Reader)> assemblies, + IReadOnlyList assemblies, Version systemRuntimeVersion, HashSet frameworkAssemblyNames, bool useSharedTypemapUniverse = false, @@ -155,7 +155,7 @@ GeneratedManifest GenerateManifest (List allPeers, AssemblyManifes return new GeneratedManifest (doc, providerNames.Count > 0 ? providerNames.ToArray () : []); } - (List peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList<(string Name, PEReader Reader)> assemblies, string? packageNamingPolicy, HashSet frameworkAssemblyNames) + (List peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList assemblies, string? packageNamingPolicy, HashSet frameworkAssemblyNames) { using var scanner = new JavaPeerScanner (packageNamingPolicy, logger, frameworkAssemblyNames); var peers = scanner.Scan (assemblies); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs index 9a03ef4ff21..96a4a9f104c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs @@ -135,7 +135,7 @@ public override bool RunTask () Directory.CreateDirectory (JavaSourceOutputDirectory); var peReaders = new List (); - var assemblies = new List<(string Name, PEReader Reader)> (); + var assemblies = new List (); TrimmableTypeMapResult? result = null; try { foreach (var (path, isFrameworkAssembly) in assemblyInputs) { @@ -143,7 +143,7 @@ public override bool RunTask () peReaders.Add (peReader); var mdReader = peReader.GetMetadataReader (); var assemblyName = mdReader.GetString (mdReader.GetAssemblyDefinition ().Name); - assemblies.Add ((assemblyName, peReader)); + assemblies.Add (new AssemblyInput (assemblyName, path, peReader)); if (isFrameworkAssembly) { frameworkAssemblyNames.Add (assemblyName); } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs index b3eba523be1..d793f87bc3b 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs @@ -55,7 +55,7 @@ public void Execute_AssemblyWithNoPeers_ReturnsEmpty () var testAssemblyPath = typeof (TrimmableTypeMapGeneratorTests).Assembly.Location; using var peReader = new PEReader (File.OpenRead (testAssemblyPath)); var result = CreateGenerator ().Execute ( - new List<(string, PEReader)> { ("TestAssembly", peReader) }, + [Input ("TestAssembly", peReader)], new Version (11, 0), new HashSet ()); Assert.Empty (result.GeneratedAssemblies); @@ -67,7 +67,7 @@ public void Execute_AssemblyWithNoPeers_ReturnsEmpty () public void Execute_WithTestFixtures_ProducesOutputs () { using var peReader = CreateTestFixturePEReader (); - var result = CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ()); + var result = CreateGenerator ().Execute ([Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet ()); Assert.NotEmpty (result.GeneratedAssemblies); Assert.NotEmpty (result.GeneratedJavaSources); Assert.Contains (result.GeneratedAssemblies, a => a.Name == "_Microsoft.Android.TypeMaps"); @@ -78,7 +78,7 @@ public void Execute_WithTestFixtures_ProducesOutputs () public void Execute_CollectsDeferredRegistrationTypes_ForAllApplicationAndInstrumentationSubtypes () { using var peReader = CreateTestFixturePEReader (); - var result = CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ()); + var result = CreateGenerator ().Execute ([Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet ()); // Abstract Instrumentation/Application subtypes are included too: their native // methods (e.g. n_OnCreate, n_OnStart) are declared on the abstract base class @@ -139,7 +139,7 @@ public void CollectApplicationRegistrationTypes_ExcludesLegacyFrameworkDescendan [Fact] public void Execute_NullAssemblyList_Throws () { - IReadOnlyList<(string Name, PEReader Reader)>? n = null; + IReadOnlyList? n = null; #pragma warning disable CS8604 Assert.Throws (() => CreateGenerator ().Execute (n, new Version (11, 0), new HashSet ())); #pragma warning restore CS8604 @@ -149,7 +149,7 @@ public void Execute_NullAssemblyList_Throws () public void Execute_GeneratedAssembliesAreValidPE () { using var peReader = CreateTestFixturePEReader (); - var result = CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ()); + var result = CreateGenerator ().Execute ([Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet ()); foreach (var assembly in result.GeneratedAssemblies) { assembly.Content.Position = 0; using var vr = new PEReader (assembly.Content, PEStreamOptions.LeaveOpen); @@ -162,7 +162,7 @@ public void Execute_GeneratedAssembliesAreValidPE () public void Execute_JavaSourcesHaveCorrectStructure () { using var peReader = CreateTestFixturePEReader (); - var result = CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ()); + var result = CreateGenerator ().Execute ([Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet ()); foreach (var source in result.GeneratedJavaSources) Assert.Contains ("class ", source.Content); } @@ -172,7 +172,7 @@ public void Execute_FrameworkAssembly_GeneratesFrameworkJcwTypes () { using var peReader = CreateTestFixturePEReader (); var result = CreateGenerator ().Execute ( - new List<(string, PEReader)> { ("Mono.Android", peReader) }, + [Input ("Mono.Android", peReader)], new Version (11, 0), new HashSet (StringComparer.OrdinalIgnoreCase) { "Mono.Android" }); @@ -196,7 +196,7 @@ public void Execute_ManifestPlaceholdersAreResolvedBeforeRooting () """); var result = CreateGenerator ().Execute ( - new List<(string, PEReader)> { ("TestFixtures", peReader) }, + [Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet (), useSharedTypemapUniverse: false, @@ -217,6 +217,8 @@ public void Execute_ManifestPlaceholdersAreResolvedBeforeRooting () TrimmableTypeMapGenerator CreateGenerator (List warnings) => new (new TestTrimmableTypeMapLogger (logMessages, warnings)); + static AssemblyInput Input (string name, PEReader reader) => new (name, "", reader); + [Theory] [InlineData ("com/example/MyActivity", "com.example.MyActivity", "com.example", "activity", "com.example.MyActivity")] [InlineData ("com/example/MyActivity", "com.example.MyActivity", "com.example", "activity", ".MyActivity")] diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index 28b2ad29b2a..cbeddcc7cb3 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -45,6 +45,17 @@ static List FindCtorMemberRefs (MetadataReader reader, st static MemberReferenceHandle FindCtorMemberRef (MetadataReader reader, string parentNamespace, string parentName, params string [] parameterTypes) => FindCtorMemberRefs (reader, parentNamespace, parentName, parameterTypes).First (); + static string GetTypeDefOrRefName (MetadataReader reader, int codedToken) + { + int tag = codedToken & 0x3; + int row = codedToken >> 2; + return tag switch { + 0 => reader.GetString (reader.GetTypeDefinition (MetadataTokens.TypeDefinitionHandle (row)).Name), + 1 => reader.GetString (reader.GetTypeReference (MetadataTokens.TypeReferenceHandle (row)).Name), + _ => throw new InvalidOperationException ($"Unexpected TypeDefOrRefOrSpec tag {tag}."), + }; + } + static TypeRefData TypeRef (string managedTypeName) => new () { ManagedTypeName = managedTypeName, AssemblyName = GetAssemblyNameForManagedType (managedTypeName), @@ -106,6 +117,55 @@ public void Generate_CreatesProxyTypes () Assert.Contains (proxyTypes, t => reader.GetString (t.Name) == "Java_Lang_Object_Proxy"); } + [Theory] + [InlineData ("my/app/GenericSelectableList")] + [InlineData ("my/app/GenericForwardingSelectableList")] + public void Generate_InheritedGenericBaseCallback_UsesConstructedBaseMemberRef (string javaName) + { + var peer = ScanFixtures ().Single (p => p.JavaName == javaName); + using var stream = GenerateAssembly (new [] { peer }); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var member = reader.MemberReferences + .Select (h => reader.GetMemberReference (h)) + .Single (m => reader.GetString (m.Name) == "n_SetSelection_I"); + + Assert.Equal (HandleKind.TypeSpecification, member.Parent.Kind); + var typeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) member.Parent); + var blob = reader.GetBlobReader (typeSpec.Signature); + + Assert.Equal (0x15, blob.ReadByte ()); // ELEMENT_TYPE_GENERICINST + Assert.Equal (0x12, blob.ReadByte ()); // ELEMENT_TYPE_CLASS + Assert.Equal ("GenericSelectionHost`1", GetTypeDefOrRefName (reader, blob.ReadCompressedInteger ())); + Assert.Equal (1, blob.ReadCompressedInteger ()); + Assert.Equal (0x0E, blob.ReadByte ()); // ELEMENT_TYPE_STRING + } + + [Fact] + public void Generate_InheritedGenericBaseCallback_UsesValueTypeGenericArgument () + { + var peer = ScanFixtures ().Single (p => p.JavaName == "my/app/EnumSelectableList"); + using var stream = GenerateAssembly (new [] { peer }); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var member = reader.MemberReferences + .Select (h => reader.GetMemberReference (h)) + .Single (m => reader.GetString (m.Name) == "n_SetSelection_I"); + + Assert.Equal (HandleKind.TypeSpecification, member.Parent.Kind); + var typeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) member.Parent); + var blob = reader.GetBlobReader (typeSpec.Signature); + + Assert.Equal (0x15, blob.ReadByte ()); // ELEMENT_TYPE_GENERICINST + Assert.Equal (0x12, blob.ReadByte ()); // ELEMENT_TYPE_CLASS + Assert.Equal ("GenericValueTypeSelectionHost`1", GetTypeDefOrRefName (reader, blob.ReadCompressedInteger ())); + Assert.Equal (1, blob.ReadCompressedInteger ()); + Assert.Equal (0x11, blob.ReadByte ()); // ELEMENT_TYPE_VALUETYPE + Assert.Equal ("SelectionMode", GetTypeDefOrRefName (reader, blob.ReadCompressedInteger ())); + } + [Fact] public void Generate_ProxyType_HasCtorAndCreateInstance () { @@ -708,6 +768,76 @@ public void Generate_IdenticalContent_ProducesIdenticalMVIDs () Assert.Equal (mvid1, mvid2); } + [Fact] + public void TypeRefData_Equality_ComparesGenericArgumentsByValue () + { + var left = new TypeRefData { + ManagedTypeName = "Test.GenericBase`1", + AssemblyName = "TestAsm", + GenericArguments = [ + TypeRef ("System.String"), + ], + }; + var right = new TypeRefData { + ManagedTypeName = "Test.GenericBase`1", + AssemblyName = "TestAsm", + GenericArguments = [ + TypeRef ("System.String"), + ], + }; + var different = right with { + GenericArguments = [ + TypeRef ("System.Object"), + ], + }; + + Assert.Equal (left, right); + Assert.Equal (left.GetHashCode (), right.GetHashCode ()); + Assert.NotEqual (left, different); + } + + [Fact] + public void Generate_DifferentConstructedCallbackTypes_ProducesDifferentMVIDs () + { + var peer = MakePeerWithActivation ("test/TypeA", "Test.TypeA", "TestAsm"); + var stringPeer = peer with { + MarshalMethods = [ + CreateInheritedGenericCallback (TypeRef ("System.String")), + ], + }; + var objectPeer = peer with { + MarshalMethods = [ + CreateInheritedGenericCallback (TypeRef ("System.Object")), + ], + }; + + using var stream1 = GenerateAssembly (new [] { stringPeer }, "SameName"); + using var stream2 = GenerateAssembly (new [] { objectPeer }, "SameName"); + + using var pe1 = new PEReader (stream1); + using var pe2 = new PEReader (stream2); + var mvid1 = pe1.GetMetadataReader ().GetGuid (pe1.GetMetadataReader ().GetModuleDefinition ().Mvid); + var mvid2 = pe2.GetMetadataReader ().GetGuid (pe2.GetMetadataReader ().GetModuleDefinition ().Mvid); + + Assert.NotEqual (mvid1, mvid2); + + static MarshalMethodInfo CreateInheritedGenericCallback (TypeRefData genericArgument) => new () { + JniName = "setValue", + NativeCallbackName = "n_SetValue_Ljava_lang_Object", + JniSignature = "(Ljava/lang/Object;)V", + ManagedMethodName = "SetValue", + DeclaringTypeName = "Test.GenericBase`1", + DeclaringAssemblyName = "TestAsm", + DeclaringType = new TypeRefData { + ManagedTypeName = "Test.GenericBase`1", + AssemblyName = "TestAsm", + GenericArguments = [ + genericArgument, + ], + }, + }; + } + [Fact] public void Generate_AcwProxy_HasRegisterNativesAndUcoMethods () { @@ -1542,6 +1672,44 @@ public void Generate_ExportProxy_UnsupportedManagedShapesThrow (string parameter Assert.Contains (expectedMessage, ex.Message); } + [Fact] + public void Generate_ExportProxy_StructuredGenericArgumentThrows () + { + var peer = MakePeerWithActivation ("my/app/UnsupportedExport", "MyApp.UnsupportedExport", "App") with { + DoNotGenerateAcw = false, + MarshalMethods = new List { + new () { + JniName = "badExport", + NativeCallbackName = "n_badExport", + JniSignature = "(Ljava/lang/Object;)V", + ManagedMethodName = "BadExport", + ManagedParameterTypes = new [] { + new TypeRefData { + ManagedTypeName = "System.Collections.Generic.List`1", + AssemblyName = "System.Collections", + GenericArguments = new [] { + new TypeRefData { + ManagedTypeName = "System.String", + AssemblyName = "System.Runtime", + }, + }, + }, + }, + ManagedReturnType = new TypeRefData { + ManagedTypeName = "System.Void", + AssemblyName = "System.Runtime", + }, + IsExport = true, + }, + }, + }; + + var ex = Assert.Throws (() => { + using var stream = GenerateAssembly (new [] { peer }, "UnsupportedStructuredGenericExport"); + }); + Assert.Contains ("generic", ex.Message); + } + [Fact] public void Generate_MultipleAcwProxies_DeduplicatesUtf8Strings () { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs index f1e2cbd0ab8..81e33f417f5 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Xunit; @@ -125,6 +126,66 @@ public void OverrideAcrossGenericIntermediateMcwBase_Detected () var setSelection = Assert.Single (peer.MarshalMethods, m => m.JniName == "setSelection"); Assert.Equal ("(I)V", setSelection.JniSignature); Assert.Equal ("GetSetSelection_IHandler", setSelection.Connector); + var declaringType = setSelection.DeclaringType; + Assert.NotNull (declaringType); + if (declaringType is null) { + throw new InvalidOperationException ("Expected override declaring type."); + } + Assert.Equal ("MyApp.GenericSelectionHost`1", declaringType.ManagedTypeName); + var argument = Assert.Single (declaringType.GenericArguments); + Assert.Equal ("System.String", argument.ManagedTypeName); + } + + [Fact] + public void OverrideAcrossGenericForwardingIntermediateMcwBase_Detected () + { + var peer = FindFixtureByJavaName ("my/app/GenericForwardingSelectableList"); + var setSelection = Assert.Single (peer.MarshalMethods, m => m.JniName == "setSelection"); + Assert.Equal ("(I)V", setSelection.JniSignature); + Assert.Equal ("GetSetSelection_IHandler", setSelection.Connector); + var declaringType = setSelection.DeclaringType; + Assert.NotNull (declaringType); + if (declaringType is null) { + throw new InvalidOperationException ("Expected override declaring type."); + } + Assert.Equal ("MyApp.GenericSelectionHost`1", declaringType.ManagedTypeName); + var argument = Assert.Single (declaringType.GenericArguments); + Assert.Equal ("System.String", argument.ManagedTypeName); + } + + [Fact] + public void OverrideAcrossGenericBaseWithSubstitutedParameter_Detected () + { + var peer = FindFixtureByJavaName ("my/app/StringValueList"); + var applyValue = Assert.Single (peer.MarshalMethods, m => m.JniName == "applyValue"); + Assert.Equal ("(Ljava/lang/Object;)V", applyValue.JniSignature); + Assert.Equal ("GetApplyValue_Ljava_lang_Object_Handler", applyValue.Connector); + var declaringType = applyValue.DeclaringType; + Assert.NotNull (declaringType); + if (declaringType is null) { + throw new InvalidOperationException ("Expected override declaring type."); + } + Assert.Equal ("MyApp.GenericValueHost`1", declaringType.ManagedTypeName); + var argument = Assert.Single (declaringType.GenericArguments); + Assert.Equal ("System.String", argument.ManagedTypeName); + } + + [Fact] + public void OverrideAcrossGenericValueTypeArgument_PreservesValueTypeArgument () + { + var peer = FindFixtureByJavaName ("my/app/EnumSelectableList"); + var setSelection = Assert.Single (peer.MarshalMethods, m => m.JniName == "setSelection"); + Assert.Equal ("(I)V", setSelection.JniSignature); + Assert.Equal ("GetSetSelection_IHandler", setSelection.Connector); + var declaringType = setSelection.DeclaringType; + Assert.NotNull (declaringType); + if (declaringType is null) { + throw new InvalidOperationException ("Expected override declaring type."); + } + Assert.Equal ("MyApp.GenericValueTypeSelectionHost`1", declaringType.ManagedTypeName); + var argument = Assert.Single (declaringType.GenericArguments); + Assert.Equal ("MyApp.SelectionMode", argument.ManagedTypeName); + Assert.True (argument.IsValueType); } [Fact] diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs index ca0b8f4adf8..76d2cd61e2f 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs @@ -1013,6 +1013,26 @@ public abstract class GenericSelectionContainer : GenericSelectionHost protected GenericSelectionContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } } + /// + /// Generic intermediate MCW base that forwards its generic parameter to the + /// registered generic base. This mirrors Xamarin.Forms renderer hierarchies + /// such as VisualElementRenderer<TElement>. + /// + [Register ("my/app/GenericForwardingSelectionContainer", DoNotGenerateAcw = true)] + public abstract class GenericForwardingSelectionContainer : GenericSelectionHost where T : class + { + protected GenericForwardingSelectionContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + } + + /// + /// Non-generic MCW base that closes the generic forwarding base. + /// + [Register ("my/app/StringForwardingSelectionContainer", DoNotGenerateAcw = true)] + public abstract class StringForwardingSelectionContainer : GenericForwardingSelectionContainer + { + protected StringForwardingSelectionContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + } + /// /// Overrides a registered method declared above the first MCW base in the hierarchy. /// @@ -1035,6 +1055,91 @@ protected GenericSelectableList (IntPtr handle, JniHandleOwnership transfer) : b public override void SetSelection (int position) { } } + /// + /// Overrides a registered method declared above a generic base that forwards + /// type parameters through another generic base. + /// + [Register ("my/app/GenericForwardingSelectableList")] + public class GenericForwardingSelectableList : StringForwardingSelectionContainer + { + protected GenericForwardingSelectableList (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + + public override void SetSelection (int position) { } + } + + /// + /// Generic base whose registered method uses its type parameter in the managed + /// signature. Override detection must compare this as string when the + /// base is closed over string. + /// + [Register ("my/app/GenericValueHost", DoNotGenerateAcw = true)] + public abstract class GenericValueHost : Java.Lang.Object where T : class + { + protected GenericValueHost (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + + [Register ("applyValue", "(Ljava/lang/Object;)V", "GetApplyValue_Ljava_lang_Object_Handler")] + public abstract void ApplyValue (T value); + } + + /// + /// Intermediate MCW base that closes a registered generic method parameter. + /// + [Register ("my/app/StringValueContainer", DoNotGenerateAcw = true)] + public abstract class StringValueContainer : GenericValueHost + { + protected StringValueContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + } + + /// + /// Overrides a registered method whose base managed signature contains a + /// substituted generic type parameter. + /// + [Register ("my/app/StringValueList")] + public class StringValueList : StringValueContainer + { + protected StringValueList (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + + public override void ApplyValue (string value) { } + } + + public enum SelectionMode { + Single, + Multiple, + } + + /// + /// Generic base closed over an enum argument so TypeSpec emission must encode + /// the argument as ELEMENT_TYPE_VALUETYPE. + /// + [Register ("my/app/GenericValueTypeSelectionHost", DoNotGenerateAcw = true)] + public abstract class GenericValueTypeSelectionHost : Java.Lang.Object + { + protected GenericValueTypeSelectionHost (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + + [Register ("setSelection", "(I)V", "GetSetSelection_IHandler")] + public abstract void SetSelection (int position); + } + + /// + /// Intermediate MCW base that closes a registered generic base over an enum. + /// + [Register ("my/app/EnumSelectionContainer", DoNotGenerateAcw = true)] + public abstract class EnumSelectionContainer : GenericValueTypeSelectionHost + { + protected EnumSelectionContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + } + + /// + /// Overrides a registered method declared on a constructed generic enum base. + /// + [Register ("my/app/EnumSelectableList")] + public class EnumSelectableList : EnumSelectionContainer + { + protected EnumSelectableList (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + + public override void SetSelection (int position) { } + } + /// /// Has a ctor with unsigned primitive params to test JNI mapping. ///