diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index 0b7055dc65a04e..a6df4e1e59f21a 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -1,27 +1,26 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Reflection.Metadata.Tests; using System.Reflection.PortableExecutable; using Xunit; +using DecodingResources = System.Reflection.Metadata.Tests.Decoding; namespace System.Reflection.Metadata.Decoding.Tests { public class CustomAttributeDecoderTests { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] + [Fact] public void TestCustomAttributeDecoder() { - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(typeof(HasAttributes).GetTypeInfo().Assembly))) + using (var stream = new MemoryStream(DecodingResources.CustomAttributeDecoder)) using (var peReader = new PEReader(stream)) { MetadataReader reader = peReader.GetMetadataReader(); var provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, typeof(HasAttributes)); + TypeDefinitionHandle typeDefHandle = FindTestType(reader, "CustomAttributeDecoderTests", "HasAttributes"); int i = 0; foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) @@ -31,27 +30,24 @@ public void TestCustomAttributeDecoder() switch (i++) { - case 0: + case 0: // [Test] Assert.Empty(value.FixedArguments); Assert.Empty(value.NamedArguments); break; - case 1: + case 1: // [Test("0", 1, 2.0)] Assert.Equal(3, value.FixedArguments.Length); - Assert.Equal("string", value.FixedArguments[0].Type); Assert.Equal("0", value.FixedArguments[0].Value); - Assert.Equal("int32", value.FixedArguments[1].Type); Assert.Equal(1, value.FixedArguments[1].Value); - Assert.Equal("float64", value.FixedArguments[2].Type); Assert.Equal(2.0, value.FixedArguments[2].Value); - Assert.Empty(value.NamedArguments); break; - case 2: + case 2: // [Test(StringField = "0", Int32Field = 1, SByteEnumArrayProperty = new[] { SByteEnum.Value })] + Assert.Empty(value.FixedArguments); Assert.Equal(3, value.NamedArguments.Length); Assert.Equal(CustomAttributeNamedArgumentKind.Field, value.NamedArguments[0].Kind); @@ -66,621 +62,190 @@ public void TestCustomAttributeDecoder() Assert.Equal(CustomAttributeNamedArgumentKind.Property, value.NamedArguments[2].Kind); Assert.Equal("SByteEnumArrayProperty", value.NamedArguments[2].Name); - Assert.Equal(typeof(SByteEnum).FullName + "[]", value.NamedArguments[2].Type); - - var array = (ImmutableArray>)(value.NamedArguments[2].Value); - Assert.Equal(1, array.Length); - Assert.Equal(typeof(SByteEnum).FullName, array[0].Type); - Assert.Equal((sbyte)SByteEnum.Value, array[0].Value); + Assert.Equal("CustomAttributeDecoderTests.SByteEnum[]", value.NamedArguments[2].Type); + var sbyteEnumArray = (ImmutableArray>)value.NamedArguments[2].Value; + Assert.Equal(1, sbyteEnumArray.Length); + Assert.Equal("CustomAttributeDecoderTests.SByteEnum", sbyteEnumArray[0].Type); + Assert.Equal((sbyte)-1, sbyteEnumArray[0].Value); break; default: - // TODO: https://github.com/dotnet/runtime/issues/73593 - // This method only tests first 3 attriubtes because the complete test 'TestCustomAttributeDecoderUsingReflection' fails on mono - // Leaving this hard coded test only for mono, until the issue fixed on mono break; } } } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/73593", TestRuntimes.Mono)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/124344", typeof(PlatformDetection), nameof(PlatformDetection.IsAppleMobile), nameof(PlatformDetection.IsCoreCLR))] - public void TestCustomAttributeDecoderUsingReflection() +#if NET && !TARGET_BROWSER // Generic attribute is not supported on .NET Framework. + [Fact] + public void TestCustomAttributeDecoderGeneric() { - Type type = typeof(HasAttributes); - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) - using (PEReader peReader = new PEReader(stream)) + using (var stream = new MemoryStream(DecodingResources.CustomAttributeDecoder)) + using (var peReader = new PEReader(stream)) { MetadataReader reader = peReader.GetMetadataReader(); - CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); - - IList attributes = type.GetCustomAttributesData(); + var provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = FindTestType(reader, "CustomAttributeDecoderTests", "HasGenericAttributes"); int i = 0; foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) { CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); CustomAttributeValue value = attribute.DecodeValue(provider); - CustomAttributeData reflectionAttribute = attributes[i++]; - Assert.Equal(reflectionAttribute.ConstructorArguments.Count, value.FixedArguments.Length); - Assert.Equal(reflectionAttribute.NamedArguments.Count, value.NamedArguments.Length); - - int j = 0; - foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) - { - Type t = reflectionAttribute.ConstructorArguments[j].ArgumentType; - Assert.Equal(TypeToString(t), arguments.Type); - if (t.IsArray && arguments.Value is not null) - { - ImmutableArray> array = (ImmutableArray>)(arguments.Value); - IList refArray = (IList)reflectionAttribute.ConstructorArguments[j].Value; - int k = 0; - foreach (CustomAttributeTypedArgument element in array) - { - if (refArray[k].ArgumentType.IsArray) - { - ImmutableArray> innerArray = (ImmutableArray>)(element.Value); - IList refInnerArray = (IList)refArray[k].Value; - int a = 0; - foreach (CustomAttributeTypedArgument el in innerArray) - { - if (refInnerArray[a].Value?.ToString() != el.Value?.ToString()) - { - Assert.Equal(refInnerArray[a].Value, el.Value); - } - a++; - } - } - else if (refArray[k].Value?.ToString() != element.Value?.ToString()) - { - if (refArray[k].ArgumentType == typeof(Type)) // TODO: check if it is expected - { - Assert.Contains(refArray[k].Value.ToString(), element.Value.ToString()); - } - else - { - Assert.Equal(refArray[k].Value, element.Value); - } - } - k++; - } - } - else if (reflectionAttribute.ConstructorArguments[j].Value?.ToString() != arguments.Value?.ToString()) - { - if (reflectionAttribute.ConstructorArguments[j].ArgumentType == typeof(Type)) - { - Assert.Contains(reflectionAttribute.ConstructorArguments[j].Value.ToString(), arguments.Value.ToString()); - } - else - { - Assert.Equal(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); - } - } - j++; - } - j = 0; - foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) + switch (i++) { - Type t = reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType; - Assert.Equal(TypeToString(t), arguments.Type); - if (t.IsArray && arguments.Value is not null) - { - ImmutableArray> array = (ImmutableArray>)(arguments.Value); - IList refArray = (IList)reflectionAttribute.NamedArguments[j].TypedValue.Value; - int k = 0; - foreach (CustomAttributeTypedArgument element in array) - { - if (refArray[k].Value?.ToString() != element.Value?.ToString()) - { - Assert.Equal(refArray[k].Value, element.Value); - } - k++; - } - } - else if (reflectionAttribute.NamedArguments[j].TypedValue.Value?.ToString() != arguments.Value?.ToString()) - { - if (reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType == typeof(Type)) // typeof operator used for named parameter, like [Test(TypeField = typeof(string))], check if it is expected - { - Assert.Contains(reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString(), arguments.Value.ToString()); - } - else - { - Assert.Equal(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); - } - } - j++; - } - } - } - } + case 0: // [GenericAttribute] + Assert.Empty(value.FixedArguments); + Assert.Empty(value.NamedArguments); + break; -#if NET && !TARGET_BROWSER // Generic attribute is not supported on .NET Framework. - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] - public void TestCustomAttributeDecoderGenericUsingReflection() - { - Type type = typeof(HasGenericAttributes); - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) - using (PEReader peReader = new PEReader(stream)) - { - MetadataReader reader = peReader.GetMetadataReader(); - CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); + case 1: // [GenericAttribute("Hello")] + Assert.Equal(1, value.FixedArguments.Length); + Assert.Equal("string", value.FixedArguments[0].Type); + Assert.Equal("Hello", value.FixedArguments[0].Value); + Assert.Empty(value.NamedArguments); + break; - IList attributes= type.GetCustomAttributesData(); + case 2: // [GenericAttribute(12)] + Assert.Equal(1, value.FixedArguments.Length); + Assert.Equal("int32", value.FixedArguments[0].Type); + Assert.Equal(12, value.FixedArguments[0].Value); + Assert.Empty(value.NamedArguments); + break; - int i = 0; - foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) - { - CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); - CustomAttributeValue value = attribute.DecodeValue(provider); - CustomAttributeData reflectionAttribute = attributes[i++]; + case 3: // [GenericAttribute("Hello", 12, TProperty = "Bye")] + Assert.Equal(2, value.FixedArguments.Length); + Assert.Equal("string", value.FixedArguments[0].Type); + Assert.Equal("Hello", value.FixedArguments[0].Value); + Assert.Equal("int32", value.FixedArguments[1].Type); + Assert.Equal(12, value.FixedArguments[1].Value); + Assert.Equal(1, value.NamedArguments.Length); + Assert.Equal("TProperty", value.NamedArguments[0].Name); + Assert.Equal("string", value.NamedArguments[0].Type); + Assert.Equal("Bye", value.NamedArguments[0].Value); + break; + + case 4: // [GenericAttribute(1, TProperty = 2)] + Assert.Equal(1, value.FixedArguments.Length); + Assert.Equal("uint8", value.FixedArguments[0].Type); + Assert.Equal((byte)1, value.FixedArguments[0].Value); + Assert.Equal(1, value.NamedArguments.Length); + Assert.Equal("TProperty", value.NamedArguments[0].Name); + Assert.Equal("uint8", value.NamedArguments[0].Type); + Assert.Equal((byte)2, value.NamedArguments[0].Value); + break; - Assert.Equal(reflectionAttribute.ConstructorArguments.Count, value.FixedArguments.Length); - Assert.Equal(reflectionAttribute.NamedArguments.Count, value.NamedArguments.Length); + case 5: // [GenericAttribute2(true, 13)] + Assert.Equal(2, value.FixedArguments.Length); + Assert.Equal("bool", value.FixedArguments[0].Type); + Assert.Equal(true, value.FixedArguments[0].Value); + Assert.Equal("int32", value.FixedArguments[1].Type); + Assert.Equal(13, value.FixedArguments[1].Value); + Assert.Empty(value.NamedArguments); + break; - int j = 0; - foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) - { - Assert.Equal(TypeToString(reflectionAttribute.ConstructorArguments[j].ArgumentType), arguments.Type); - if (reflectionAttribute.ConstructorArguments[j].Value.ToString() != arguments.Value.ToString()) - { - Assert.Equal(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); - } - j++; - } - j = 0; - foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) - { - Assert.Equal(TypeToString(reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType), arguments.Type); - if (reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString() != arguments.Value.ToString()) - { - Assert.Equal(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); - } - j++; + case 6: // [GenericAttribute(typeof(HasAttributes))] + Assert.Equal(1, value.FixedArguments.Length); + Assert.Equal(provider.GetSystemType(), value.FixedArguments[0].Type); + Assert.Contains("CustomAttributeDecoderTests.HasAttributes", (string)value.FixedArguments[0].Value); + Assert.Empty(value.NamedArguments); + break; + + case 7: // [GenericAttribute(TProperty = typeof(HasAttributes))] + Assert.Empty(value.FixedArguments); + Assert.Equal(1, value.NamedArguments.Length); + Assert.Equal("TProperty", value.NamedArguments[0].Name); + Assert.Equal(provider.GetSystemType(), value.NamedArguments[0].Type); + Assert.Contains("CustomAttributeDecoderTests.HasAttributes", (string)value.NamedArguments[0].Value); + break; } } + + Assert.Equal(8, i); } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] + [Fact] public void TestCustomAttributeDecoderGenericArray() { - Type type = typeof(HasGenericArrayAttributes); - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) - using (PEReader peReader = new PEReader(stream)) + using (var stream = new MemoryStream(DecodingResources.CustomAttributeDecoder)) + using (var peReader = new PEReader(stream)) { MetadataReader reader = peReader.GetMetadataReader(); - CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); - - IList attributes = type.GetCustomAttributesData(); + var provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = FindTestType(reader, "CustomAttributeDecoderTests", "HasGenericArrayAttributes"); + int i = 0; foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) { CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); CustomAttributeValue value = attribute.DecodeValue(provider); - if (value.FixedArguments.Length == 2) - { - Assert.Equal(2, value.FixedArguments.Length); - ImmutableArray> array1 = (ImmutableArray>)(value.FixedArguments[0].Value); - Assert.Equal("int32[]", value.FixedArguments[0].Type); - Assert.Equal(1, array1[0].Value); - Assert.Equal(3, array1[2].Value); - ImmutableArray> array2 = (ImmutableArray>)(value.FixedArguments[1].Value); - Assert.Equal("uint8[]", value.FixedArguments[1].Type); - Assert.Equal((byte)4, array2[0].Value); - Assert.Equal((byte)5, array2[1].Value); - - Assert.Empty(value.NamedArguments); - } - else + switch (i++) { - Assert.Equal(1, value.FixedArguments.Length); - - Assert.Equal("uint8", value.FixedArguments[0].Type); - Assert.Equal((byte)1, value.FixedArguments[0].Value); - - Assert.Equal(2, value.NamedArguments.Length); - - Assert.Equal("uint8", value.NamedArguments[0].Type); - Assert.Equal((byte)2, value.NamedArguments[0].Value); + case 0: // [GenericAttribute2(new int[] { 1, 2, 3 }, new byte[] { 4, 5 })] + { + Assert.Equal(2, value.FixedArguments.Length); + ImmutableArray> array1 = (ImmutableArray>)(value.FixedArguments[0].Value); + Assert.Equal("int32[]", value.FixedArguments[0].Type); + Assert.Equal(1, array1[0].Value); + Assert.Equal(3, array1[2].Value); + ImmutableArray> array2 = (ImmutableArray>)(value.FixedArguments[1].Value); + Assert.Equal("uint8[]", value.FixedArguments[1].Type); + Assert.Equal((byte)4, array2[0].Value); + Assert.Equal((byte)5, array2[1].Value); + Assert.Empty(value.NamedArguments); + break; + } - ImmutableArray> array = (ImmutableArray>)(value.NamedArguments[1].Value); - Assert.Equal("uint8[]", value.NamedArguments[1].Type); - Assert.Equal((byte)3, array[0].Value); + case 1: // [GenericAttribute(1, TProperty = 2, TArrayProperty = new byte[] { 3, 4 })] + { + Assert.Equal(1, value.FixedArguments.Length); + Assert.Equal("uint8", value.FixedArguments[0].Type); + Assert.Equal((byte)1, value.FixedArguments[0].Value); + Assert.Equal(2, value.NamedArguments.Length); + Assert.Equal("uint8", value.NamedArguments[0].Type); + Assert.Equal((byte)2, value.NamedArguments[0].Value); + ImmutableArray> array = (ImmutableArray>)(value.NamedArguments[1].Value); + Assert.Equal("uint8[]", value.NamedArguments[1].Type); + Assert.Equal((byte)3, array[0].Value); + break; + } } } - } - } - [GenericAttribute] - [GenericAttribute("Hello")] - [GenericAttribute(12)] - [GenericAttribute("Hello", 12, TProperty = "Bye")] - [GenericAttribute(1, TProperty = 2)] - [GenericAttribute2(true, 13)] - // [GenericAttribute(MyEnum.Property)] TODO: https://github.com/dotnet/runtime/issues/16552 - [GenericAttribute(typeof(HasAttributes))] - [GenericAttribute(TProperty = typeof(HasAttributes))] - public class HasGenericAttributes { } - - [GenericAttribute2(new int[] { 1, 2, 3 }, new byte[] { 4, 5 })] - [GenericAttribute(1, TProperty = 2, TArrayProperty = new byte[] { 3, 4 })] - public class HasGenericArrayAttributes { } - - [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] - internal class GenericAttribute : Attribute - { - public GenericAttribute() { } - public GenericAttribute(T value) - { - Field = value; - } - public GenericAttribute(T value, int count) - { - Field = value; + Assert.Equal(2, i); } - public T TProperty { get; set; } - public T[] TArrayProperty { get; set; } - public T Field; - } - - [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] - internal class GenericAttribute2 : Attribute - { - public GenericAttribute2() { } - public GenericAttribute2(K key) { } - public GenericAttribute2(K key, V value) { } - public K Key { get; set; } - public V Value { get; set; } - public K[] ArrayProperty { get; set; } } #endif - // no arguments - [Test] - - // multiple fixed arguments - [Test("0", 1, 2.0)] - - // multiple named arguments - [Test(StringField = "0", Int32Field = 1, SByteEnumArrayProperty = new[] { SByteEnum.Value })] - - // multiple fixed and named arguments - [Test("0", 1, 2.0, StringField = "0", Int32Field = 1, DoubleField = 2.0)] - - // single fixed null argument - [Test((object)null)] - [Test((string)null)] - [Test((Type)null)] - [Test((int[])null)] - - // single fixed arguments with strong type - [Test("string")] - [Test((sbyte)-1)] - [Test((short)-2)] - [Test((int)-4)] - [Test((long)-8)] - [Test((sbyte)-1)] - [Test((short)-2)] - [Test((int)-4)] - [Test((long)-8)] - [Test((byte)1)] - [Test((ushort)2)] - [Test((uint)4)] - [Test((ulong)8)] - [Test(true)] - [Test(false)] - [Test(typeof(string))] - /* [Test(SByteEnum.Value)] // The FullName is (System.Reflection.Metadata.Decoding.Tests.CustomAttributeDecoderTests+SByteEnum) - [Test(Int16Enum.Value)] // but some enums '+' is replaced with '/' and causing inconsistency - [Test(Int32Enum.Value)] // Updaated https://github.com/dotnet/runtime/issues/16552 to resolve this scenario later - [Test(Int64Enum.Value)] - [Test(ByteEnum.Value)] - [Test(UInt16Enum.Value)] - [Test(UInt32Enum.Value)] - [Test(UInt64Enum.Value)]*/ - [Test(new string[] { })] - [Test(new string[] { "x", "y", "z", null })] - // [Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 - - // same single fixed arguments as above, typed as object - [Test((object)("string"))] - [Test((object)(sbyte)-1)] - [Test((object)(short)-2)] - [Test((object)(int)-4)] - [Test((object)(long)-8)] - [Test((object)(sbyte)-1)] - [Test((object)(short)-2)] - [Test((object)(int)-4)] - [Test((object)(long)-8)] - [Test((object)(byte)1)] - [Test((object)(ushort)2)] - [Test((object)(uint)4)] - [Test((object)(true))] - [Test((object)(false))] - [Test((object)(typeof(string)))] - [Test((object)(SByteEnum.Value))] - [Test((object)(Int16Enum.Value))] - [Test((object)(Int32Enum.Value))] - [Test((object)(Int64Enum.Value))] - [Test((object)(ByteEnum.Value))] - [Test((object)(UInt16Enum.Value))] - [Test((object)(UInt32Enum.Value))] - [Test((object)(UInt64Enum.Value))] - [Test((object)(new string[] { }))] - [Test((object)(new string[] { "x", "y", "z", null }))] - [Test((object)(new Int32Enum[] { Int32Enum.Value }))] - - // same values as above two cases, but put into an object[] - [Test(new object[] { - "string", - (sbyte)-1, - (short)-2, - (int)-4, - (long)-8, - (sbyte)-1, - (short)-2, - (int)-4, - (long)-8, - (byte)1, - (ushort)2, - (uint)4, - true, - false, - typeof(string), // check if the produced value is expected - SByteEnum.Value, - Int16Enum.Value, - Int32Enum.Value, - Int64Enum.Value, - SByteEnum.Value, - Int16Enum.Value, - Int32Enum.Value, - Int64Enum.Value, - new string[] {}, - new string[] { "x", "y", "z", null }, - })] - - // same values as strongly-typed fixed arguments as named arguments - // single fixed arguments with strong type - [Test(StringField = "string")] - [Test(SByteField = -1)] - [Test(Int16Field = -2)] - [Test(Int32Field = -4)] - [Test(Int64Field = -8)] - [Test(SByteField = -1)] - [Test(Int16Field = -2)] - [Test(Int32Field = -4)] - [Test(Int64Field = -8)] - [Test(ByteField = 1)] - [Test(UInt16Field = 2)] - [Test(UInt32Field = 4)] - [Test(UInt64Field = 8)] - [Test(BooleanField = true)] - [Test(BooleanField = false)] - [Test(TypeField = typeof(string))] - [Test(SByteEnumField = SByteEnum.Value)] - [Test(Int16EnumField = Int16Enum.Value)] - [Test(Int32EnumField = Int32Enum.Value)] - [Test(Int64EnumField = Int64Enum.Value)] - [Test(ByteEnumField = ByteEnum.Value)] - [Test(UInt16EnumField = UInt16Enum.Value)] - [Test(UInt32EnumField = UInt32Enum.Value)] - [Test(UInt64EnumField = UInt64Enum.Value)] - [Test(new string[] { })] - [Test(new string[] { "x", "y", "z", null })] - // [Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 - - // null named arguments - [Test(ObjectField = null)] - [Test(StringField = null)] - - [Test(Int32ArrayProperty = null)] - - private sealed class HasAttributes { } - - public enum SByteEnum : sbyte { Value = -1 } - public enum Int16Enum : short { Value = -2 } - public enum Int32Enum : int { Value = -3 } - public enum Int64Enum : long { Value = -4 } - public enum ByteEnum : sbyte { Value = 1 } - public enum UInt16Enum : ushort { Value = 2 } - public enum UInt32Enum : uint { Value = 3 } - public enum UInt64Enum : ulong { Value = 4 } - - [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] - public sealed class TestAttribute : Attribute - { - public TestAttribute() { } - public TestAttribute(string x, int y, double z) { } - public TestAttribute(string value) { } - public TestAttribute(object value) { } - public TestAttribute(sbyte value) { } - public TestAttribute(short value) { } - public TestAttribute(int value) { } - public TestAttribute(long value) { } - public TestAttribute(byte value) { } - public TestAttribute(ushort value) { } - public TestAttribute(uint value) { } - public TestAttribute(ulong value) { } - public TestAttribute(bool value) { } - public TestAttribute(float value) { } - public TestAttribute(double value) { } - public TestAttribute(Type value) { } - public TestAttribute(SByteEnum value) { } - public TestAttribute(Int16Enum value) { } - public TestAttribute(Int32Enum value) { } - public TestAttribute(Int64Enum value) { } - public TestAttribute(ByteEnum value) { } - public TestAttribute(UInt16Enum value) { } - public TestAttribute(UInt32Enum value) { } - public TestAttribute(UInt64Enum value) { } - public TestAttribute(string[] value) { } - public TestAttribute(object[] value) { } - public TestAttribute(sbyte[] value) { } - public TestAttribute(short[] value) { } - public TestAttribute(int[] value) { } - public TestAttribute(long[] value) { } - public TestAttribute(byte[] value) { } - public TestAttribute(ushort[] value) { } - public TestAttribute(uint[] value) { } - public TestAttribute(ulong[] value) { } - public TestAttribute(bool[] value) { } - public TestAttribute(float[] value) { } - public TestAttribute(double[] value) { } - public TestAttribute(Type[] value) { } - public TestAttribute(SByteEnum[] value) { } - public TestAttribute(Int16Enum[] value) { } - public TestAttribute(Int32Enum[] value) { } - public TestAttribute(Int64Enum[] value) { } - public TestAttribute(ByteEnum[] value) { } - public TestAttribute(UInt16Enum[] value) { } - public TestAttribute(UInt32Enum[] value) { } - public TestAttribute(UInt64Enum[] value) { } - - public string StringField; - public object ObjectField; - public sbyte SByteField; - public short Int16Field; - public int Int32Field; - public long Int64Field; - public byte ByteField; - public ushort UInt16Field; - public uint UInt32Field; - public ulong UInt64Field; - public bool BooleanField; - public float SingleField; - public double DoubleField; - public Type TypeField; - public SByteEnum SByteEnumField; - public Int16Enum Int16EnumField; - public Int32Enum Int32EnumField; - public Int64Enum Int64EnumField; - public ByteEnum ByteEnumField; - public UInt16Enum UInt16EnumField; - public UInt32Enum UInt32EnumField; - public UInt64Enum UInt64EnumField; - - public string[] StringArrayProperty { get; set; } - public object[] ObjectArrayProperty { get; set; } - public sbyte[] SByteArrayProperty { get; set; } - public short[] Int16ArrayProperty { get; set; } - public int[] Int32ArrayProperty { get; set; } - public long[] Int64ArrayProperty { get; set; } - public byte[] ByteArrayProperty { get; set; } - public ushort[] UInt16ArrayProperty { get; set; } - public uint[] UInt32ArrayProperty { get; set; } - public ulong[] UInt64ArrayProperty { get; set; } - public bool[] BooleanArrayProperty { get; set; } - public float[] SingleArrayProperty { get; set; } - public double[] DoubleArrayProperty { get; set; } - public Type[] TypeArrayProperty { get; set; } - public SByteEnum[] SByteEnumArrayProperty { get; set; } - public Int16Enum[] Int16EnumArrayProperty { get; set; } - public Int32Enum[] Int32EnumArrayProperty { get; set; } - public Int64Enum[] Int64EnumArrayProperty { get; set; } - public ByteEnum[] ByteEnumArrayProperty { get; set; } - public UInt16Enum[] UInt16EnumArrayProperty { get; set; } - public UInt32Enum[] UInt32EnumArrayProperty { get; set; } - public UInt64Enum[] UInt64EnumArrayProperty { get; set; } - } - - private string TypeToString(Type type) + private static TypeDefinitionHandle FindTestType(MetadataReader reader, string @namespace, string name) { - if (type == typeof(Type)) - return $"[{MetadataReaderTestHelpers.RuntimeAssemblyName}]System.Type"; - - if (type.IsArray) + foreach (TypeDefinitionHandle handle in reader.TypeDefinitions) { - if (type.GetElementType().IsEnum) + TypeDefinition definition = reader.GetTypeDefinition(handle); + if (reader.StringComparer.Equals(definition.Namespace, @namespace) && + reader.StringComparer.Equals(definition.Name, name)) { - Type el = type.GetElementType(); - return type.FullName; + return handle; } - return GetPrimitiveType(type.GetElementType()) + "[]"; - } - - if (type.IsEnum) - return type.FullName; - - return GetPrimitiveType(type); - } - - private static string GetPrimitiveType(Type type) - { - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: - return "bool"; - - case TypeCode.Byte: - return "uint8"; - - case TypeCode.Char: - return "char"; - - case TypeCode.Double: - return "float64"; - - case TypeCode.Int16: - return "int16"; - - case TypeCode.Int32: - return "int32"; - - case TypeCode.Int64: - return "int64"; - - case TypeCode.Object: - return "object"; - - case TypeCode.SByte: - return "int8"; - - case TypeCode.Single: - return "float32"; - - case TypeCode.String: - return "string"; - - case TypeCode.UInt16: - return "uint16"; - - case TypeCode.UInt32: - return "uint32"; - - case TypeCode.UInt64: - return "uint64"; - - default: - throw new ArgumentOutOfRangeException(nameof(type)); } - } - public enum MyEnum - { - Ctor, - Property + Assert.Fail($"Cannot find test type: {@namespace}.{name}"); + return default; } private class CustomAttributeTypeProvider : DisassemblingTypeProvider, ICustomAttributeTypeProvider { public string GetSystemType() { - return $"[{MetadataReaderTestHelpers.RuntimeAssemblyName}]System.Type"; + return "[System.Runtime]System.Type"; } public bool IsSystemType(string type) { - return type == $"[{MetadataReaderTestHelpers.RuntimeAssemblyName}]System.Type" // encountered as typeref - || Type.GetType(type) == typeof(Type); // encountered as serialized to reflection notation + return type == "[System.Runtime]System.Type"; } public string GetTypeFromSerializedName(string name) @@ -690,36 +255,23 @@ public string GetTypeFromSerializedName(string name) public PrimitiveTypeCode GetUnderlyingEnumType(string type) { - Type runtimeType = Type.GetType(type.Replace('/', '+')); // '/' vs '+' is only difference between ilasm and reflection notation for fixed set below. - - if (runtimeType == typeof(SByteEnum)) - return PrimitiveTypeCode.SByte; - - if (runtimeType == typeof(Int16Enum)) - return PrimitiveTypeCode.Int16; + // Strip assembly-qualified suffix if present (e.g. "Namespace.Type, Assembly, Version=...") + int commaIndex = type.IndexOf(','); + string typeName = commaIndex >= 0 ? type.Substring(0, commaIndex).Trim() : type; - if (runtimeType == typeof(Int32Enum)) - return PrimitiveTypeCode.Int32; - - if (runtimeType == typeof(Int64Enum)) - return PrimitiveTypeCode.Int64; - - if (runtimeType == typeof(ByteEnum)) - return PrimitiveTypeCode.Byte; - - if (runtimeType == typeof(UInt16Enum)) - return PrimitiveTypeCode.UInt16; - - if (runtimeType == typeof(UInt32Enum)) - return PrimitiveTypeCode.UInt32; - - if (runtimeType == typeof(UInt64Enum)) - return PrimitiveTypeCode.UInt64; - - if (runtimeType == typeof(MyEnum)) - return PrimitiveTypeCode.Byte; - - throw new ArgumentOutOfRangeException(); + return typeName switch + { + "CustomAttributeDecoderTests.SByteEnum" => PrimitiveTypeCode.SByte, + "CustomAttributeDecoderTests.Int16Enum" => PrimitiveTypeCode.Int16, + "CustomAttributeDecoderTests.Int32Enum" => PrimitiveTypeCode.Int32, + "CustomAttributeDecoderTests.Int64Enum" => PrimitiveTypeCode.Int64, + "CustomAttributeDecoderTests.ByteEnum" => PrimitiveTypeCode.Byte, + "CustomAttributeDecoderTests.UInt16Enum" => PrimitiveTypeCode.UInt16, + "CustomAttributeDecoderTests.UInt32Enum" => PrimitiveTypeCode.UInt32, + "CustomAttributeDecoderTests.UInt64Enum" => PrimitiveTypeCode.UInt64, + "CustomAttributeDecoderTests.MyEnum" => PrimitiveTypeCode.Int32, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; } } } diff --git a/src/libraries/System.Reflection.Metadata/tests/Resources/Decoding/CustomAttributeDecoder.cs b/src/libraries/System.Reflection.Metadata/tests/Resources/Decoding/CustomAttributeDecoder.cs new file mode 100644 index 00000000000000..8af1eb175e0522 --- /dev/null +++ b/src/libraries/System.Reflection.Metadata/tests/Resources/Decoding/CustomAttributeDecoder.cs @@ -0,0 +1,293 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Source for CustomAttributeDecoder.dll, which is embedded as a test resource. +// To rebuild the DLL, create a project targeting net9.0 or later with LangVersion=latest, +// compile this file, and copy the output to Resources/Decoding/CustomAttributeDecoder.dll. + +using System; + +namespace CustomAttributeDecoderTests +{ + public enum SByteEnum : sbyte { Value = -1 } + public enum Int16Enum : short { Value = -2 } + public enum Int32Enum : int { Value = -3 } + public enum Int64Enum : long { Value = -4 } + public enum ByteEnum : sbyte { Value = 1 } // named ByteEnum but uses sbyte underlying type to match existing test expectations + public enum UInt16Enum : ushort { Value = 2 } + public enum UInt32Enum : uint { Value = 3 } + public enum UInt64Enum : ulong { Value = 4 } + + public enum MyEnum { Ctor, Property } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public sealed class TestAttribute : Attribute + { + public TestAttribute() { } + public TestAttribute(string x, int y, double z) { } + public TestAttribute(string value) { } + public TestAttribute(object value) { } + public TestAttribute(sbyte value) { } + public TestAttribute(short value) { } + public TestAttribute(int value) { } + public TestAttribute(long value) { } + public TestAttribute(byte value) { } + public TestAttribute(ushort value) { } + public TestAttribute(uint value) { } + public TestAttribute(ulong value) { } + public TestAttribute(bool value) { } + public TestAttribute(float value) { } + public TestAttribute(double value) { } + public TestAttribute(Type value) { } + public TestAttribute(SByteEnum value) { } + public TestAttribute(Int16Enum value) { } + public TestAttribute(Int32Enum value) { } + public TestAttribute(Int64Enum value) { } + public TestAttribute(ByteEnum value) { } + public TestAttribute(UInt16Enum value) { } + public TestAttribute(UInt32Enum value) { } + public TestAttribute(UInt64Enum value) { } + public TestAttribute(string[] value) { } + public TestAttribute(object[] value) { } + public TestAttribute(sbyte[] value) { } + public TestAttribute(short[] value) { } + public TestAttribute(int[] value) { } + public TestAttribute(long[] value) { } + public TestAttribute(byte[] value) { } + public TestAttribute(ushort[] value) { } + public TestAttribute(uint[] value) { } + public TestAttribute(ulong[] value) { } + public TestAttribute(bool[] value) { } + public TestAttribute(float[] value) { } + public TestAttribute(double[] value) { } + public TestAttribute(Type[] value) { } + public TestAttribute(SByteEnum[] value) { } + public TestAttribute(Int16Enum[] value) { } + public TestAttribute(Int32Enum[] value) { } + public TestAttribute(Int64Enum[] value) { } + public TestAttribute(ByteEnum[] value) { } + public TestAttribute(UInt16Enum[] value) { } + public TestAttribute(UInt32Enum[] value) { } + public TestAttribute(UInt64Enum[] value) { } + + public string StringField; + public object ObjectField; + public sbyte SByteField; + public short Int16Field; + public int Int32Field; + public long Int64Field; + public byte ByteField; + public ushort UInt16Field; + public uint UInt32Field; + public ulong UInt64Field; + public bool BooleanField; + public float SingleField; + public double DoubleField; + public Type TypeField; + public SByteEnum SByteEnumField; + public Int16Enum Int16EnumField; + public Int32Enum Int32EnumField; + public Int64Enum Int64EnumField; + public ByteEnum ByteEnumField; + public UInt16Enum UInt16EnumField; + public UInt32Enum UInt32EnumField; + public UInt64Enum UInt64EnumField; + + public string[] StringArrayProperty { get; set; } + public object[] ObjectArrayProperty { get; set; } + public sbyte[] SByteArrayProperty { get; set; } + public short[] Int16ArrayProperty { get; set; } + public int[] Int32ArrayProperty { get; set; } + public long[] Int64ArrayProperty { get; set; } + public byte[] ByteArrayProperty { get; set; } + public ushort[] UInt16ArrayProperty { get; set; } + public uint[] UInt32ArrayProperty { get; set; } + public ulong[] UInt64ArrayProperty { get; set; } + public bool[] BooleanArrayProperty { get; set; } + public float[] SingleArrayProperty { get; set; } + public double[] DoubleArrayProperty { get; set; } + public Type[] TypeArrayProperty { get; set; } + public SByteEnum[] SByteEnumArrayProperty { get; set; } + public Int16Enum[] Int16EnumArrayProperty { get; set; } + public Int32Enum[] Int32EnumArrayProperty { get; set; } + public Int64Enum[] Int64EnumArrayProperty { get; set; } + public ByteEnum[] ByteEnumArrayProperty { get; set; } + public UInt16Enum[] UInt16EnumArrayProperty { get; set; } + public UInt32Enum[] UInt32EnumArrayProperty { get; set; } + public UInt64Enum[] UInt64EnumArrayProperty { get; set; } + } + + // no arguments + [Test] + + // multiple fixed arguments + [Test("0", 1, 2.0)] + + // multiple named arguments + [Test(StringField = "0", Int32Field = 1, SByteEnumArrayProperty = new[] { SByteEnum.Value })] + + // multiple fixed and named arguments + [Test("0", 1, 2.0, StringField = "0", Int32Field = 1, DoubleField = 2.0)] + + // single fixed null argument + [Test((object)null)] + [Test((string)null)] + [Test((Type)null)] + [Test((int[])null)] + + // single fixed arguments with strong type + [Test("string")] + [Test((sbyte)-1)] + [Test((short)-2)] + [Test((int)-4)] + [Test((long)-8)] + [Test((sbyte)-1)] + [Test((short)-2)] + [Test((int)-4)] + [Test((long)-8)] + [Test((byte)1)] + [Test((ushort)2)] + [Test((uint)4)] + [Test((ulong)8)] + [Test(true)] + [Test(false)] + [Test(typeof(string))] + [Test(new string[] { })] + [Test(new string[] { "x", "y", "z", null })] + + // same single fixed arguments as above, typed as object + [Test((object)("string"))] + [Test((object)(sbyte)-1)] + [Test((object)(short)-2)] + [Test((object)(int)-4)] + [Test((object)(long)-8)] + [Test((object)(sbyte)-1)] + [Test((object)(short)-2)] + [Test((object)(int)-4)] + [Test((object)(long)-8)] + [Test((object)(byte)1)] + [Test((object)(ushort)2)] + [Test((object)(uint)4)] + [Test((object)(true))] + [Test((object)(false))] + [Test((object)(typeof(string)))] + [Test((object)(SByteEnum.Value))] + [Test((object)(Int16Enum.Value))] + [Test((object)(Int32Enum.Value))] + [Test((object)(Int64Enum.Value))] + [Test((object)(ByteEnum.Value))] + [Test((object)(UInt16Enum.Value))] + [Test((object)(UInt32Enum.Value))] + [Test((object)(UInt64Enum.Value))] + [Test((object)(new string[] { }))] + [Test((object)(new string[] { "x", "y", "z", null }))] + [Test((object)(new Int32Enum[] { Int32Enum.Value }))] + + // same values as above two cases, but put into an object[] + [Test(new object[] { + "string", + (sbyte)-1, + (short)-2, + (int)-4, + (long)-8, + (sbyte)-1, + (short)-2, + (int)-4, + (long)-8, + (byte)1, + (ushort)2, + (uint)4, + true, + false, + typeof(string), + SByteEnum.Value, + Int16Enum.Value, + Int32Enum.Value, + Int64Enum.Value, + SByteEnum.Value, + Int16Enum.Value, + Int32Enum.Value, + Int64Enum.Value, + new string[] {}, + new string[] { "x", "y", "z", null }, + })] + + // same values as strongly-typed fixed arguments as named arguments + [Test(StringField = "string")] + [Test(SByteField = -1)] + [Test(Int16Field = -2)] + [Test(Int32Field = -4)] + [Test(Int64Field = -8)] + [Test(SByteField = -1)] + [Test(Int16Field = -2)] + [Test(Int32Field = -4)] + [Test(Int64Field = -8)] + [Test(ByteField = 1)] + [Test(UInt16Field = 2)] + [Test(UInt32Field = 4)] + [Test(UInt64Field = 8)] + [Test(BooleanField = true)] + [Test(BooleanField = false)] + [Test(TypeField = typeof(string))] + [Test(SByteEnumField = SByteEnum.Value)] + [Test(Int16EnumField = Int16Enum.Value)] + [Test(Int32EnumField = Int32Enum.Value)] + [Test(Int64EnumField = Int64Enum.Value)] + [Test(ByteEnumField = ByteEnum.Value)] + [Test(UInt16EnumField = UInt16Enum.Value)] + [Test(UInt32EnumField = UInt32Enum.Value)] + [Test(UInt64EnumField = UInt64Enum.Value)] + [Test(new string[] { })] + [Test(new string[] { "x", "y", "z", null })] + + // null named arguments + [Test(ObjectField = null)] + [Test(StringField = null)] + + [Test(Int32ArrayProperty = null)] + + public sealed class HasAttributes { } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class GenericAttribute : Attribute + { + public GenericAttribute() { } + public GenericAttribute(T value) + { + Field = value; + } + public GenericAttribute(T value, int count) + { + Field = value; + } + public T TProperty { get; set; } + public T[] TArrayProperty { get; set; } + public T Field; + } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class GenericAttribute2 : Attribute + { + public GenericAttribute2() { } + public GenericAttribute2(K key) { } + public GenericAttribute2(K key, V value) { } + public K Key { get; set; } + public V Value { get; set; } + public K[] ArrayProperty { get; set; } + } + + [GenericAttribute] + [GenericAttribute("Hello")] + [GenericAttribute(12)] + [GenericAttribute("Hello", 12, TProperty = "Bye")] + [GenericAttribute(1, TProperty = 2)] + [GenericAttribute2(true, 13)] + // [GenericAttribute(MyEnum.Property)] TODO: https://github.com/dotnet/runtime/issues/16552 + [GenericAttribute(typeof(HasAttributes))] + [GenericAttribute(TProperty = typeof(HasAttributes))] + public class HasGenericAttributes { } + + [GenericAttribute2(new int[] { 1, 2, 3 }, new byte[] { 4, 5 })] + [GenericAttribute(1, TProperty = 2, TArrayProperty = new byte[] { 3, 4 })] + public class HasGenericArrayAttributes { } +} diff --git a/src/libraries/System.Reflection.Metadata/tests/Resources/Decoding/CustomAttributeDecoder.dll b/src/libraries/System.Reflection.Metadata/tests/Resources/Decoding/CustomAttributeDecoder.dll new file mode 100644 index 00000000000000..3844243c0a3a64 Binary files /dev/null and b/src/libraries/System.Reflection.Metadata/tests/Resources/Decoding/CustomAttributeDecoder.dll differ diff --git a/src/libraries/System.Reflection.Metadata/tests/Resources/TestResources.cs b/src/libraries/System.Reflection.Metadata/tests/Resources/TestResources.cs index df78a0d222689a..c7735d507801f2 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Resources/TestResources.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Resources/TestResources.cs @@ -66,6 +66,11 @@ internal static class PortablePdbs public static readonly byte[] DocumentsEmbeddedDll = ResourceHelper.GetResource("PortablePdbs.Documents.Embedded.dll"); } + internal static class Decoding + { + public static readonly byte[] CustomAttributeDecoder = ResourceHelper.GetResource("Decoding.CustomAttributeDecoder.dll"); + } + internal static class SynthesizedPeImages { private static Lazy> _image1 = new Lazy>(GenerateImage); diff --git a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj index c87d9b4a6f2502..b457671f9de01c 100644 --- a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj +++ b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj @@ -146,6 +146,8 @@ + +