diff --git a/Mono.Cecil/AssemblyReader.cs b/Mono.Cecil/AssemblyReader.cs index b66c1622e..ee2c229a9 100644 --- a/Mono.Cecil/AssemblyReader.cs +++ b/Mono.Cecil/AssemblyReader.cs @@ -3598,6 +3598,12 @@ CustomAttributeArgument ReadCustomAttributeElement (TypeReference type) object ReadCustomAttributeElementValue (TypeReference type) { var etype = type.etype; + if (etype == ElementType.GenericInst) { + // The only way to get a generic here is that it's an enum on a generic type + // so for enum we don't need to know the generic arguments (they have no effect) + type = type.GetElementType (); + etype = type.etype; + } switch (etype) { case ElementType.String: diff --git a/Mono.Cecil/AssemblyWriter.cs b/Mono.Cecil/AssemblyWriter.cs index 25076ff32..3cba95edd 100644 --- a/Mono.Cecil/AssemblyWriter.cs +++ b/Mono.Cecil/AssemblyWriter.cs @@ -2993,6 +2993,10 @@ void WriteCustomAttributeValue (TypeReference type, object value) else WriteCustomAttributeEnumValue (type, value); break; + case ElementType.GenericInst: + // Generic instantiation can only happen for an enum (no other generic like types can appear in attribute value) + WriteCustomAttributeEnumValue (type, value); + break; default: WritePrimitiveValue (value); break; @@ -3099,6 +3103,14 @@ void WriteCustomAttributeFieldOrPropType (TypeReference type) WriteTypeReference (type); } return; + case ElementType.GenericInst: + // Generic instantiation can really only happen if it's an enum type since no other + // types are allowed in the attribute value. + // Enums are special in attribute data, they're encoded as ElementType.Enum followed by a typeref + // followed by the value. + WriteElementType (ElementType.Enum); + WriteTypeReference (type); + return; default: WriteElementType (etype); return; diff --git a/Test/Mono.Cecil.Tests/CompilationService.cs b/Test/Mono.Cecil.Tests/CompilationService.cs index 0c9d35c1b..f7ecc4c29 100644 --- a/Test/Mono.Cecil.Tests/CompilationService.cs +++ b/Test/Mono.Cecil.Tests/CompilationService.cs @@ -186,7 +186,7 @@ static Compilation GetCompilation (string name) case ".cs": return CS.CSharpCompilation.Create ( assemblyName, - new [] { CS.SyntaxFactory.ParseSyntaxTree (source) }, + new [] { CS.SyntaxFactory.ParseSyntaxTree (source, new CS.CSharpParseOptions (preprocessorSymbols: new string [] { "NET_CORE" })) }, references, new CS.CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release)); default: diff --git a/Test/Mono.Cecil.Tests/CustomAttributesTests.cs b/Test/Mono.Cecil.Tests/CustomAttributesTests.cs index 0c8524abb..a406c4179 100644 --- a/Test/Mono.Cecil.Tests/CustomAttributesTests.cs +++ b/Test/Mono.Cecil.Tests/CustomAttributesTests.cs @@ -546,6 +546,105 @@ public void DefineCustomAttributeFromBlob () module.Dispose (); } +#if NET_CORE + [Test] + public void BoxedEnumOnGenericArgumentOnType () + { + TestCSharp ("CustomAttributes.cs", module => { + var valueEnumGenericType = module.GetType ("BoxedValueEnumOnGenericType"); + + Assert.IsTrue (valueEnumGenericType.HasCustomAttributes); + Assert.AreEqual (1, valueEnumGenericType.CustomAttributes.Count); + + var attribute = valueEnumGenericType.CustomAttributes [0]; + Assert.AreEqual ("System.Void FooAttribute::.ctor(System.Object,System.Object)", + attribute.Constructor.FullName); + + Assert.IsTrue (attribute.HasConstructorArguments); + Assert.AreEqual (2, attribute.ConstructorArguments.Count); + + AssertCustomAttributeArgument ("(Object:(GenericWithEnum`1/OnGenericNumber:0))", attribute.ConstructorArguments [0]); + AssertCustomAttributeArgument ("(Object:(GenericWithEnum`1/OnGenericNumber:1))", attribute.ConstructorArguments [1]); + }); + } + + [Test] + public void EnumOnGenericArgumentOnType () + { + TestCSharp ("CustomAttributes.cs", module => { + var valueEnumGenericType = module.GetType ("ValueEnumOnGenericType"); + + Assert.IsTrue (valueEnumGenericType.HasCustomAttributes); + Assert.AreEqual (1, valueEnumGenericType.CustomAttributes.Count); + + var attribute = valueEnumGenericType.CustomAttributes [0]; + Assert.AreEqual ("System.Void FooAttribute::.ctor(GenericWithEnum`1/OnGenericNumber)", + attribute.Constructor.FullName); + + Assert.IsTrue (attribute.HasConstructorArguments); + Assert.AreEqual (1, attribute.ConstructorArguments.Count); + + AssertCustomAttributeArgument ("(GenericWithEnum`1/OnGenericNumber:1)", attribute.ConstructorArguments [0]); + }); + } + + [Test] + public void EnumOnGenericFieldOnType () + { + TestCSharp ("CustomAttributes.cs", module => { + var valueEnumGenericType = module.GetType ("FieldEnumOnGenericType"); + + Assert.IsTrue (valueEnumGenericType.HasCustomAttributes); + Assert.AreEqual (1, valueEnumGenericType.CustomAttributes.Count); + + var attribute = valueEnumGenericType.CustomAttributes [0]; + var argument = attribute.Fields.Where (a => a.Name == "NumberEnumField").First ().Argument; + + AssertCustomAttributeArgument ("(GenericWithEnum`1/OnGenericNumber:0)", argument); + }); + } + + [Test] + public void EnumOnGenericPropertyOnType () + { + TestCSharp ("CustomAttributes.cs", module => { + var valueEnumGenericType = module.GetType ("PropertyEnumOnGenericType"); + + Assert.IsTrue (valueEnumGenericType.HasCustomAttributes); + Assert.AreEqual (1, valueEnumGenericType.CustomAttributes.Count); + + var attribute = valueEnumGenericType.CustomAttributes [0]; + var argument = attribute.Properties.Where (a => a.Name == "NumberEnumProperty").First ().Argument; + + AssertCustomAttributeArgument ("(GenericWithEnum`1/OnGenericNumber:1)", argument); + }); + } + + [Test] + public void EnumDeclaredInGenericTypeArray () + { + TestCSharp ("CustomAttributes.cs", module => { + var type = module.GetType ("WithAttributeUsingNestedEnumArray"); + var attributes = type.CustomAttributes; + Assert.AreEqual (1, attributes.Count); + var attribute = attributes [0]; + Assert.AreEqual (1, attribute.Fields.Count); + var arg = attribute.Fields [0].Argument; + Assert.AreEqual ("System.Object", arg.Type.FullName); + + var argumentValue = (CustomAttributeArgument)arg.Value; + Assert.AreEqual ("GenericWithEnum`1/OnGenericNumber[]", argumentValue.Type.FullName); + var argumentValues = (CustomAttributeArgument [])argumentValue.Value; + + Assert.AreEqual ("GenericWithEnum`1/OnGenericNumber", argumentValues [0].Type.FullName); + Assert.AreEqual (0, (int)argumentValues [0].Value); + + Assert.AreEqual ("GenericWithEnum`1/OnGenericNumber", argumentValues [1].Type.FullName); + Assert.AreEqual (1, (int)argumentValues [1].Value); + }); + } +#endif + static void AssertCustomAttribute (string expected, CustomAttribute attribute) { Assert.AreEqual (expected, PrettyPrint (attribute)); @@ -643,7 +742,7 @@ static void PrettyPrint (TypeReference type, StringBuilder signature) if (type.IsArray) { ArrayType array = (ArrayType) type; signature.AppendFormat ("{0}[]", array.ElementType.etype.ToString ()); - } else if (type.etype == ElementType.None) { + } else if (type.etype == ElementType.None || type.etype == ElementType.GenericInst) { signature.Append (type.FullName); } else signature.Append (type.etype.ToString ()); diff --git a/Test/Resources/cs/CustomAttributes.cs b/Test/Resources/cs/CustomAttributes.cs index 11939ed20..530d654e4 100644 --- a/Test/Resources/cs/CustomAttributes.cs +++ b/Test/Resources/cs/CustomAttributes.cs @@ -11,6 +11,14 @@ enum Bingo : short { Binga = 4, } +class GenericWithEnum { + public enum OnGenericNumber + { + One, + Two + } +} + /* in System.Security.AccessControl @@ -70,6 +78,10 @@ public FooAttribute (Type type) { } + public FooAttribute (GenericWithEnum.OnGenericNumber number) + { + } + public int Bang { get { return 0; } set {} } public string Fiou { get { return "fiou"; } set {} } @@ -77,6 +89,9 @@ public FooAttribute (Type type) public string [] PanPan; public Type Chose; + + public GenericWithEnum.OnGenericNumber NumberEnumField; + public GenericWithEnum.OnGenericNumber NumberEnumProperty { get; set; } } [Foo ("bar")] @@ -160,3 +175,25 @@ public class Child { [Foo ("Foo\0Bar\0")] class NullCharInString { } + +#if NET_CORE +[Foo (GenericWithEnum.OnGenericNumber.One, GenericWithEnum.OnGenericNumber.Two)] +class BoxedValueEnumOnGenericType { +} + +[Foo (GenericWithEnum.OnGenericNumber.Two)] +class ValueEnumOnGenericType { +} + +[Foo (NumberEnumField = GenericWithEnum.OnGenericNumber.One)] +class FieldEnumOnGenericType { +} + +[Foo(NumberEnumProperty = GenericWithEnum.OnGenericNumber.Two)] +class PropertyEnumOnGenericType { +} + +[Foo(Pan = new[] { GenericWithEnum.OnGenericNumber.One, GenericWithEnum.OnGenericNumber.Two })] +class WithAttributeUsingNestedEnumArray { +} +#endif \ No newline at end of file