1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections;
6 using System.Collections.Generic;
7 using System.Collections.ObjectModel;
8 
9 using Internal.Reflection.Tracing;
10 
11 namespace System.Reflection.Runtime.CustomAttributes
12 {
13     //
14     // Common base class for the Runtime's implementation of CustomAttributeData.
15     //
16     internal abstract partial class RuntimeCustomAttributeData : RuntimeImplementedCustomAttributeData
17     {
18         public abstract override Type AttributeType { get; }
19 
20         public abstract override ConstructorInfo Constructor { get; }
21 
22         public sealed override IList<CustomAttributeTypedArgument> ConstructorArguments
23         {
24             get
25             {
26 #if ENABLE_REFLECTION_TRACE
27                 if (ReflectionTrace.Enabled)
28                     ReflectionTrace.CustomAttributeData_ConstructorArguments(this);
29 #endif
30 
31                 return new ReadOnlyCollection<CustomAttributeTypedArgument>(GetConstructorArguments(throwIfMissingMetadata: true));
32             }
33         }
34 
35         // Equals/GetHashCode no need to override (they just implement reference equality but desktop never unified these things.)
36 
37         public sealed override IList<CustomAttributeNamedArgument> NamedArguments
38         {
39             get
40             {
41 #if ENABLE_REFLECTION_TRACE
42                 if (ReflectionTrace.Enabled)
43                     ReflectionTrace.CustomAttributeData_NamedArguments(this);
44 #endif
45 
46                 return new ReadOnlyCollection<CustomAttributeNamedArgument>(GetNamedArguments(throwIfMissingMetadata: true));
47             }
48         }
49 
ToString()50         public sealed override String ToString()
51         {
52             try
53             {
54                 String ctorArgs = "";
55                 IList<CustomAttributeTypedArgument> constructorArguments = GetConstructorArguments(throwIfMissingMetadata: false);
56                 if (constructorArguments == null)
57                     return LastResortToString;
58                 for (int i = 0; i < constructorArguments.Count; i++)
59                     ctorArgs += String.Format(i == 0 ? "{0}" : ", {0}", ComputeTypedArgumentString(constructorArguments[i], typed: false));
60 
61                 String namedArgs = "";
62                 IList<CustomAttributeNamedArgument> namedArguments = GetNamedArguments(throwIfMissingMetadata: false);
63                 if (namedArguments == null)
64                     return LastResortToString;
65                 for (int i = 0; i < namedArguments.Count; i++)
66                 {
67                     CustomAttributeNamedArgument namedArgument = namedArguments[i];
68 
69                     // Legacy: Desktop sets "typed" to "namedArgument.ArgumentType != typeof(Object)" - on Project N, this property is not available
70                     // (nor conveniently computable as it's not captured in the Project N metadata.) The only consequence is that for
71                     // the rare case of fields and properties typed "Object", we won't decorate the argument value with its actual type name.
72                     bool typed = true;
73                     namedArgs += String.Format(
74                         i == 0 && ctorArgs.Length == 0 ? "{0} = {1}" : ", {0} = {1}",
75                         namedArgument.MemberName,
76                         ComputeTypedArgumentString(namedArgument.TypedValue, typed));
77                 }
78 
79                 return String.Format("[{0}({1}{2})]", AttributeTypeString, ctorArgs, namedArgs);
80             }
81             catch (MissingMetadataException)
82             {
83                 return LastResortToString;
84             }
85         }
86 
ResolveAttributeConstructor(Type attributeType, Type[] parameterTypes)87         protected static ConstructorInfo ResolveAttributeConstructor(Type attributeType, Type[] parameterTypes)
88         {
89             int parameterCount = parameterTypes.Length;
90             foreach (ConstructorInfo candidate in attributeType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
91             {
92                 ParameterInfo[] candidateParameters = candidate.GetParametersNoCopy();
93                 if (parameterCount != candidateParameters.Length)
94                     continue;
95 
96                 for (int i = 0; i < parameterCount; i++)
97                 {
98                     if (!parameterTypes[i].Equals(candidateParameters[i]))
99                         continue;
100                 }
101 
102                 return candidate;
103             }
104 
105             throw new MissingMethodException();
106         }
107 
108         internal abstract String AttributeTypeString { get; }
109 
110         //
111         // If throwIfMissingMetadata is false, returns null rather than throwing a MissingMetadataException.
112         //
GetConstructorArguments(bool throwIfMissingMetadata)113         internal abstract IList<CustomAttributeTypedArgument> GetConstructorArguments(bool throwIfMissingMetadata);
114 
115         //
116         // If throwIfMissingMetadata is false, returns null rather than throwing a MissingMetadataException.
117         //
GetNamedArguments(bool throwIfMissingMetadata)118         internal abstract IList<CustomAttributeNamedArgument> GetNamedArguments(bool throwIfMissingMetadata);
119 
120         //
121         // Computes the ToString() value for a CustomAttributeTypedArgument struct.
122         //
ComputeTypedArgumentString(CustomAttributeTypedArgument cat, bool typed)123         private static String ComputeTypedArgumentString(CustomAttributeTypedArgument cat, bool typed)
124         {
125             Type argumentType = cat.ArgumentType;
126             if (argumentType == null)
127                 return cat.ToString();
128 
129             Object value = cat.Value;
130             if (argumentType.IsEnum)
131                 return String.Format(typed ? "{0}" : "({1}){0}", value, argumentType.FullName);
132 
133             if (value == null)
134                 return String.Format(typed ? "null" : "({0})null", argumentType.Name);
135 
136             if (argumentType.Equals(CommonRuntimeTypes.String))
137                 return String.Format("\"{0}\"", value);
138 
139             if (argumentType.Equals(CommonRuntimeTypes.Char))
140                 return String.Format("'{0}'", value);
141 
142             if (argumentType.Equals(CommonRuntimeTypes.Type))
143                 return String.Format("typeof({0})", ((Type)value).FullName);
144 
145             else if (argumentType.IsArray)
146             {
147                 String result = null;
148                 IList<CustomAttributeTypedArgument> array = value as IList<CustomAttributeTypedArgument>;
149 
150                 Type elementType = argumentType.GetElementType();
151                 result = String.Format(@"new {0}[{1}] {{ ", elementType.IsEnum ? elementType.FullName : elementType.Name, array.Count);
152 
153                 for (int i = 0; i < array.Count; i++)
154                     result += String.Format(i == 0 ? "{0}" : ", {0}", ComputeTypedArgumentString(array[i], elementType != CommonRuntimeTypes.Object));
155 
156                 return result += " }";
157             }
158 
159             return String.Format(typed ? "{0}" : "({1}){0}", value, argumentType.Name);
160         }
161 
162         private string LastResortToString
163         {
164             get
165             {
166                 // This emulates Object.ToString() for consistency with prior .Net Native implementations.
167                 return GetType().ToString();
168             }
169         }
170 
171         //
172         // Wrap a custom attribute argument (or an element of an array-typed custom attribute argument) in a CustomAttributeTypeArgument structure
173         // for insertion into a CustomAttributeData value.
174         //
WrapInCustomAttributeTypedArgument(Object value, Type argumentType)175         protected CustomAttributeTypedArgument WrapInCustomAttributeTypedArgument(Object value, Type argumentType)
176         {
177             if (argumentType.Equals(CommonRuntimeTypes.Object))
178             {
179                 // If the declared attribute type is System.Object, we must report the type based on the runtime value.
180                 if (value == null)
181                     argumentType = CommonRuntimeTypes.String;  // Why is null reported as System.String? Because that's what the desktop CLR does.
182                 else if (value is Type)
183                     argumentType = CommonRuntimeTypes.Type;    // value.GetType() will not actually be System.Type - rather it will be some internal implementation type. We only want to report it as System.Type.
184                 else
185                     argumentType = value.GetType();
186             }
187 
188             // Handle the array case
189             if (value is IEnumerable enumerableValue && !(value is string))
190             {
191                 if (!argumentType.IsArray)
192                     throw new BadImageFormatException();
193                 Type reportedElementType = argumentType.GetElementType();
194                 LowLevelListWithIList<CustomAttributeTypedArgument> elementTypedArguments = new LowLevelListWithIList<CustomAttributeTypedArgument>();
195                 foreach (Object elementValue in enumerableValue)
196                 {
197                     CustomAttributeTypedArgument elementTypedArgument = WrapInCustomAttributeTypedArgument(elementValue, reportedElementType);
198                     elementTypedArguments.Add(elementTypedArgument);
199                 }
200                 return new CustomAttributeTypedArgument(argumentType, new ReadOnlyCollection<CustomAttributeTypedArgument>(elementTypedArguments));
201             }
202             else
203             {
204                 return new CustomAttributeTypedArgument(argumentType, value);
205             }
206         }
207     }
208 }
209