Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Support HandleKind.TypeSpecification (#…
Browse files Browse the repository at this point in the history
…9373)

Fixes: #9369

[C# 11 added support for generic custom attributes][0]:

	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
	public class GenericTestAttribute<T> : Attribute {
	}

which means generic types can now be used as assembly-level attributes:

	[assembly: GenericTestAttribute<string>]

Unfortunately, attempting to do this would result in an
`InvalidCastException` from the `<FilterAssemblies/>` task:

	System.InvalidCastException: Die angegebene Umwandlung ist ungültig.
	error XAFLT7007:    bei System.Reflection.Throw.InvalidCast()
	error XAFLT7007:    bei System.Reflection.Metadata.TypeReferenceHandle.op_Explicit(EntityHandle handle)
	error XAFLT7007:    bei Xamarin.Android.Tasks.MetadataExtensions.GetCustomAttributeFullName(MetadataReader reader, CustomAttribute attribute)
	error XAFLT7007:    bei Xamarin.Android.Tasks.FilterAssemblies.IsAndroidAssembly(AssemblyDefinition assembly, MetadataReader reader)
	error XAFLT7007:    bei Xamarin.Android.Tasks.FilterAssemblies.ProcessAssembly(ITaskItem assemblyItem, List`1 output)
	error XAFLT7007:    bei Xamarin.Android.Tasks.FilterAssemblies.RunTask()
	error XAFLT7007:    bei Microsoft.Android.Build.Tasks.AndroidTask.Execute()

This happens because when System.Reflection.Metadata comes across an
Attribute which has a generic type parameter, it does *not* return a
[`TypeReferenceHandle`][1], but instead a
[`TypeSpecificationHandle`][2].

Update `MetadataExtensions.GetCustomAttributeFullName()` to support
[`HandleKind.TypeSpecification`][3]

[0]: https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-11#generic-attributes
[1]: https://learn.microsoft.com/dotnet/api/system.reflection.metadata.typereferencehandle?view=net-8.0
[2]: https://learn.microsoft.com/dotnet/api/system.reflection.metadata.typespecificationhandle?view=net-8.0
[3]: https://learn.microsoft.com/dotnet/api/system.reflection.metadata.handlekind?view=net-8.0
  • Loading branch information
dellis1972 authored Oct 8, 2024
1 parent 4429c52 commit d806eb4
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 8 deletions.
4 changes: 4 additions & 0 deletions samples/HelloWorld/HelloLibrary/LibraryActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
using Android.Views;
using Android.Widget;

[assembly: HelloLibrary.GenericTest<string>]
namespace HelloLibrary
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public class GenericTestAttribute<T> : Attribute { }

[Activity(Label = "Library Activity", Name="mono.samples.hello.LibraryActivity")]
public class LibraryActivity : Activity
{
Expand Down
2 changes: 1 addition & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ void DoAddAssembliesFromArchCollection (AndroidTargetArch arch, Dictionary<strin
// Thus, we no longer just store them in the apk but we call the `GetCompressionMethod` method to find out whether
// or not we're supposed to compress .so files.
foreach (ITaskItem assembly in assemblies.Values) {
if (MonoAndroidHelper.IsReferenceAssembly (assembly.ItemSpec)) {
if (MonoAndroidHelper.IsReferenceAssembly (assembly.ItemSpec, Log)) {
Log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, Properties.Resources.XA0107, assembly.ItemSpec);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ bool IsAndroidAssembly (AssemblyDefinition assembly, MetadataReader reader)
{
foreach (var handle in assembly.GetCustomAttributes ()) {
var attribute = reader.GetCustomAttribute (handle);
var name = reader.GetCustomAttributeFullName (attribute);
var name = reader.GetCustomAttributeFullName (attribute, Log);
switch (name) {
case "System.Runtime.Versioning.TargetFrameworkAttribute":
string targetFrameworkIdentifier = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1461,5 +1461,48 @@ public void BuildWithJavaToolOptions ()
Environment.SetEnvironmentVariable ("JAVA_TOOL_OPTIONS", oldEnvVar);
}
}

[Test]
public void LibraryWithGenericAttribute ()
{
var path = Path.Combine ("temp", TestContext.CurrentContext.Test.Name);
var lib = new XamarinAndroidLibraryProject {
ProjectName = "Library1",
IsRelease = true,
Sources = {
new BuildItem.Source ("Class1.cs") {
TextContent = () => """
namespace Library1;
public class Class1 { }
"""
},
new BuildItem.Source ("GenericTestAttribute.cs") {
TextContent = () => """
using System;
[assembly: GenericTestAttribute<Guid>]
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public class GenericTestAttribute<T> : Attribute { }
""",
},
},
};
var proj = new XamarinAndroidApplicationProject {
ProjectName = "App1",
IsRelease = true,
Sources = {
new BuildItem.Source ("Class2.cs") {
TextContent= () => """
namespace App1;
class Class2 : Library1.Class1 { }
""",
},
},
};
proj.AddReference (lib);
using var libb = CreateDllBuilder (Path.Combine (path, "Library1"));
Assert.IsTrue (libb.Build (lib), "Library1 Build should have succeeded.");
using var b = CreateApkBuilder (Path.Combine (path, "App1"));
Assert.IsTrue (b.Build (proj), "App1 Build should have succeeded.");
}
}
}
26 changes: 23 additions & 3 deletions src/Xamarin.Android.Build.Tasks/Utilities/MetadataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,37 @@
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Microsoft.Build.Utilities;
using Microsoft.Android.Build.Tasks;

namespace Xamarin.Android.Tasks
{
public static class MetadataExtensions
{
public static string GetCustomAttributeFullName (this MetadataReader reader, CustomAttribute attribute)
public static string GetCustomAttributeFullName (this MetadataReader reader, CustomAttribute attribute, TaskLoggingHelper log)
{
if (attribute.Constructor.Kind == HandleKind.MemberReference) {
var ctor = reader.GetMemberReference ((MemberReferenceHandle)attribute.Constructor);
var type = reader.GetTypeReference ((TypeReferenceHandle)ctor.Parent);
return reader.GetString (type.Namespace) + "." + reader.GetString (type.Name);
try {
if (ctor.Parent.Kind == HandleKind.TypeReference) {
var type = reader.GetTypeReference ((TypeReferenceHandle)ctor.Parent);
return reader.GetString (type.Namespace) + "." + reader.GetString (type.Name);
} else if (ctor.Parent.Kind == HandleKind.TypeSpecification) {
var type = reader.GetTypeSpecification ((TypeSpecificationHandle)ctor.Parent);
BlobReader blobReader = reader.GetBlobReader (type.Signature);
SignatureTypeCode typeCode = blobReader.ReadSignatureTypeCode ();
EntityHandle typeHandle = blobReader.ReadTypeHandle ();
TypeReference typeRef = reader.GetTypeReference ((TypeReferenceHandle)typeHandle);
return reader.GetString (typeRef.Namespace) + "." + reader.GetString (typeRef.Name);
} else {
log.LogDebugMessage ($"Unsupported EntityHandle.Kind: {ctor.Parent.Kind}");
return null;
}
}
catch (InvalidCastException ex) {
log.LogDebugMessage ($"Unsupported EntityHandle.Kind `{ctor.Parent.Kind}`: {ex}");
return null;
}
} else if (attribute.Constructor.Kind == HandleKind.MethodDefinition) {
var ctor = reader.GetMethodDefinition ((MethodDefinitionHandle)attribute.Constructor);
var type = reader.GetTypeDefinition (ctor.GetDeclaringType ());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,15 +364,15 @@ public static bool HasMonoAndroidReference (MetadataReader reader)
return false;
}

public static bool IsReferenceAssembly (string assembly)
public static bool IsReferenceAssembly (string assembly, TaskLoggingHelper log)
{
using (var stream = File.OpenRead (assembly))
using (var pe = new PEReader (stream)) {
var reader = pe.GetMetadataReader ();
var assemblyDefinition = reader.GetAssemblyDefinition ();
foreach (var handle in assemblyDefinition.GetCustomAttributes ()) {
var attribute = reader.GetCustomAttribute (handle);
var attributeName = reader.GetCustomAttributeFullName (attribute);
var attributeName = reader.GetCustomAttributeFullName (attribute, log);
if (attributeName == "System.Runtime.CompilerServices.ReferenceAssemblyAttribute")
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ string GetResourceDesignerClass (MetadataReader reader)
var assembly = reader.GetAssemblyDefinition ();
foreach (var handle in assembly.GetCustomAttributes ()) {
var attribute = reader.GetCustomAttribute (handle);
var fullName = reader.GetCustomAttributeFullName (attribute);
var fullName = reader.GetCustomAttributeFullName (attribute, Log);
if (fullName == "Android.Runtime.ResourceDesignerAttribute") {
var values = attribute.GetCustomAttributeArguments ();
foreach (var arg in values.NamedArguments) {
Expand Down

0 comments on commit d806eb4

Please sign in to comment.