Skip to content

Commit d806eb4

Browse files
authored
[Xamarin.Android.Build.Tasks] Support HandleKind.TypeSpecification (#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
1 parent 4429c52 commit d806eb4

File tree

7 files changed

+75
-8
lines changed

7 files changed

+75
-8
lines changed

samples/HelloWorld/HelloLibrary/LibraryActivity.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
using Android.Views;
1717
using Android.Widget;
1818

19+
[assembly: HelloLibrary.GenericTest<string>]
1920
namespace HelloLibrary
2021
{
22+
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
23+
public class GenericTestAttribute<T> : Attribute { }
24+
2125
[Activity(Label = "Library Activity", Name="mono.samples.hello.LibraryActivity")]
2226
public class LibraryActivity : Activity
2327
{

src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ void DoAddAssembliesFromArchCollection (AndroidTargetArch arch, Dictionary<strin
493493
// Thus, we no longer just store them in the apk but we call the `GetCompressionMethod` method to find out whether
494494
// or not we're supposed to compress .so files.
495495
foreach (ITaskItem assembly in assemblies.Values) {
496-
if (MonoAndroidHelper.IsReferenceAssembly (assembly.ItemSpec)) {
496+
if (MonoAndroidHelper.IsReferenceAssembly (assembly.ItemSpec, Log)) {
497497
Log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, Properties.Resources.XA0107, assembly.ItemSpec);
498498
}
499499

src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ bool IsAndroidAssembly (AssemblyDefinition assembly, MetadataReader reader)
102102
{
103103
foreach (var handle in assembly.GetCustomAttributes ()) {
104104
var attribute = reader.GetCustomAttribute (handle);
105-
var name = reader.GetCustomAttributeFullName (attribute);
105+
var name = reader.GetCustomAttributeFullName (attribute, Log);
106106
switch (name) {
107107
case "System.Runtime.Versioning.TargetFrameworkAttribute":
108108
string targetFrameworkIdentifier = null;

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,5 +1461,48 @@ public void BuildWithJavaToolOptions ()
14611461
Environment.SetEnvironmentVariable ("JAVA_TOOL_OPTIONS", oldEnvVar);
14621462
}
14631463
}
1464+
1465+
[Test]
1466+
public void LibraryWithGenericAttribute ()
1467+
{
1468+
var path = Path.Combine ("temp", TestContext.CurrentContext.Test.Name);
1469+
var lib = new XamarinAndroidLibraryProject {
1470+
ProjectName = "Library1",
1471+
IsRelease = true,
1472+
Sources = {
1473+
new BuildItem.Source ("Class1.cs") {
1474+
TextContent = () => """
1475+
namespace Library1;
1476+
public class Class1 { }
1477+
"""
1478+
},
1479+
new BuildItem.Source ("GenericTestAttribute.cs") {
1480+
TextContent = () => """
1481+
using System;
1482+
[assembly: GenericTestAttribute<Guid>]
1483+
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
1484+
public class GenericTestAttribute<T> : Attribute { }
1485+
""",
1486+
},
1487+
},
1488+
};
1489+
var proj = new XamarinAndroidApplicationProject {
1490+
ProjectName = "App1",
1491+
IsRelease = true,
1492+
Sources = {
1493+
new BuildItem.Source ("Class2.cs") {
1494+
TextContent= () => """
1495+
namespace App1;
1496+
class Class2 : Library1.Class1 { }
1497+
""",
1498+
},
1499+
},
1500+
};
1501+
proj.AddReference (lib);
1502+
using var libb = CreateDllBuilder (Path.Combine (path, "Library1"));
1503+
Assert.IsTrue (libb.Build (lib), "Library1 Build should have succeeded.");
1504+
using var b = CreateApkBuilder (Path.Combine (path, "App1"));
1505+
Assert.IsTrue (b.Build (proj), "App1 Build should have succeeded.");
1506+
}
14641507
}
14651508
}

src/Xamarin.Android.Build.Tasks/Utilities/MetadataExtensions.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,37 @@
33
using System.IO;
44
using System.Reflection.Metadata;
55
using System.Reflection.PortableExecutable;
6+
using Microsoft.Build.Utilities;
7+
using Microsoft.Android.Build.Tasks;
68

79
namespace Xamarin.Android.Tasks
810
{
911
public static class MetadataExtensions
1012
{
11-
public static string GetCustomAttributeFullName (this MetadataReader reader, CustomAttribute attribute)
13+
public static string GetCustomAttributeFullName (this MetadataReader reader, CustomAttribute attribute, TaskLoggingHelper log)
1214
{
1315
if (attribute.Constructor.Kind == HandleKind.MemberReference) {
1416
var ctor = reader.GetMemberReference ((MemberReferenceHandle)attribute.Constructor);
15-
var type = reader.GetTypeReference ((TypeReferenceHandle)ctor.Parent);
16-
return reader.GetString (type.Namespace) + "." + reader.GetString (type.Name);
17+
try {
18+
if (ctor.Parent.Kind == HandleKind.TypeReference) {
19+
var type = reader.GetTypeReference ((TypeReferenceHandle)ctor.Parent);
20+
return reader.GetString (type.Namespace) + "." + reader.GetString (type.Name);
21+
} else if (ctor.Parent.Kind == HandleKind.TypeSpecification) {
22+
var type = reader.GetTypeSpecification ((TypeSpecificationHandle)ctor.Parent);
23+
BlobReader blobReader = reader.GetBlobReader (type.Signature);
24+
SignatureTypeCode typeCode = blobReader.ReadSignatureTypeCode ();
25+
EntityHandle typeHandle = blobReader.ReadTypeHandle ();
26+
TypeReference typeRef = reader.GetTypeReference ((TypeReferenceHandle)typeHandle);
27+
return reader.GetString (typeRef.Namespace) + "." + reader.GetString (typeRef.Name);
28+
} else {
29+
log.LogDebugMessage ($"Unsupported EntityHandle.Kind: {ctor.Parent.Kind}");
30+
return null;
31+
}
32+
}
33+
catch (InvalidCastException ex) {
34+
log.LogDebugMessage ($"Unsupported EntityHandle.Kind `{ctor.Parent.Kind}`: {ex}");
35+
return null;
36+
}
1737
} else if (attribute.Constructor.Kind == HandleKind.MethodDefinition) {
1838
var ctor = reader.GetMethodDefinition ((MethodDefinitionHandle)attribute.Constructor);
1939
var type = reader.GetTypeDefinition (ctor.GetDeclaringType ());

src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,15 +364,15 @@ public static bool HasMonoAndroidReference (MetadataReader reader)
364364
return false;
365365
}
366366

367-
public static bool IsReferenceAssembly (string assembly)
367+
public static bool IsReferenceAssembly (string assembly, TaskLoggingHelper log)
368368
{
369369
using (var stream = File.OpenRead (assembly))
370370
using (var pe = new PEReader (stream)) {
371371
var reader = pe.GetMetadataReader ();
372372
var assemblyDefinition = reader.GetAssemblyDefinition ();
373373
foreach (var handle in assemblyDefinition.GetCustomAttributes ()) {
374374
var attribute = reader.GetCustomAttribute (handle);
375-
var attributeName = reader.GetCustomAttributeFullName (attribute);
375+
var attributeName = reader.GetCustomAttributeFullName (attribute, log);
376376
if (attributeName == "System.Runtime.CompilerServices.ReferenceAssemblyAttribute")
377377
return true;
378378
}

src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ string GetResourceDesignerClass (MetadataReader reader)
8585
var assembly = reader.GetAssemblyDefinition ();
8686
foreach (var handle in assembly.GetCustomAttributes ()) {
8787
var attribute = reader.GetCustomAttribute (handle);
88-
var fullName = reader.GetCustomAttributeFullName (attribute);
88+
var fullName = reader.GetCustomAttributeFullName (attribute, Log);
8989
if (fullName == "Android.Runtime.ResourceDesignerAttribute") {
9090
var values = attribute.GetCustomAttributeArguments ();
9191
foreach (var arg in values.NamedArguments) {

0 commit comments

Comments
 (0)