diff --git a/Documentation/docs-mobile/TOC.yml b/Documentation/docs-mobile/TOC.yml index 1244c066b05..03bfd8e5f0e 100644 --- a/Documentation/docs-mobile/TOC.yml +++ b/Documentation/docs-mobile/TOC.yml @@ -332,6 +332,8 @@ href: messages/xa4249.md - name: XA4258 href: messages/xa4258.md + - name: XA4263 + href: messages/xa4263.md - name: XA4301 href: messages/xa4301.md - name: XA4302 diff --git a/Documentation/docs-mobile/messages/index.md b/Documentation/docs-mobile/messages/index.md index dabb47571de..4c17d558c1c 100644 --- a/Documentation/docs-mobile/messages/index.md +++ b/Documentation/docs-mobile/messages/index.md @@ -229,6 +229,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + [XA4255](xa4255.md): Generated trimmable type map Java source '{path}' was not found. + [XA4256](xa4256.md): Skipping Java peer type '{type}' from assembly '{assembly}' because referenced type '{referencedType}' from assembly '{referencedAssembly}' could not be resolved in '{path}'. This type will not be included in the trimmable type map. + [XA4258](xa4258.md): Java name '{name}' contains reserved Java identifier '{identifier}'. Change the package or type name. ++ [XA4263](xa4263.md): The exported member '{member}' has unsupported signature type '{type}'. Use a Java peer type, a supported managed-to-Java mapping, or [ExportParameter]. + XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI. + [XA4301](xa4301.md): Apk already contains the item `xxx`. + [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex} diff --git a/Documentation/docs-mobile/messages/xa4263.md b/Documentation/docs-mobile/messages/xa4263.md new file mode 100644 index 00000000000..fa454a2957a --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4263.md @@ -0,0 +1,28 @@ +--- +title: .NET for Android error XA4263 +description: XA4263 error code +ms.date: 08/31/2026 +f1_keywords: + - "XA4263" +--- + +# .NET for Android error XA4263 + +## Example message + +```text +error XA4263: The exported member 'Peer.Get' has unsupported signature type 'ManagedValue'. Use a Java peer type, a supported managed-to-Java mapping, or [ExportParameter]. +``` + +## Issue + +An `[Export]` method or `[ExportField]` initializer uses a managed parameter or return type that has no Java type mapping. +The type cannot be represented safely in the generated Java declaration. +This error also occurs when `[ExportParameter]` specifies a Java mapping that is incompatible with the managed type. + +## Solution + +Use a primitive, string, array, Java peer, or another supported managed-to-Java type. +Apply input/output stream mappings only to `Stream`, and XML parser mappings only to `XmlReader`. +Do not apply these scalar mappings to arrays or other wrappers around those types. +The types must be the framework `System.IO.Stream` and `System.Xml.XmlReader` types, not user-defined types with the same full name. diff --git a/Xamarin.Android.slnx b/Xamarin.Android.slnx index 9ec9f9fcdb6..4c1a64bd0c8 100644 --- a/Xamarin.Android.slnx +++ b/Xamarin.Android.slnx @@ -60,8 +60,10 @@ + + diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs index 185c2637dcf..fdaca8c7f46 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs @@ -118,6 +118,10 @@ static void WriteUcoConstructor (this BinaryWriter writer, UcoConstructorData co foreach (var parameterType in constructor.ManagedParameterTypes) { writer.WriteTypeRef (parameterType); } + writer.Write (constructor.ParameterKinds.Count); + foreach (var parameterKind in constructor.ParameterKinds) { + writer.Write ((int) parameterKind); + } } static void WriteNativeRegistration (this BinaryWriter writer, NativeRegistrationData registration) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index 054a44f1795..03100fa3cae 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -407,6 +407,11 @@ sealed record UcoConstructorData /// to the corresponding managed type before calling the user ctor. /// public IReadOnlyList ManagedParameterTypes { get; init; } = []; + + /// + /// Per-parameter [ExportParameter] kinds for constructor argument marshalling. + /// + public IReadOnlyList ParameterKinds { get; init; } = []; } /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index f77ee24e0b8..65d6a024180 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -380,6 +380,7 @@ static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy) AssemblyName = peer.AssemblyName, }, ManagedParameterTypes = ctor.ManagedParameterTypes, + ParameterKinds = ctor.ManagedParameterExportKinds, HasMatchingManagedCtor = ctor.HasMatchingManagedCtor, }); } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 56a160a7914..4633e1df0b5 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -1182,7 +1182,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy if (uco.HasMatchingManagedCtor) { var ctorRef = AddManagedCtorRef (targetTypeRef, uco.ManagedParameterTypes); var managedCtorHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, - enc => EmitManagedConstructorActivation (enc, targetTypeRef, ctorRef, uco.ManagedParameterTypes, jniParams), + enc => EmitManagedConstructorActivation (enc, targetTypeRef, ctorRef, uco.ManagedParameterTypes, uco.ParameterKinds, jniParams), blob => EncodeUcoConstructorLocals_DefaultConstructor (blob, targetTypeRef)); AddUnmanagedCallersOnlyAttribute (managedCtorHandle); return managedCtorHandle; @@ -1278,6 +1278,7 @@ void EmitManagedConstructorActivation ( EntityHandle targetTypeRef, MemberReferenceHandle ctorRef, IReadOnlyList managedParameterTypes, + IReadOnlyList parameterKinds, IReadOnlyList jniParams) { var havePeer = enc.DefineLabel (); @@ -1303,7 +1304,8 @@ void EmitManagedConstructorActivation ( enc.MarkLabel (havePeer); enc.LoadLocal (4); for (int i = 0; i < managedParameterTypes.Count; i++) { - EmitManagedConstructorArgument (enc, managedParameterTypes [i], jniParams [i], i + 2); + var parameterKind = i < parameterKinds.Count ? parameterKinds [i] : ExportParameterKindInfo.Unspecified; + EmitManagedConstructorArgument (enc, managedParameterTypes [i], parameterKind, jniParams [i], i + 2); } enc.Call (ctorRef, managedParameterTypes.Count, isInstance: true); @@ -1383,53 +1385,14 @@ void EmitUcoConstructorBodyWithMarshal (TrackedInstructionEncoder encoder, Contr cfb.AddFinallyRegion (tryStart, finallyStart, finallyStart, afterAll); } - void EmitManagedConstructorArgument (TrackedInstructionEncoder encoder, TypeRefData managedType, JniParamKind jniKind, int argumentIndex) + void EmitManagedConstructorArgument ( + TrackedInstructionEncoder encoder, + TypeRefData managedType, + ExportParameterKindInfo exportKind, + JniParamKind jniKind, + int argumentIndex) { - if (managedType.ManagedTypeName == "System.Boolean") { - encoder.LoadArgument (argumentIndex); - encoder.LoadConstantI4 (0); - encoder.OpCode (ILOpCode.Cgt_un); - return; - } - - if (jniKind != JniParamKind.Object) { - encoder.LoadArgument (argumentIndex); - return; - } - - if (managedType.ManagedTypeName == "System.String") { - encoder.LoadArgument (argumentIndex); - encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer - encoder.Call (_jniEnvGetStringRef, parameterCount: 2, returnsValue: true); - return; - } - - if (TryGetSzArrayElementType (managedType.ManagedTypeName, out var elementType)) { - var arrayType = ResolveManagedTypeHandle (managedType.ManagedTypeName, managedType.AssemblyName); - var elementTypeHandle = ResolveManagedTypeHandle (elementType, managedType.AssemblyName); - - encoder.LoadArgument (argumentIndex); - encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer - encoder.LoadToken (elementTypeHandle); - encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); - encoder.Call (_jniEnvGetArrayRef, parameterCount: 3, returnsValue: true); - encoder.CastClass (arrayType); - return; - } - - encoder.LoadArgument (argumentIndex); - encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer - if (managedType.ManagedTypeName == "System.Object") { - encoder.OpCode (ILOpCode.Ldnull); - encoder.Call (_javaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true); - return; - } - - var managedTypeHandle = ResolveManagedTypeHandle (managedType.ManagedTypeName, managedType.AssemblyName); - encoder.LoadToken (managedTypeHandle); - encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); - encoder.Call (_javaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true); - encoder.CastClass (managedTypeHandle); + GetExportMethodDispatchEmitter ().LoadManagedArgument (encoder, managedType, exportKind, jniKind, argumentIndex); } EntityHandle ResolveRuntimeTypeSpec (RuntimeTypeSpec type) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs index 879bf42aafc..edd36592f83 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs @@ -23,8 +23,10 @@ void LogUnresolvableJavaPeerSkippedWarning ( void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName); void LogInvalidJavaNameError (string javaName, string invalidIdentifier); void LogExportFieldWithParametersError (); + void LogExportOnGenericTypeError (); void LogExportFieldOnGenericTypeError (); void LogExportFieldReturnsVoidError (); + void LogUnsupportedExportSignatureError (string memberName, string managedTypeName); void LogCustomJavaObjectError (string managedTypeName); void LogCustomJavaObjectWarning (string managedTypeName); } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs index 30ca13c1b06..30e340ef367 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs @@ -30,6 +30,7 @@ sealed class AssemblyIndex : IDisposable public MetadataReader Reader { get; } public string AssemblyName { get; } + public string MetadataAssemblyName { get; } public string AssemblyPath { get; } internal TypeRefSignatureTypeProvider TypeRefSignatureProvider { get; } @@ -53,6 +54,11 @@ sealed class AssemblyIndex : IDisposable /// public HashSet ExportedTypeNames { get; } = new (StringComparer.Ordinal); + /// + /// Maps forwarded managed type names to the target assembly simple name. + /// + public Dictionary ForwardedTypeAssemblies { get; } = new (StringComparer.Ordinal); + /// /// True iff the assembly's metadata mentions /// Java.Interop.JniAddNativeMethodRegistrationAttribute (as a @@ -77,6 +83,7 @@ sealed class AssemblyIndex : IDisposable valueTypeReferences = new TypeRefData? [reader.TypeReferences.Count + 1]; Reader = reader; AssemblyName = assemblyName; + MetadataAssemblyName = reader.GetString (reader.GetAssemblyDefinition ().Name); AssemblyPath = assemblyPath; TypeRefSignatureProvider = new TypeRefSignatureTypeProvider (this); } @@ -107,7 +114,12 @@ void Build () foreach (var exportedTypeHandle in Reader.ExportedTypes) { var exportedType = Reader.GetExportedType (exportedTypeHandle); - ExportedTypeNames.Add (GetExportedTypeFullName (exportedType)); + var fullName = GetExportedTypeFullName (exportedType); + ExportedTypeNames.Add (fullName); + var forwardedAssemblyName = GetForwardedAssemblyName (exportedType); + if (forwardedAssemblyName is not null) { + ForwardedTypeAssemblies [fullName] = forwardedAssemblyName; + } } foreach (var typeHandle in Reader.TypeDefinitions) { @@ -146,6 +158,18 @@ string GetExportedTypeFullName (ExportedType exportedType) var ns = Reader.GetString (exportedType.Namespace); return MetadataTypeNameResolver.JoinNamespaceAndName (ns, name); } + + string? GetForwardedAssemblyName (ExportedType exportedType) + { + if (exportedType.Implementation.Kind == HandleKind.AssemblyReference) { + var assemblyReference = Reader.GetAssemblyReference ((AssemblyReferenceHandle) exportedType.Implementation); + return Reader.GetString (assemblyReference.Name); + } + if (exportedType.Implementation.Kind == HandleKind.ExportedType) { + return GetForwardedAssemblyName (Reader.GetExportedType ((ExportedTypeHandle) exportedType.Implementation)); + } + return null; + } } (RegisterInfo? register, TypeAttributeInfo? attrs) ParseAttributes (TypeDefinition typeDef) @@ -173,8 +197,6 @@ string GetExportedTypeFullName (ExportedType exportedType) registerInfo = registerInfo with { JniName = registerInfo.JniName.Replace ('.', '/') }; } else if (attrName == "JniTypeSignatureAttribute") { registerInfo = ParseJniTypeSignatureAttribute (ca); - } else if (attrName == "ExportAttribute") { - // [Export] is a method-level attribute; it is parsed at scan time by JavaPeerScanner } else if (IsKnownComponentAttribute (attrName)) { attrInfo ??= CreateTypeAttributeInfo (attrName); var value = DecodeAttribute (ca); diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs index 15497a95bf7..1a102926209 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs @@ -6,6 +6,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// enum ExportParameterKindInfo { + Invalid = -1, Unspecified = 0, InputStream = 1, OutputStream = 2, diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs index 86915bff704..114856936f9 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs @@ -368,6 +368,11 @@ public sealed record JavaConstructorInfo /// internal IReadOnlyList ManagedParameterTypes { get; init; } = []; + /// + /// Per-parameter [ExportParameter] kinds for constructor argument marshalling. + /// + internal IReadOnlyList ManagedParameterExportKinds { get; init; } = []; + /// /// True when this Java constructor has a matching public managed constructor on the target type. /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs index 6cf34b2693e..a401094dd30 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs @@ -61,15 +61,36 @@ internal JavaPeerScanner (string? packageNamingPolicy, ITrimmableTypeMapLogger? /// bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHandle handle, [NotNullWhen (true)] out AssemblyIndex? resolvedIndex) { - if (assemblyCache.TryGetValue (assemblyName, out resolvedIndex) && - resolvedIndex.TypesByFullName.TryGetValue (typeName, out handle)) { - return true; + var visitedAssemblies = new HashSet (StringComparer.Ordinal); + while (visitedAssemblies.Add (assemblyName) && + TryGetAssemblyIndex (assemblyName, out resolvedIndex)) { + if (resolvedIndex.TypesByFullName.TryGetValue (typeName, out handle)) { + return true; + } + if (!resolvedIndex.ForwardedTypeAssemblies.TryGetValue (typeName, out assemblyName)) { + break; + } } handle = default; resolvedIndex = null; return false; } + bool TryGetAssemblyIndex (string assemblyName, [NotNullWhen (true)] out AssemblyIndex? index) + { + if (assemblyCache.TryGetValue (assemblyName, out index)) { + return true; + } + foreach (var candidate in assemblyCache.Values) { + if (string.Equals (candidate.MetadataAssemblyName, assemblyName, StringComparison.Ordinal)) { + index = candidate; + return true; + } + } + index = null; + return false; + } + /// /// Resolves the type that declares the native n_* callback. When the [Register] connector /// names a declaring type (e.g. an *Invoker in another assembly) that type is used; @@ -677,10 +698,17 @@ static void SkipArrayShape (ref BlobReader reader) // Pass 1: collect methods with [Register], [Export], or [ExportField] directly on them foreach (var methodHandle in typeDef.GetMethods ()) { var methodDef = index.Reader.GetMethodDefinition (methodHandle); + var methodName = index.Reader.GetString (methodDef.Name); + if (methodName == ".cctor" && (methodDef.Attributes & MethodAttributes.Static) != 0) { + continue; + } if (!ValidateExportField (methodDef, index, isGenericType)) { continue; } + if (!ValidateExportSignature (methodDef, index, isGenericType)) { + continue; + } // Check for [ExportField] — produces both a marshal method AND a field CollectExportField (methodDef, index, fields); @@ -750,6 +778,16 @@ static bool IsExportFieldAttribute (CustomAttribute attribute, AssemblyIndex ind return AssemblyIndex.IsCustomAttributeMatch (attribute, index.Reader, "Java.Interop", "ExportFieldAttribute"); } + static bool IsExportAttribute (CustomAttribute attribute, AssemblyIndex index) + { + return AssemblyIndex.IsCustomAttributeMatch (attribute, index.Reader, "Java.Interop", "ExportAttribute"); + } + + static bool IsExportParameterAttribute (CustomAttribute attribute, AssemblyIndex index) + { + return AssemblyIndex.IsCustomAttributeMatch (attribute, index.Reader, "Java.Interop", "ExportParameterAttribute"); + } + bool ValidateExportField (MethodDefinition methodDef, AssemblyIndex index, bool isGenericType) { foreach (var caHandle in methodDef.GetCustomAttributes ()) { @@ -778,6 +816,85 @@ bool ValidateExportField (MethodDefinition methodDef, AssemblyIndex index, bool return true; } + bool ValidateExportSignature (MethodDefinition methodDef, AssemblyIndex index, bool isGenericType) + { + bool isExport = false; + bool isExportField = false; + foreach (var caHandle in methodDef.GetCustomAttributes ()) { + var ca = index.Reader.GetCustomAttribute (caHandle); + if (IsExportAttribute (ca, index)) { + isExport = true; + } else if (IsExportFieldAttribute (ca, index)) { + isExportField = true; + } + } + if (!isExport && !isExportField) { + return true; + } + + var methodName = index.Reader.GetString (methodDef.Name); + bool isConstructor = methodName == ".ctor"; + if (isConstructor && !isExport) { + return true; + } + if (!isConstructor && isExport && isGenericType) { + logger?.LogExportOnGenericTypeError (); + return false; + } + + var sig = methodDef.DecodeSignature (index.TypeRefSignatureProvider, index); + var (parameterKinds, returnKind) = GetExportParameterKinds (methodDef, index, sig.ParameterTypes.Length); + var declaringType = index.Reader.GetTypeDefinition (methodDef.GetDeclaringType ()); + string declaringTypeName = MetadataTypeNameResolver.GetFullName (declaringType, index.Reader); + string memberName = methodName [0] == '.' + ? declaringTypeName + methodName + : $"{declaringTypeName}.{methodName}"; + + for (int i = 0; i < sig.ParameterTypes.Length; i++) { + if (isConstructor && IsOwnedByConstructorDiagnostics (sig.ParameterTypes [i])) { + continue; + } + if (!HasExportSignatureMapping (sig.ParameterTypes [i], parameterKinds [i])) { + logger?.LogUnsupportedExportSignatureError (memberName, sig.ParameterTypes [i].DisplayName); + return false; + } + } + if (isConstructor) { + return true; + } + if (!HasExportSignatureMapping (sig.ReturnType, returnKind)) { + logger?.LogUnsupportedExportSignatureError (memberName, sig.ReturnType.DisplayName); + return false; + } + return true; + } + + /// + /// PR #12567 owns XA4260 for signature shapes that no Java constructor can represent. + /// This layer only adds XA4263 for unresolved types and invalid [ExportParameter] pairs. + /// + internal static bool IsOwnedByConstructorDiagnostics (TypeRefData parameterType) + { + string managedTypeName = parameterType.ManagedTypeName; + if (managedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { + return IsOwnedByConstructorDiagnostics (parameterType with { + ManagedTypeName = managedTypeName.Substring (0, managedTypeName.Length - 2), + }); + } + return parameterType.GenericArguments.Count > 0 || + managedTypeName.StartsWith ("!", StringComparison.Ordinal) || + managedTypeName == "delegate*" || + managedTypeName.EndsWith ("&", StringComparison.Ordinal) || + managedTypeName.EndsWith ("*", StringComparison.Ordinal) || + (managedTypeName.EndsWith ("]", StringComparison.Ordinal) && + !managedTypeName.EndsWith ("[]", StringComparison.Ordinal)); + } + + internal bool HasExportSignatureMapping (TypeRefData managedType, ExportParameterKindInfo exportKind) + { + return TryManagedTypeToJniDescriptor (managedType, exportKind, out _); + } + static bool HasJniAddNativeMethodRegistrationAttribute (TypeDefinition typeDef, AssemblyIndex index) { const string JniAddNativeMethodRegistrationAttribute = "JniAddNativeMethodRegistrationAttribute"; @@ -1126,32 +1243,47 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index, } /// - /// Looks up a managed type name across loaded assemblies. If the type has + /// Looks up a managed type in its referenced assembly. If the type has /// [Register], returns "L<jniName>;". Otherwise returns null. /// + string? TryResolveJniObjectDescriptor (TypeRefData managedType) + { + if (!TryResolveType (managedType.ManagedTypeName, managedType.AssemblyName, out var handle, out var index)) { + return null; + } + return GetJniObjectDescriptor (handle, index); + } + + /// + /// Looks up an unqualified managed type name across loaded assemblies. + /// Used only by legacy-compatible constructor signature discovery. + /// string? TryResolveJniObjectDescriptor (string managedType) { foreach (var index in assemblyCache.Values) { if (index.TypesByFullName.TryGetValue (managedType, out var handle)) { - if (index.RegisterInfoByType.TryGetValue (handle, out var registerInfo)) { - return $"L{registerInfo.JniName};"; - } - - // User peer types (extend a Java peer but lack [Register]) - // get a CRC64-based JNI name in ScanAssembly. Mirror that here - // so [Export]/[ExportField] signatures referring to such types - // emit the correct peer descriptor instead of falling back to - // java/lang/Object. - var typeDef = index.Reader.GetTypeDefinition (handle); - if (ExtendsJavaPeer (handle, typeDef, index)) { - var (jniName, _) = ComputeAutoJniNames (typeDef, index); - return $"L{jniName};"; - } + return GetJniObjectDescriptor (handle, index); } } return null; } + string? GetJniObjectDescriptor (TypeDefinitionHandle handle, AssemblyIndex index) + { + if (index.RegisterInfoByType.TryGetValue (handle, out var registerInfo)) { + return $"L{registerInfo.JniName};"; + } + + // User peer types (extend a Java peer but lack [Register]) get a CRC64-based + // JNI name in ScanAssembly. Mirror that for exported signatures. + var typeDef = index.Reader.GetTypeDefinition (handle); + if (ExtendsJavaPeer (handle, typeDef, index)) { + var (jniName, _) = ComputeAutoJniNames (typeDef, index); + return $"L{jniName};"; + } + return null; + } + /// /// Resolves a `typeof(X)` argument captured as an assembly-qualified name /// (e.g. "Java.IO.IOException, Mono.Android, ...") to its JNI internal @@ -1202,17 +1334,12 @@ bool IsEnumOrEnumArray (string managedType, string? assemblyName = null) (TypeDefinition typeDef, AssemblyIndex index)? TryFindEnumTypeDefinition (string managedType, string? assemblyName = null) { - // Prefer the typed assembly hint so two assemblies with same-named types - // (one enum, one not) resolve deterministically — assemblyCache - // enumeration order is non-deterministic. - if (assemblyName is { Length: > 0 } && - assemblyCache.TryGetValue (assemblyName, out var hintedIndex) && - hintedIndex.TypesByFullName.TryGetValue (managedType, out var hintedHandle)) { - var hintedDef = hintedIndex.Reader.GetTypeDefinition (hintedHandle); - if (IsEnumType (hintedDef, hintedIndex)) { - return (hintedDef, hintedIndex); + if (assemblyName is { Length: > 0 }) { + if (!TryResolveType (managedType, assemblyName, out var resolvedHandle, out var resolvedIndex)) { + return null; } - // Hinted assembly had a same-named non-enum; keep scanning. + var resolvedType = resolvedIndex.Reader.GetTypeDefinition (resolvedHandle); + return IsEnumType (resolvedType, resolvedIndex) ? (resolvedType, resolvedIndex) : null; } foreach (var index in assemblyCache.Values) { @@ -1745,7 +1872,7 @@ bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index, return true; } - if (attrName == "ExportAttribute") { + if (IsExportAttribute (ca, index)) { (registerInfo, exportInfo) = ParseExportAttribute (ca, methodDef, index); return true; } @@ -1786,6 +1913,7 @@ bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index, (RegisterInfo registerInfo, ExportInfo exportInfo) ParseExportAttribute (CustomAttribute ca, MethodDefinition methodDef, AssemblyIndex index) { var value = index.DecodeAttribute (ca); + var managedName = index.Reader.GetString (methodDef.Name); // [Export("name")] or [Export] (uses method name) string? exportName = null; @@ -1828,7 +1956,10 @@ bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index, } if (string.IsNullOrEmpty (exportName)) { - exportName = index.Reader.GetString (methodDef.Name); + exportName = managedName; + } + if (managedName == ".ctor") { + exportName = ".ctor"; } string resolvedExportName = exportName ?? throw new InvalidOperationException ("Export name should not be null at this point."); @@ -1889,36 +2020,29 @@ static ExportParameterKindInfo GetExportParameterKind (Parameter parameter, Asse { foreach (var caHandle in parameter.GetCustomAttributes ()) { var ca = index.Reader.GetCustomAttribute (caHandle); - var attrName = index.GetCustomAttributeName (ca); - if (attrName != "ExportParameterAttribute") { + if (!IsExportParameterAttribute (ca, index)) { continue; } var value = index.DecodeAttribute (ca); - if (value.FixedArguments.Length > 0 && TryConvertExportParameterKind (value.FixedArguments [0].Value, out var ctorKind)) { - return ctorKind; + if (value.FixedArguments.Length > 0) { + return ConvertExportParameterKind (value.FixedArguments [0].Value); } foreach (var named in value.NamedArguments) { - if (named.Name == "Kind" && TryConvertExportParameterKind (named.Value, out var namedKind)) { - return namedKind; + if (named.Name == "Kind") { + return ConvertExportParameterKind (named.Value); } } + + return ExportParameterKindInfo.Invalid; } return ExportParameterKindInfo.Unspecified; } - static bool TryConvertExportParameterKind (object? value, out ExportParameterKindInfo kind) - { - if (value is int i && Enum.IsDefined (typeof (ExportParameterKindInfo), i)) { - kind = (ExportParameterKindInfo) i; - return true; - } - - kind = ExportParameterKindInfo.Unspecified; - return false; - } + static ExportParameterKindInfo ConvertExportParameterKind (object? value) => + value is int i ? (ExportParameterKindInfo) i : ExportParameterKindInfo.Invalid; string BuildJniSignatureFromManaged (MethodSignature sig, IReadOnlyList parameterKinds, ExportParameterKindInfo returnKind) { @@ -1942,11 +2066,18 @@ string BuildJniSignatureFromManaged (MethodSignature sig, IReadOnly { var managedName = index.Reader.GetString (methodDef.Name); var sig = methodDef.DecodeSignature (index.TypeRefSignatureProvider, index); - var jniSig = BuildJniSignatureFromManaged (sig, GetDefaultExportKinds (sig.ParameterTypes.Length), ExportParameterKindInfo.Unspecified); + var (parameterKinds, returnKind) = GetExportParameterKinds (methodDef, index, sig.ParameterTypes.Length); + var jniSig = BuildJniSignatureFromManaged (sig, parameterKinds, returnKind); return ( new RegisterInfo { JniName = managedName, Signature = jniSig, Connector = "__export__", DoNotGenerateAcw = false }, - new ExportInfo { ThrownNames = null, SuperArgumentsString = null, IsField = true } + new ExportInfo { + ThrownNames = null, + SuperArgumentsString = null, + IsField = true, + ParameterKinds = parameterKinds, + ReturnKind = returnKind, + } ); } @@ -1956,30 +2087,50 @@ string BuildJniSignatureFromManaged (MethodSignature sig, IReadOnly /// for types that cannot be resolved (used by [Export] signature computation). /// string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindInfo exportKind = ExportParameterKindInfo.Unspecified) + { + return TryManagedTypeToJniDescriptor (managedType, exportKind, out var descriptor) + ? descriptor + : "Ljava/lang/Object;"; + } + + bool TryManagedTypeToJniDescriptor ( + TypeRefData managedType, + ExportParameterKindInfo exportKind, + out string descriptor) { if (exportKind != ExportParameterKindInfo.Unspecified) { - return exportKind switch { - ExportParameterKindInfo.InputStream => "Ljava/io/InputStream;", - ExportParameterKindInfo.OutputStream => "Ljava/io/OutputStream;", - ExportParameterKindInfo.XmlPullParser => "Lorg/xmlpull/v1/XmlPullParser;", - ExportParameterKindInfo.XmlResourceParser => "Landroid/content/res/XmlResourceParser;", - _ => "Ljava/lang/Object;", - }; + return TryGetExportParameterDescriptor (managedType, exportKind, out descriptor); } - var primitive = TryGetPrimitiveJniDescriptor (managedType.ManagedTypeName); - if (primitive is not null) { - return primitive; + if (managedType.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { + var elementType = managedType with { + ManagedTypeName = managedType.ManagedTypeName.Substring (0, managedType.ManagedTypeName.Length - 2), + }; + if (TryManagedTypeToJniDescriptor (elementType, ExportParameterKindInfo.Unspecified, out var elementDescriptor)) { + descriptor = $"[{elementDescriptor}"; + return true; + } + descriptor = ""; + return false; } - if (managedType.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { - return $"[{ManagedTypeToJniDescriptor (managedType with { ManagedTypeName = managedType.ManagedTypeName.Substring (0, managedType.ManagedTypeName.Length - 2) })}"; + var primitive = TryGetPrimitiveJniDescriptor (managedType.ManagedTypeName); + if (primitive is not null && + IsSpecialManagedType ( + managedType, + managedType.ManagedTypeName, + "System.Runtime", + "System.Private.CoreLib", + "mscorlib")) { + descriptor = primitive; + return true; } // Try to resolve as a Java peer type with [Register] - var resolved = TryResolveJniObjectDescriptor (managedType.ManagedTypeName); + var resolved = TryResolveJniObjectDescriptor (managedType); if (resolved is not null) { - return resolved; + descriptor = resolved; + return true; } // Well-known interface types that legacy CallbackCode mapped explicitly @@ -1988,24 +2139,91 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI // live in System.Collections (no Java peer at all) and are wrapped at // runtime by JavaList/JavaDictionary/JavaCollection. var wellKnown = managedType.ManagedTypeName switch { - "Java.Lang.ICharSequence" => "Ljava/lang/CharSequence;", - "System.Collections.IList" => "Ljava/util/List;", - "System.Collections.IDictionary" => "Ljava/util/Map;", - "System.Collections.ICollection" => "Ljava/util/Collection;", + "Java.Lang.ICharSequence" when IsSpecialManagedType ( + managedType, + "Java.Lang.ICharSequence", + "Mono.Android") => + "Ljava/lang/CharSequence;", + "System.Collections.IList" when IsSpecialManagedType ( + managedType, + "System.Collections.IList", + "System.Runtime", + "System.Collections.NonGeneric", + "System.Private.CoreLib", + "mscorlib") => + "Ljava/util/List;", + "System.Collections.IDictionary" when IsSpecialManagedType ( + managedType, + "System.Collections.IDictionary", + "System.Runtime", + "System.Collections.NonGeneric", + "System.Private.CoreLib", + "mscorlib") => + "Ljava/util/Map;", + "System.Collections.ICollection" when IsSpecialManagedType ( + managedType, + "System.Collections.ICollection", + "System.Runtime", + "System.Collections.NonGeneric", + "System.Private.CoreLib", + "mscorlib") => + "Ljava/util/Collection;", _ => null, }; if (wellKnown is not null) { - return wellKnown; + descriptor = wellKnown; + return true; } // Enum parameters use their underlying primitive JNI ABI (matches legacy // CallbackCode behavior). var enumDescriptor = TryResolveEnumUnderlyingDescriptor (managedType.ManagedTypeName, managedType.AssemblyName); if (enumDescriptor is not null) { - return enumDescriptor; + descriptor = enumDescriptor; + return true; } - return "Ljava/lang/Object;"; + descriptor = ""; + return false; + } + + bool TryGetExportParameterDescriptor ( + TypeRefData managedType, + ExportParameterKindInfo exportKind, + out string descriptor) + { + descriptor = exportKind switch { + ExportParameterKindInfo.InputStream when IsSpecialManagedType (managedType, "System.IO.Stream", "System.Runtime", "System.Private.CoreLib") => + "Ljava/io/InputStream;", + ExportParameterKindInfo.OutputStream when IsSpecialManagedType (managedType, "System.IO.Stream", "System.Runtime", "System.Private.CoreLib") => + "Ljava/io/OutputStream;", + ExportParameterKindInfo.XmlPullParser when IsSpecialManagedType ( + managedType, + "System.Xml.XmlReader", + "System.Xml.ReaderWriter", + "System.Private.Xml") => + "Lorg/xmlpull/v1/XmlPullParser;", + ExportParameterKindInfo.XmlResourceParser when IsSpecialManagedType ( + managedType, + "System.Xml.XmlReader", + "System.Xml.ReaderWriter", + "System.Private.Xml") => + "Landroid/content/res/XmlResourceParser;", + _ => "", + }; + return descriptor.Length > 0; + } + + bool IsSpecialManagedType (TypeRefData managedType, string managedTypeName, params string [] assemblyNames) + { + if (!string.Equals (managedType.ManagedTypeName, managedTypeName, StringComparison.Ordinal)) { + return false; + } + if (assemblyNames.Contains (managedType.AssemblyName, StringComparer.Ordinal)) { + return true; + } + return TryResolveType (managedTypeName, managedType.AssemblyName, out _, out var resolvedIndex) && + assemblyNames.Contains (resolvedIndex.MetadataAssemblyName, StringComparer.Ordinal); } /// @@ -2569,7 +2787,10 @@ List BuildJavaConstructors (List marshal // Unsupported managed parameter shapes fail in model building for [Export] // constructors; non-[Export] registrations keep the legacy activation fallback. publicConstructors ??= GetSupportedPublicConstructors (typeDef, index); - var managedParams = TryGetMatchingPublicConstructorParameterTypes (publicConstructors, mm.JniSignature); + var managedParams = TryGetMatchingPublicConstructorParameterTypes ( + publicConstructors, + mm.JniSignature, + mm.ManagedParameterExportKinds); ctors.Add (new JavaConstructorInfo { JniSignature = mm.JniSignature, ConstructorIndex = ctorIndex, @@ -2578,6 +2799,7 @@ List BuildJavaConstructors (List marshal HasMatchingManagedCtor = managedParams != null, ManagedParameterTypes = managedParams ?? [], Annotations = mm.Annotations, + ManagedParameterExportKinds = mm.ManagedParameterExportKinds, }); ctorIndex++; } @@ -2598,10 +2820,7 @@ List GetSupportedPublicConstructors (TypeDefinition typeD var sig = methodDef.DecodeSignature (index.TypeRefSignatureProvider, genericContext: index); bool unsupported = false; foreach (var parameter in sig.ParameterTypes) { - var typeName = parameter.ManagedTypeName; - if (parameter.GenericArguments.Count > 0 || - typeName.EndsWith ("&", StringComparison.Ordinal) || - typeName.EndsWith ("*", StringComparison.Ordinal)) { + if (IsOwnedByConstructorDiagnostics (parameter)) { unsupported = true; break; } @@ -2625,27 +2844,54 @@ List GetSupportedPublicConstructors (TypeDefinition typeD /// parameter types. Returns when no compatible /// constructor exists. /// - static IReadOnlyList? TryGetMatchingPublicConstructorParameterTypes ( + IReadOnlyList? TryGetMatchingPublicConstructorParameterTypes ( List publicConstructors, - string jniSignature) + string jniSignature, + IReadOnlyList parameterKinds) { int closeParen = jniSignature.IndexOf (')'); if (closeParen < 0) { throw new ArgumentException ($"Malformed JNI signature '{jniSignature}': missing ')'"); } int parameterSignatureLength = closeParen + 1; + var jniParams = parameterKinds.Any (kind => kind != ExportParameterKindInfo.Unspecified) + ? JniSignatureHelper.ParseParameters (jniSignature) + : null; foreach (var constructor in publicConstructors) { - if (constructor.JniParameterSignature.Length != parameterSignatureLength || - string.CompareOrdinal (jniSignature, 0, constructor.JniParameterSignature, 0, parameterSignatureLength) != 0) { + if (jniParams is null) { + if (constructor.JniParameterSignature.Length != parameterSignatureLength || + string.CompareOrdinal (jniSignature, 0, constructor.JniParameterSignature, 0, parameterSignatureLength) != 0) { + continue; + } + } else if (!ManagedConstructorParametersMatchJniSignature (constructor.ParameterTypes, parameterKinds, jniParams)) { continue; } // If multiple overloads with the same JNI-compatible signature exist, match // the first public constructor in metadata order, like TypeManager.Activate. - return [.. constructor.ParameterTypes]; + return constructor.ParameterTypes; } return null; } + bool ManagedConstructorParametersMatchJniSignature ( + IReadOnlyList managedParams, + IReadOnlyList parameterKinds, + IReadOnlyList jniParams) + { + if (managedParams.Count != jniParams.Count) { + return false; + } + + for (int i = 0; i < managedParams.Count; i++) { + var parameterKind = i < parameterKinds.Count ? parameterKinds [i] : ExportParameterKindInfo.Unspecified; + var managedDescriptor = ManagedTypeToJniDescriptor (managedParams [i], parameterKind); + if (!string.Equals (managedDescriptor, jniParams [i].JniType, StringComparison.Ordinal)) { + return false; + } + } + return true; + } + /// /// Checks a single method for [ExportField] and adds a JavaFieldInfo if found. /// Called inline during Pass 1 to avoid a separate iteration. @@ -2671,7 +2917,8 @@ void CollectExportField (MethodDefinition methodDef, AssemblyIndex index, List + /// Looks up a localized string similar to The exported member '{0}' has unsupported signature type '{1}'. + /// Use a Java peer type, a supported managed-to-Java mapping, or [ExportParameter].. + /// + public static string XA4263 { + get { + return ResourceManager.GetString("XA4263", resourceCulture); + } + } + /// /// Looks up a localized string similar to Native library '{0}' will not be bundled because it has an unsupported ABI. Move this file to a directory with a valid Android ABI name such as 'libs/armeabi-v7a/'.. /// diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index aef2dbc527a..37d338c5f2b 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -1242,6 +1242,12 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS The following are literal names and should not be translated: Java. {0} - Java package or type name {1} - Java reserved keyword or restricted identifier + + + The exported member '{0}' has unsupported signature type '{1}'. Use a Java peer type, a supported managed-to-Java mapping, or [ExportParameter]. + The following are literal names and should not be translated: Java, [ExportParameter]. +{0} - Fully-qualified managed member name +{1} - Managed parameter or return type Command '{0}' failed.\n{1} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs index 1c2b02aa583..f9121daccb3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs @@ -61,10 +61,14 @@ public void LogInvalidJavaNameError (string javaName, string invalidIdentifier) log.LogCodedError ("XA4258", Properties.Resources.XA4258, javaName, invalidIdentifier); public void LogExportFieldWithParametersError () => log.LogCodedError ("XA4205", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4205); + public void LogExportOnGenericTypeError () => + log.LogCodedError ("XA4206", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4206); public void LogExportFieldOnGenericTypeError () => log.LogCodedError ("XA4207", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4207); public void LogExportFieldReturnsVoidError () => log.LogCodedError ("XA4208", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4208); + public void LogUnsupportedExportSignatureError (string memberName, string managedTypeName) => + log.LogCodedError ("XA4263", Properties.Resources.XA4263, memberName, managedTypeName); public void LogCustomJavaObjectError (string managedTypeName) => log.LogError ("{0}", $"XA4212: {string.Format (Properties.Resources.XA4212, managedTypeName)}"); public void LogCustomJavaObjectWarning (string managedTypeName) => 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 a426a5b7f0d..9c1df5990e5 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 @@ -95,6 +95,438 @@ public void Build_InvalidExportField_ReportsLegacyDiagnostic ( } } + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "arbitrary-parameter", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "arbitrary-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "arbitrary-parameter", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "arbitrary-return", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "arbitrary-return", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "arbitrary-return", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "arbitrary-field", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "arbitrary-field", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "arbitrary-field", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "generic-parameter", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "generic-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "generic-parameter", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "generic-instantiation", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "generic-instantiation", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "generic-instantiation", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "function-pointer", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "function-pointer", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "function-pointer", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "by-ref-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "by-ref-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "pointer-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "pointer-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "rectangular-array-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "rectangular-array-parameter", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "generic-declaring-type", "XA4206")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "generic-declaring-type", "XA4206")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "generic-declaring-type", "XA4206")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "mismatched-export-parameter", "XALNS7004")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "mismatched-export-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "mismatched-export-parameter", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "generic-export-parameter", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "generic-export-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "generic-export-parameter", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "function-pointer-export-parameter", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "function-pointer-export-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "function-pointer-export-parameter", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "mismatched-field-export-parameter", "XALNS7004")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "mismatched-field-export-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "mismatched-field-export-parameter", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "special-array-parameter", "success")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "special-array-parameter", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "special-array-parameter", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "special-array-return", "success")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "special-array-return", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "special-array-return", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "special-array-field", "success")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "special-array-field", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "special-array-field", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "special-xml-array-return", "success")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "special-xml-array-return", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "special-xml-array-return", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-static-constructor", "success")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-static-constructor", "success")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-static-constructor", "success")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-constructor-arbitrary", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-constructor-arbitrary", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-constructor-arbitrary", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-constructor-invalid-kind", "XALNS7004")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-constructor-invalid-kind", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-constructor-invalid-kind", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-constructor-valid-kind", "success")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-constructor-valid-kind", "success")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-constructor-valid-kind", "success")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-named-constructor-arbitrary", "XALNS7003")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-named-constructor-arbitrary", "XA4263")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-named-constructor-arbitrary", "XA4263")] + [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-named-constructor-valid-kind", "success")] + [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-named-constructor-valid-kind", "success")] + [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-named-constructor-valid-kind", "success")] + public void Build_ExportSignature_MatchesRuntimeClassification ( + string typeMapImplementation, + AndroidRuntime runtime, + string invalidShape, + string expectedCode) + { + bool isRelease = runtime == AndroidRuntime.NativeAOT; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + var (additionalTypes, member, marker, typeParameters) = invalidShape switch { + "arbitrary-parameter" => ( + "public sealed class ManagedOnly { }", + """[Export ("unsupported")] public void UnsupportedMember (ManagedOnly value) { }""", + "unsupported", + ""), + "arbitrary-return" => ( + "public sealed class ManagedOnly { }", + """[Export ("unsupported")] public ManagedOnly UnsupportedMember () => new ();""", + "unsupported", + ""), + "arbitrary-field" => ( + "public sealed class ManagedOnly { }", + """[ExportField ("UNSUPPORTED_FIELD")] public ManagedOnly UnsupportedMember () => new ();""", + "UNSUPPORTED_FIELD", + ""), + "generic-parameter" => ( + "", + """[Export ("unsupported")] public T UnsupportedMember (T value) => value;""", + "unsupported", + ""), + "generic-instantiation" => ( + "", + """[Export ("unsupported")] public List UnsupportedMember (List value) => value;""", + "unsupported", + ""), + "function-pointer" => ( + "", + """[Export ("unsupported")] public unsafe delegate* unmanaged UnsupportedMember (delegate* unmanaged value) => value;""", + "unsupported", + ""), + "by-ref-parameter" => ( + "", + """[Export ("unsupported")] public void UnsupportedMember (ref int value) { }""", + "unsupported", + ""), + "pointer-parameter" => ( + "", + """[Export ("unsupported")] public unsafe void UnsupportedMember (int* value) { }""", + "unsupported", + ""), + "rectangular-array-parameter" => ( + "", + """[Export ("unsupported")] public void UnsupportedMember (string [,] value) { }""", + "unsupported", + ""), + "generic-declaring-type" => ( + "", + """[Export ("unsupported")] public int UnsupportedMember () => 0;""", + "unsupported", + ""), + "mismatched-export-parameter" => ( + "public sealed class ManagedOnly { }", + """ + [Export ("unsupported")] + public ManagedOnly UnsupportedMember ( + [ExportParameter (ExportParameterKind.InputStream)] ManagedOnly value) + => value; + """, + "unsupported", + ""), + "generic-export-parameter" => ( + "", + """ + [Export ("unsupported")] + public T UnsupportedMember ( + [ExportParameter (ExportParameterKind.InputStream)] T value) + => value; + """, + "unsupported", + ""), + "function-pointer-export-parameter" => ( + "", + """ + [Export ("unsupported")] + public unsafe delegate* unmanaged UnsupportedMember ( + [ExportParameter (ExportParameterKind.InputStream)] delegate* unmanaged value) + => value; + """, + "unsupported", + ""), + "mismatched-field-export-parameter" => ( + "public sealed class ManagedOnly { }", + """ + [return: ExportParameter (ExportParameterKind.OutputStream)] + [ExportField ("UNSUPPORTED_FIELD")] + public ManagedOnly UnsupportedMember () => new (); + """, + "UNSUPPORTED_FIELD", + ""), + "special-array-parameter" => ( + "", + """ + [Export ("unsupported")] + public void UnsupportedMember ( + [ExportParameter (ExportParameterKind.InputStream)] Stream [] value) + { + } + """, + "unsupported", + ""), + "special-array-return" => ( + "", + """ + [return: ExportParameter (ExportParameterKind.OutputStream)] + [Export ("unsupported")] + public Stream [] UnsupportedMember () => []; + """, + "unsupported", + ""), + "special-array-field" => ( + "", + """ + [return: ExportParameter (ExportParameterKind.OutputStream)] + [ExportField ("UNSUPPORTED_FIELD")] + public Stream [] UnsupportedMember () => []; + """, + "UNSUPPORTED_FIELD", + ""), + "special-xml-array-return" => ( + "", + """ + [return: ExportParameter (ExportParameterKind.XmlPullParser)] + [Export ("unsupported")] + public XmlReader [] UnsupportedMember () => []; + """, + "unsupported", + ""), + "export-static-constructor" => ( + "", + """ + [Export] + static SignaturePeer () + { + } + """, + ".cctor", + ""), + "export-constructor-arbitrary" => ( + "public sealed class ManagedOnly { }", + """ + [Export (".ctor", SuperArgumentsString = "")] + public SignaturePeer (ManagedOnly value) + { + } + """, + "SignaturePeer", + ""), + "export-constructor-invalid-kind" => ( + "public sealed class ManagedOnly { }", + """ + [Export (".ctor", SuperArgumentsString = "")] + public SignaturePeer ( + [ExportParameter (ExportParameterKind.InputStream)] ManagedOnly value) + { + } + """, + "SignaturePeer", + ""), + "export-constructor-valid-kind" => ( + "", + """ + [Export (".ctor", SuperArgumentsString = "")] + public SignaturePeer ( + [ExportParameter (ExportParameterKind.InputStream)] Stream value) + { + } + """, + "java.io.InputStream", + ""), + "export-named-constructor-arbitrary" => ( + "public sealed class ManagedOnly { }", + """ + [Export ("notAConstructor", SuperArgumentsString = "")] + public SignaturePeer (ManagedOnly value) + { + } + """, + "SignaturePeer", + ""), + "export-named-constructor-valid-kind" => ( + "", + """ + [Export ("notAConstructor", SuperArgumentsString = "")] + public SignaturePeer ( + [ExportParameter (ExportParameterKind.InputStream)] Stream value) + { + } + """, + "java.io.InputStream", + ""), + _ => throw new InvalidOperationException ($"Unknown unsupported [Export] shape '{invalidShape}'."), + }; + var proj = new XamarinAndroidApplicationProject { + IsRelease = isRelease, + References = { + new BuildItem.Reference ("Mono.Android.Export"), + }, + }; + proj.SetRuntime (runtime); + proj.SetProperty ("AndroidTypeMapImplementation", typeMapImplementation); + proj.SetProperty ("AllowUnsafeBlocks", "true"); + proj.Sources.Add (new BuildItem.Source ("ExportSignatureValidation.cs") { + TextContent = () => $$""" + using System.Collections.Generic; + using System.IO; + using System.Xml; + using Android.Runtime; + using Java.Interop; + + namespace ExportSignatureValidation { + {{additionalTypes}} + + [Register ("com/example/exports/SignaturePeer")] + public class SignaturePeer{{typeParameters}} : Java.Lang.Object { + public SignaturePeer () { + } + + {{member}} + } + } + """, + }); + + using var builder = CreateApkBuilder (); + builder.ThrowOnBuildFailure = false; + var succeeded = builder.Build (proj); + if (expectedCode == "success") { + Assert.IsTrue (succeeded, $"{runtime}/{typeMapImplementation} should retain legacy build support for {invalidShape}."); + return; + } + + Assert.IsFalse (succeeded, $"{runtime}/{typeMapImplementation} should reject {invalidShape}."); + StringAssertEx.Contains ($"error {expectedCode}", builder.LastBuildOutput, $"The build should report {expectedCode}."); + if (expectedCode == "XA4263") { + var expectedMemberName = invalidShape.Contains ("constructor", StringComparison.Ordinal) + ? "ExportSignatureValidation.SignaturePeer.ctor" + : "ExportSignatureValidation.SignaturePeer.UnsupportedMember"; + StringAssertEx.Contains ( + expectedMemberName, + builder.LastBuildOutput, + "The diagnostic should identify the unsupported managed member." + ); + } + AssertNoExportOutputs (builder, marker); + } + + [TestCase (AndroidRuntime.CoreCLR)] + [TestCase (AndroidRuntime.NativeAOT)] + public void Build_SpecialMappingLookalikeTypes_ReportXA4263WithoutPartialOutputs (AndroidRuntime runtime) + { + bool isRelease = runtime == AndroidRuntime.NativeAOT; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = isRelease, + References = { + new BuildItem.Reference ("Mono.Android.Export"), + }, + }; + proj.SetRuntime (runtime); + proj.SetProperty ("AndroidTypeMapImplementation", "trimmable"); + proj.Sources.Add (new BuildItem.Source ("SpecialMappingLookalikes.cs") { + TextContent = () => """ + using Android.Runtime; + using Java.Interop; + + namespace System.IO { + public class Stream { + } + } + + namespace System.Xml { + public class XmlReader { + } + } + + namespace Java.Lang { + public interface ICharSequence { + } + } + + namespace System.Collections { + public interface IList { + } + + public interface IDictionary { + } + + public interface ICollection { + } + } + + namespace SpecialMappingLookalikes { + [Register ("com/example/exports/SpecialMappingLookalikePeer")] + public class SpecialMappingLookalikePeer : Java.Lang.Object { + [return: ExportParameter (ExportParameterKind.OutputStream)] + [Export ("invalidStream")] + public System.IO.Stream InvalidStream ( + [ExportParameter (ExportParameterKind.InputStream)] System.IO.Stream value) + => value; + + [return: ExportParameter (ExportParameterKind.XmlPullParser)] + [ExportField ("INVALID_XML_FIELD")] + public System.Xml.XmlReader InvalidXmlField () => new (); + + [Export ("invalidCharSequence")] + public Java.Lang.ICharSequence InvalidCharSequence (Java.Lang.ICharSequence value) => value; + + [Export ("invalidList")] + public System.Collections.IList InvalidList (System.Collections.IList value) => value; + + [Export ("invalidDictionary")] + public System.Collections.IDictionary InvalidDictionary (System.Collections.IDictionary value) => value; + + [Export ("invalidCollection")] + public System.Collections.ICollection InvalidCollection (System.Collections.ICollection value) => value; + + [Export ("notAConstructor", SuperArgumentsString = "")] + public SpecialMappingLookalikePeer ( + [ExportParameter (ExportParameterKind.InputStream)] System.IO.Stream value) + { + } + } + } + """, + }); + + using var builder = CreateApkBuilder (); + builder.ThrowOnBuildFailure = false; + Assert.IsFalse (builder.Build (proj), $"{runtime}/trimmable should reject special-mapping lookalike types."); + foreach (var memberName in new [] { + "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidStream", + "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidXmlField", + "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidCharSequence", + "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidList", + "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidDictionary", + "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidCollection", + "SpecialMappingLookalikes.SpecialMappingLookalikePeer.ctor", + }) { + Assert.IsTrue ( + builder.LastBuildOutput.Any (line => + line.Contains ("error XA4263", StringComparison.Ordinal) && + line.Contains (memberName, StringComparison.Ordinal)), + $"The build should report XA4263 for '{memberName}'." + ); + } + AssertNoExportOutputs (builder, "invalidStream"); + } + static XamarinAndroidApplicationProject CreateExportFieldValidationProject ( AndroidRuntime runtime, string typeMapImplementation, @@ -129,6 +561,73 @@ public ValidationPeer () { return proj; } + [Test] + public void FindExportOutputs_FindsEveryArtifactKind () + { + var root = Path.Combine (Root, "temp", TestName); + var typemapDirectory = Path.Combine (root, "typemap"); + var acwMapFile = Path.Combine (root, "acw-map.txt"); + var trimmableJavaDirectory = Path.Combine (typemapDirectory, "java", "com", "example"); + var llvmIrJavaDirectory = Path.Combine (root, "android", "src", "com", "example"); + Directory.CreateDirectory (trimmableJavaDirectory); + Directory.CreateDirectory (llvmIrJavaDirectory); + + var expected = new [] { + Path.Combine (typemapDirectory, "_Example.TypeMap.dll"), + Path.Combine (typemapDirectory, "_Microsoft.Android.TypeMaps.dll"), + acwMapFile, + Path.Combine (trimmableJavaDirectory, "TrimmablePeer.java"), + Path.Combine (llvmIrJavaDirectory, "LlvmIrPeer.java"), + }; + File.WriteAllBytes (expected [0], []); + File.WriteAllBytes (expected [1], []); + File.WriteAllText (expected [2], "Managed, Assembly;com/example/Peer"); + File.WriteAllText (expected [3], "public int VALUE = InitialValue ();"); + File.WriteAllText (expected [4], "public int VALUE = InitialValue ();"); + + CollectionAssert.AreEquivalent ( + expected, + FindExportOutputs (typemapDirectory, acwMapFile, Path.Combine (root, "android", "src"), "VALUE") + ); + } + + static void AssertNoExportOutputs (ProjectBuilder builder, string memberName) + { + var typemapDirectory = builder.Output.GetIntermediaryPath ("typemap"); + var acwMapFile = builder.Output.GetIntermediaryPath ("acw-map.txt"); + var androidSourceDirectory = builder.Output.GetIntermediaryPath (Path.Combine ("android", "src")); + var outputs = FindExportOutputs (typemapDirectory, acwMapFile, androidSourceDirectory, memberName); + Assert.IsEmpty ( + outputs, + "Invalid exported metadata should not produce typemap assemblies, ACW maps, or partial Java output:" + + Environment.NewLine + string.Join (Environment.NewLine, outputs) + ); + } + + static string [] FindExportOutputs (string typemapDirectory, string acwMapFile, string androidSourceDirectory, string memberName) + { + var outputs = new List (); + if (Directory.Exists (typemapDirectory)) { + outputs.AddRange (Directory.GetFiles (typemapDirectory, "*.TypeMap.dll", SearchOption.AllDirectories)); + outputs.AddRange (Directory.GetFiles (typemapDirectory, "_Microsoft.Android.TypeMaps.dll", SearchOption.AllDirectories)); + } + if (File.Exists (acwMapFile)) { + outputs.Add (acwMapFile); + } + + foreach (var javaDirectory in new [] { Path.Combine (typemapDirectory, "java"), androidSourceDirectory }) { + if (!Directory.Exists (javaDirectory)) { + continue; + } + foreach (var javaFile in Directory.GetFiles (javaDirectory, "*.java", SearchOption.AllDirectories)) { + if (File.ReadAllText (javaFile).Contains (memberName, StringComparison.Ordinal)) { + outputs.Add (javaFile); + } + } + } + return outputs.ToArray (); + } + [Test] public void Build_PublishAotProject_UsesTrimmableTypeMapForCoreClrDebug () { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj index 96df7a6693b..7e5b7d0bd14 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj @@ -22,6 +22,7 @@ + @@ -36,6 +37,8 @@ ReferenceOutputAssembly=false because we don't compile against it — we just need the DLL on disk. --> + + + diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/ICharSequence.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/ICharSequence.cs new file mode 100644 index 00000000000..8fbba093837 --- /dev/null +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/ICharSequence.cs @@ -0,0 +1,7 @@ +namespace Java.Lang +{ + // Mirrors Mono.Android's interface without a [Register] attribute. + public interface ICharSequence + { + } +} diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/MonoAndroidFixture.csproj b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/MonoAndroidFixture.csproj new file mode 100644 index 00000000000..92393ba196a --- /dev/null +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/MonoAndroidFixture.csproj @@ -0,0 +1,12 @@ + + + + $(DotNetStableTargetFramework) + latest + enable + Mono.Android + Mono.Android + false + + + diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs index d6b10c033ce..9033bca8e81 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs @@ -148,6 +148,9 @@ public void Scan_ExportMethod_CapturesPreciseManagedTypeMetadata () var xmlMethod = FindFixtureByJavaName ("my/app/ExportMarshallingShapes") .MarshalMethods.First (m => m.JniName == "readXml"); + var streamMethod = FindFixtureByJavaName ("my/app/ExportMarshallingShapes") + .MarshalMethods.First (m => m.JniName == "wrapStream"); + Assert.Equal ("System.Runtime", streamMethod.ManagedReturnType.AssemblyName); Assert.Equal (ExportParameterKindInfo.XmlPullParser, xmlMethod.ManagedParameterExportKinds [0]); Assert.Equal (ExportParameterKindInfo.XmlPullParser, xmlMethod.ManagedReturnExportKind); Assert.Equal ("System.Xml.ReaderWriter", xmlMethod.ManagedReturnType.AssemblyName); diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs index 88ca5b2f78b..2baf133e290 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs @@ -10,6 +10,27 @@ public enum JniHandleOwnership } } +namespace Lookalike +{ + [AttributeUsage (AttributeTargets.Method, AllowMultiple = false)] + public sealed class ExportAttribute : Attribute + { + public ExportAttribute (string name) { } + } + + [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false)] + public sealed class ExportParameterAttribute : Attribute + { + public ExportParameterAttribute (Java.Interop.ExportParameterKind kind) { } + } + + [AttributeUsage (AttributeTargets.Method, AllowMultiple = false)] + public sealed class ExportFieldAttribute : Attribute + { + public ExportFieldAttribute (string name) { } + } +} + namespace Java.Interop { public struct JniObjectReference diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj index c5f61f8238c..fb6e87d28d5 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj @@ -11,6 +11,7 @@ + diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs index 5d0b6000196..51375b8a434 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs @@ -29,12 +29,6 @@ public class Exception : Throwable { protected Exception (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } } - - // Mirrors Mono.Android's Java.Lang.ICharSequence: an interface without a - // [Register] attribute. The trimmable typemap scanner / emitter must - // special-case it to map onto java/lang/CharSequence and dispatch via - // Android.Runtime.CharSequence.ToLocalJniHandle. - public interface ICharSequence { } } namespace Android.App @@ -580,6 +574,137 @@ public class ExportCollectionShapes : Java.Lang.Object public System.Collections.ICollection? EchoCollection (System.Collections.ICollection? value) => value; } + public class UnsupportedExportValue + { + } + + [Register ("my/app/ExportWithUnsupportedManagedParameter")] + public class ExportWithUnsupportedManagedParameter : Java.Lang.Object + { + [Java.Interop.Export ("unsupported")] + public void UnsupportedMember (UnsupportedExportValue value) { } + } + + [Register ("my/app/ExportWithUnsupportedManagedReturn")] + public class ExportWithUnsupportedManagedReturn : Java.Lang.Object + { + [Java.Interop.Export ("unsupported")] + public UnsupportedExportValue UnsupportedMember () => new (); + } + + [Register ("my/app/ExportFieldWithUnsupportedManagedReturn")] + public class ExportFieldWithUnsupportedManagedReturn : Java.Lang.Object + { + [Java.Interop.ExportField ("UNSUPPORTED_FIELD")] + public UnsupportedExportValue UnsupportedMember () => new (); + } + + [Register ("my/app/ExportWithGenericMethodParameter")] + public class ExportWithGenericMethodParameter : Java.Lang.Object + { + [Java.Interop.Export ("unsupported")] + public T UnsupportedMember (T value) => value; + } + + [Register ("my/app/ExportWithGenericInstantiation")] + public class ExportWithGenericInstantiation : Java.Lang.Object + { + [Java.Interop.Export ("unsupported")] + public System.Collections.Generic.List UnsupportedMember (System.Collections.Generic.List value) => value; + } + + [Register ("my/app/ExportWithInvalidExportParameterType")] + public class ExportWithInvalidExportParameterType : Java.Lang.Object + { + [Java.Interop.Export ("unsupported")] + public UnsupportedExportValue UnsupportedMember ( + [Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] UnsupportedExportValue value) + => value; + } + + [Register ("my/app/ExportWithInvalidExportParameterKind")] + public class ExportWithInvalidExportParameterKind : Java.Lang.Object + { + [Java.Interop.Export ("unsupported")] + public void UnsupportedMember ( + [Java.Interop.ExportParameter ((Java.Interop.ExportParameterKind) 999)] int value) + { + } + } + + [Register ("my/app/ExportWithGenericExportParameter")] + public class ExportWithGenericExportParameter : Java.Lang.Object + { + [Java.Interop.Export ("unsupported")] + public T UnsupportedMember ( + [Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] T value) + => value; + } + + [Register ("my/app/ExportFieldWithInvalidExportParameterType")] + public class ExportFieldWithInvalidExportParameterType : Java.Lang.Object + { + [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.OutputStream)] + [Java.Interop.ExportField ("UNSUPPORTED_FIELD")] + public UnsupportedExportValue UnsupportedMember () => new (); + } + + [Register ("my/app/GenericExportType")] + public class GenericExportType : Java.Lang.Object + { + [Java.Interop.Export ("unsupported")] + public int UnsupportedMember () => 0; + } + + [Register ("my/app/ExportAttributeLookalikes")] + public class ExportAttributeLookalikes : Java.Lang.Object + { + [Lookalike.Export ("NOT_AN_EXPORT")] + public void LookalikeExport ( + [Lookalike.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] UnsupportedExportValue value) + { + } + + [Java.Interop.Export ("realExport")] + public string RealExport ( + [Lookalike.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] string value) + => value; + } + + [Register ("my/app/ExportConstructorUnsupportedManagedParameter")] + public class ExportConstructorUnsupportedManagedParameter : Android.App.Activity + { + [Java.Interop.Export ("notAConstructor", SuperArgumentsString = "")] + public ExportConstructorUnsupportedManagedParameter (UnsupportedExportValue value) { } + } + + [Register ("my/app/ExportConstructorInvalidExportParameter")] + public class ExportConstructorInvalidExportParameter : Android.App.Activity + { + [Java.Interop.Export (".ctor", SuperArgumentsString = "")] + public ExportConstructorInvalidExportParameter ( + [Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] UnsupportedExportValue value) + { + } + } + + [Register ("my/app/ExportConstructorMappedParameter")] + public class ExportConstructorMappedParameter : Android.App.Activity + { + [Java.Interop.Export ("notAConstructor", SuperArgumentsString = "")] + public ExportConstructorMappedParameter ( + [Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] Stream value) + { + } + } + + [Register ("my/app/ExportStaticConstructor")] + public class ExportStaticConstructor : Java.Lang.Object + { + [Java.Interop.Export] + static ExportStaticConstructor () { } + } + /// /// Has [Export] methods with different access modifiers. /// The JCW should respect the C# visibility for [Export] methods. @@ -616,6 +741,14 @@ protected ExportFieldExample (IntPtr handle, JniHandleOwnership transfer) : base [Java.Interop.ExportField ("VALUE")] [JavaAnnotation] public string GetValue () => ""; + + [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.OutputStream)] + [Java.Interop.ExportField ("OUTPUT_STREAM")] + public Stream? GetOutputStream () => null; + + [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.XmlPullParser)] + [Java.Interop.ExportField ("XML_PARSER")] + public XmlReader? GetXmlParser () => null; } [Register ("my/app/BaseInstrumentation")]