// Copyright (c) 2010-2018 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection.Metadata; using System.Text; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; namespace ICSharpCode.Decompiler.Documentation { /// /// Provides ID strings for entities. (C# 4.0 spec, §A.3.1) /// ID strings are used to identify members in XML documentation files. /// public static class IdStringProvider { #region GetIdString /// /// Gets the ID string (C# 4.0 spec, §A.3.1) for the specified entity. /// public static string GetIdString(this MetadataFile module, EntityHandle handle) { if (handle.IsNil) throw new ArgumentException("The handle must not be nil.", nameof(handle)); var metadata = module.Metadata; var b = new StringBuilder(); switch (handle.Kind) { case HandleKind.TypeDefinition: b.Append("T:"); AppendTypeDefinitionName(b, metadata, (TypeDefinitionHandle)handle); break; case HandleKind.FieldDefinition: b.Append("F:"); AppendFieldIdString(b, metadata, (FieldDefinitionHandle)handle); break; case HandleKind.MethodDefinition: b.Append("M:"); AppendMethodIdString(b, metadata, (MethodDefinitionHandle)handle); break; case HandleKind.PropertyDefinition: b.Append("P:"); AppendPropertyIdString(b, metadata, (PropertyDefinitionHandle)handle); break; case HandleKind.EventDefinition: b.Append("E:"); AppendEventIdString(b, metadata, (EventDefinitionHandle)handle); break; default: throw new ArgumentException($"Unsupported handle kind: {handle.Kind}", nameof(handle)); } return b.ToString(); } /// /// Gets the ID string (C# 4.0 spec, §A.3.1) for the specified entity. /// /// /// The ID string is computed from the entity's metadata; for specialized members /// it describes the underlying member definition. /// public static string GetIdString(this IEntity entity) { if (entity == null) throw new ArgumentNullException(nameof(entity)); var module = entity.ParentModule?.MetadataFile; if (module == null || entity.MetadataToken.IsNil) throw new NotSupportedException("Cannot compute an ID string for an entity that is not backed by metadata."); return GetIdString(module, entity.MetadataToken); } /// /// Appends the fully qualified name of a type definition, handling nested types. /// The metadata name already contains the `n arity suffix for generic types. /// static void AppendTypeDefinitionName(StringBuilder b, MetadataReader metadata, TypeDefinitionHandle handle) { var typeDef = metadata.GetTypeDefinition(handle); var declaringType = typeDef.GetDeclaringType(); if (declaringType.IsNil) { var ns = metadata.GetString(typeDef.Namespace); if (!string.IsNullOrEmpty(ns)) { b.Append(ns); b.Append('.'); } b.Append(metadata.GetString(typeDef.Name)); } else { AppendTypeDefinitionName(b, metadata, declaringType); b.Append('.'); b.Append(metadata.GetString(typeDef.Name)); } } static void AppendTypeReferenceName(StringBuilder b, MetadataReader metadata, TypeReference typeRef) { if (typeRef.ResolutionScope.Kind == HandleKind.TypeReference) { var outerRef = metadata.GetTypeReference((TypeReferenceHandle)typeRef.ResolutionScope); AppendTypeReferenceName(b, metadata, outerRef); b.Append('.'); b.Append(metadata.GetString(typeRef.Name)); } else { var ns = metadata.GetString(typeRef.Namespace); if (!string.IsNullOrEmpty(ns)) { b.Append(ns); b.Append('.'); } b.Append(metadata.GetString(typeRef.Name)); } } static void AppendFieldIdString(StringBuilder b, MetadataReader metadata, FieldDefinitionHandle handle) { var fieldDef = metadata.GetFieldDefinition(handle); var declaringType = fieldDef.GetDeclaringType(); AppendTypeDefinitionName(b, metadata, declaringType); b.Append('.'); b.Append(metadata.GetString(fieldDef.Name)); } static void AppendMethodIdString(StringBuilder b, MetadataReader metadata, MethodDefinitionHandle handle) { var methodDef = metadata.GetMethodDefinition(handle); var declaringType = methodDef.GetDeclaringType(); AppendTypeDefinitionName(b, metadata, declaringType); b.Append('.'); var methodName = metadata.GetString(methodDef.Name); b.Append(methodName.Replace('.', '#').Replace('<', '{').Replace('>', '}')); // Method type parameter count var genericParams = methodDef.GetGenericParameters(); if (genericParams.Count > 0) { b.Append("``"); b.Append(genericParams.Count); } // Parameters var signature = methodDef.DecodeSignature( new IdStringSignatureTypeProvider(), new MetadataGenericContext(handle, metadata)); AppendParameterList(b, signature.ParameterTypes); // Return type for conversion operators if (methodName is "op_Implicit" or "op_Explicit" or "op_CheckedExplicit") { b.Append('~'); b.Append(signature.ReturnType); } } static void AppendParameterList(StringBuilder b, ImmutableArray parameters) { if (parameters.Length > 0) { b.Append('('); for (int i = 0; i < parameters.Length; i++) { if (i > 0) b.Append(','); b.Append(parameters[i]); } b.Append(')'); } } static void AppendPropertyIdString(StringBuilder b, MetadataReader metadata, PropertyDefinitionHandle handle) { var propertyDef = metadata.GetPropertyDefinition(handle); var declaringType = FindDeclaringTypeOfProperty(metadata, handle); AppendTypeDefinitionName(b, metadata, declaringType); b.Append('.'); var signature = propertyDef.DecodeSignature( new IdStringSignatureTypeProvider(), new MetadataGenericContext(declaringType, metadata)); b.Append(metadata.GetString(propertyDef.Name).Replace('.', '#').Replace('<', '{').Replace('>', '}')); // Indexers have parameters AppendParameterList(b, signature.ParameterTypes); } static TypeDefinitionHandle FindDeclaringTypeOfProperty(MetadataReader metadata, PropertyDefinitionHandle propertyHandle) { var accessors = metadata.GetPropertyDefinition(propertyHandle).GetAccessors(); var accessor = !accessors.Getter.IsNil ? accessors.Getter : !accessors.Setter.IsNil ? accessors.Setter : accessors.Others.Length > 0 ? accessors.Others[0] : default; if (!accessor.IsNil) return metadata.GetMethodDefinition(accessor).GetDeclaringType(); // Accessor-less properties are invalid metadata; fall back to scanning all types. foreach (var typeHandle in metadata.TypeDefinitions) { var typeDef = metadata.GetTypeDefinition(typeHandle); foreach (var ph in typeDef.GetProperties()) { if (ph == propertyHandle) return typeHandle; } } return default; } static void AppendEventIdString(StringBuilder b, MetadataReader metadata, EventDefinitionHandle handle) { var eventDef = metadata.GetEventDefinition(handle); var declaringType = FindDeclaringTypeOfEvent(metadata, handle); AppendTypeDefinitionName(b, metadata, declaringType); b.Append('.'); b.Append(metadata.GetString(eventDef.Name).Replace('.', '#').Replace('<', '{').Replace('>', '}')); } static TypeDefinitionHandle FindDeclaringTypeOfEvent(MetadataReader metadata, EventDefinitionHandle eventHandle) { var accessors = metadata.GetEventDefinition(eventHandle).GetAccessors(); var accessor = !accessors.Adder.IsNil ? accessors.Adder : !accessors.Remover.IsNil ? accessors.Remover : !accessors.Raiser.IsNil ? accessors.Raiser : accessors.Others.Length > 0 ? accessors.Others[0] : default; if (!accessor.IsNil) return metadata.GetMethodDefinition(accessor).GetDeclaringType(); // Accessor-less events are invalid metadata; fall back to scanning all types. foreach (var typeHandle in metadata.TypeDefinitions) { var typeDef = metadata.GetTypeDefinition(typeHandle); foreach (var eh in typeDef.GetEvents()) { if (eh == eventHandle) return typeHandle; } } return default; } static bool IsAsciiDigit(char c) => c >= '0' && c <= '9'; /// /// Signature type provider that produces ID string fragments. /// readonly struct IdStringSignatureTypeProvider : ISignatureTypeProvider { public string GetPrimitiveType(PrimitiveTypeCode typeCode) { return typeCode switch { PrimitiveTypeCode.Void => "System.Void", PrimitiveTypeCode.Boolean => "System.Boolean", PrimitiveTypeCode.Char => "System.Char", PrimitiveTypeCode.SByte => "System.SByte", PrimitiveTypeCode.Byte => "System.Byte", PrimitiveTypeCode.Int16 => "System.Int16", PrimitiveTypeCode.UInt16 => "System.UInt16", PrimitiveTypeCode.Int32 => "System.Int32", PrimitiveTypeCode.UInt32 => "System.UInt32", PrimitiveTypeCode.Int64 => "System.Int64", PrimitiveTypeCode.UInt64 => "System.UInt64", PrimitiveTypeCode.Single => "System.Single", PrimitiveTypeCode.Double => "System.Double", PrimitiveTypeCode.String => "System.String", PrimitiveTypeCode.Object => "System.Object", PrimitiveTypeCode.IntPtr => "System.IntPtr", PrimitiveTypeCode.UIntPtr => "System.UIntPtr", PrimitiveTypeCode.TypedReference => "System.TypedReference", _ => throw new ArgumentOutOfRangeException(nameof(typeCode)) }; } public string GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) { var sb = new StringBuilder(); AppendTypeDefinitionName(sb, reader, handle); return sb.ToString(); } public string GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) { var sb = new StringBuilder(); var typeRef = reader.GetTypeReference(handle); AppendTypeReferenceName(sb, reader, typeRef); return sb.ToString(); } public string GetTypeFromSpecification(MetadataReader reader, MetadataGenericContext genericContext, TypeSpecificationHandle handle, byte rawTypeKind) { var typeSpec = reader.GetTypeSpecification(handle); return typeSpec.DecodeSignature(this, genericContext); } public string GetGenericTypeParameter(MetadataGenericContext genericContext, int index) { return "`" + index; } public string GetGenericMethodParameter(MetadataGenericContext genericContext, int index) { return "``" + index; } public string GetGenericInstantiation(string genericType, ImmutableArray typeArguments) { // The generic arguments must be distributed to their nesting level: // "Ns.Outer`1.Inner`2" + [A, B, C] => "Ns.Outer{A}.Inner{B,C}". // The uninstantiated name carries a `k arity marker at every generic // nesting level and the arguments are ordered outermost-first, so each // marker consumes the next k arguments. var sb = new StringBuilder(genericType.Length + typeArguments.Length * 16); int nextArgument = 0; for (int i = 0; i < genericType.Length; i++) { char c = genericType[i]; if (c != '`' || i + 1 >= genericType.Length || !IsAsciiDigit(genericType[i + 1])) { sb.Append(c); continue; } int markerEnd = i + 1; int arity = 0; while (markerEnd < genericType.Length && IsAsciiDigit(genericType[markerEnd])) { arity = arity * 10 + (genericType[markerEnd] - '0'); markerEnd++; } if (arity > typeArguments.Length - nextArgument) { // The name does not follow the `k arity convention; keep the // marker verbatim rather than inventing an argument split. sb.Append(genericType, i, markerEnd - i); } else { sb.Append('{'); for (int k = 0; k < arity; k++) { if (k > 0) sb.Append(','); sb.Append(typeArguments[nextArgument++]); } sb.Append('}'); } i = markerEnd - 1; } if (nextArgument < typeArguments.Length) { // Arity markers did not account for all arguments; append the // remainder so no argument is silently dropped. sb.Append('{'); for (int k = nextArgument; k < typeArguments.Length; k++) { if (k > nextArgument) sb.Append(','); sb.Append(typeArguments[k]); } sb.Append('}'); } return sb.ToString(); } public string GetArrayType(string elementType, ArrayShape shape) { // C# 4.0 spec, section A.3.1: each dimension is rendered as // "lowerbound:size", omitting either part when it is unspecified. // Compilers emit neither bound for single-dimensional arrays and // a zero lower bound with unspecified size ("0:") otherwise. var sb = new StringBuilder(elementType); sb.Append('['); if (shape.Rank > 1 || shape.LowerBounds.Length > 0 || shape.Sizes.Length > 0) { for (int i = 0; i < shape.Rank; i++) { if (i > 0) sb.Append(','); if (i < shape.LowerBounds.Length) { sb.Append(shape.LowerBounds[i]); sb.Append(':'); } if (i < shape.Sizes.Length) sb.Append(shape.Sizes[i]); } } sb.Append(']'); return sb.ToString(); } public string GetSZArrayType(string elementType) { return elementType + "[]"; } public string GetPointerType(string elementType) { return elementType + "*"; } public string GetByReferenceType(string elementType) { return elementType + "@"; } public string GetFunctionPointerType(MethodSignature signature) { //var sb = new StringBuilder("method "); //sb.Append(signature.ReturnType); //sb.Append(" *("); //for (int i = 0; i < signature.ParameterTypes.Length; i++) //{ // if (i > 0) // sb.Append(','); // sb.Append(signature.ParameterTypes[i]); //} //sb.Append(')'); //return sb.ToString(); // The C# spec does not define a syntax for function pointer types in ID strings // Roslyn just returns an empty string, so we'll do the same to avoid confusion. return ""; } public string GetModifiedType(string modifier, string unmodifiedType, bool isRequired) { // Custom modifiers are not part of the ID string: Roslyn ignores them (e.g. a // virtual method's 'in' parameter carries modreq(InAttribute) but is // documented as T@). return unmodifiedType; } public string GetPinnedType(string elementType) { // '^' following the modified type, per the MSVC xml doc format. Pinned // types cannot occur in member signatures, only in local variable // signatures, so no compiler ever generates this in an ID string. return elementType + "^"; } } #endregion #region GetTypeName public static string GetTypeName(IType type) { if (type == null) throw new ArgumentNullException(nameof(type)); StringBuilder b = new StringBuilder(); AppendTypeName(b, type, false); return b.ToString(); } static void AppendTypeName(StringBuilder b, IType type, bool explicitInterfaceImpl) { switch (type.Kind) { case TypeKind.Dynamic: b.Append(explicitInterfaceImpl ? "System#Object" : "System.Object"); break; case TypeKind.TypeParameter: ITypeParameter tp = (ITypeParameter)type; if (explicitInterfaceImpl) { b.Append(tp.Name); } else { b.Append('`'); if (tp.OwnerType == SymbolKind.Method) b.Append('`'); b.Append(tp.Index); } break; case TypeKind.Array: ArrayType array = (ArrayType)type; AppendTypeName(b, array.ElementType, explicitInterfaceImpl); b.Append('['); if (array.Dimensions > 1) { for (int i = 0; i < array.Dimensions; i++) { if (i > 0) b.Append(explicitInterfaceImpl ? '@' : ','); if (!explicitInterfaceImpl) b.Append("0:"); } } b.Append(']'); break; case TypeKind.Pointer: AppendTypeName(b, ((PointerType)type).ElementType, explicitInterfaceImpl); b.Append('*'); break; case TypeKind.ByReference: AppendTypeName(b, ((ByReferenceType)type).ElementType, explicitInterfaceImpl); b.Append('@'); break; default: IType declType = type.DeclaringType; if (declType != null) { AppendTypeName(b, declType, explicitInterfaceImpl); b.Append(explicitInterfaceImpl ? '#' : '.'); b.Append(type.Name); AppendTypeParameters(b, type, declType.TypeParameterCount, explicitInterfaceImpl); } else { if (explicitInterfaceImpl) b.Append(type.FullName.Replace('.', '#')); else b.Append(type.FullName); AppendTypeParameters(b, type, 0, explicitInterfaceImpl); } break; } } static void AppendTypeParameters(StringBuilder b, IType type, int outerTypeParameterCount, bool explicitInterfaceImpl) { int tpc = type.TypeParameterCount - outerTypeParameterCount; if (tpc > 0) { ParameterizedType pt = type as ParameterizedType; if (pt != null) { b.Append('{'); var ta = pt.TypeArguments; for (int i = outerTypeParameterCount; i < ta.Count; i++) { if (i > outerTypeParameterCount) b.Append(explicitInterfaceImpl ? '@' : ','); AppendTypeName(b, ta[i], explicitInterfaceImpl); } b.Append('}'); } else { b.Append('`'); b.Append(tpc); } } } #endregion #region ParseMemberName /// /// Parse the ID string into a member reference. /// /// The ID string representing the member (with "M:", "F:", "P:" or "E:" prefix). /// A member reference that represents the ID string. /// The syntax of the ID string is invalid /// /// The member reference will look in first, /// and if the member is not found there, /// it will look in all other assemblies of the compilation. /// public static IMemberReference ParseMemberIdString(string memberIdString) { if (memberIdString == null) throw new ArgumentNullException(nameof(memberIdString)); if (memberIdString.Length < 2 || memberIdString[1] != ':') throw new ReflectionNameParseException(0, "Missing type tag"); char typeChar = memberIdString[0]; int parenPos = memberIdString.IndexOf('('); if (parenPos < 0) parenPos = memberIdString.LastIndexOf('~'); if (parenPos < 0) parenPos = memberIdString.Length; int dotPos = memberIdString.LastIndexOf('.', parenPos - 1); if (dotPos < 0) throw new ReflectionNameParseException(0, "Could not find '.' separating type name from member name"); string typeName = memberIdString.Substring(0, dotPos); int pos = 2; ITypeReference typeReference = ParseTypeName(typeName, ref pos); if (pos != typeName.Length) throw new ReflectionNameParseException(pos, "Expected end of type name"); // string memberName = memberIDString.Substring(dotPos + 1, parenPos - (dotPos + 1)); // pos = memberName.LastIndexOf("``"); // if (pos > 0) // memberName = memberName.Substring(0, pos); // memberName = memberName.Replace('#', '.'); return new IdStringMemberReference(typeReference, typeChar, memberIdString); } #endregion #region ParseTypeName /// /// Parse the ID string type name into a type reference. /// /// The ID string representing the type (the "T:" prefix is optional). /// A type reference that represents the ID string. /// The syntax of the ID string is invalid /// /// /// The type reference will look in first, /// and if the type is not found there, /// it will look in all other assemblies of the compilation. /// /// /// If the type is open (contains type parameters '`0' or '``0'), /// an with the appropriate CurrentTypeDefinition/CurrentMember is required /// to resolve the reference to the ITypeParameter. /// /// public static ITypeReference ParseTypeName(string typeName) { if (typeName == null) throw new ArgumentNullException(nameof(typeName)); int pos = 0; if (typeName.StartsWith("T:", StringComparison.Ordinal)) pos = 2; ITypeReference r = ParseTypeName(typeName, ref pos); if (pos < typeName.Length) throw new ReflectionNameParseException(pos, "Expected end of type name"); return r; } static bool IsIDStringSpecialCharacter(char c) { switch (c) { case ':': case '{': case '}': case '[': case ']': case '(': case ')': case '`': case '*': case '@': case ',': return true; default: return false; } } static ITypeReference ParseTypeName(string typeName, ref int pos) { string reflectionTypeName = typeName; if (pos == typeName.Length) throw new ReflectionNameParseException(pos, "Unexpected end"); ITypeReference result; if (reflectionTypeName[pos] == '`') { // type parameter reference pos++; if (pos == reflectionTypeName.Length) throw new ReflectionNameParseException(pos, "Unexpected end"); if (reflectionTypeName[pos] == '`') { // method type parameter reference pos++; int index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos); result = TypeParameterReference.Create(SymbolKind.Method, index); } else { // class type parameter reference int index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos); result = TypeParameterReference.Create(SymbolKind.TypeDefinition, index); } } else { // not a type parameter reference: read the actual type name List typeArguments = new List(); string typeNameWithoutSuffix = ReadTypeName(typeName, ref pos, true, out int typeParameterCount, typeArguments); result = new GetPotentiallyNestedClassTypeReference(typeNameWithoutSuffix, typeParameterCount); while (pos < typeName.Length && typeName[pos] == '.') { pos++; string nestedTypeName = ReadTypeName(typeName, ref pos, false, out typeParameterCount, typeArguments); result = new NestedTypeReference(result, nestedTypeName, typeParameterCount); } if (typeArguments.Count > 0) { result = new ParameterizedTypeReference(result, typeArguments); } } while (pos < typeName.Length) { switch (typeName[pos]) { case '[': int dimensions = 1; do { pos++; if (pos == typeName.Length) throw new ReflectionNameParseException(pos, "Unexpected end"); if (typeName[pos] == ',') dimensions++; } while (typeName[pos] != ']'); result = new ArrayTypeReference(result, dimensions); break; case '*': result = new PointerTypeReference(result); break; case '@': result = new ByReferenceTypeReference(result); break; default: return result; } pos++; } return result; } static string ReadTypeName(string typeName, ref int pos, bool allowDottedName, out int typeParameterCount, List typeArguments) { int startPos = pos; // skip the simple name portion: while (pos < typeName.Length && !IsIDStringSpecialCharacter(typeName[pos]) && (allowDottedName || typeName[pos] != '.')) pos++; if (pos == startPos) throw new ReflectionNameParseException(pos, "Expected type name"); string shortTypeName = typeName.Substring(startPos, pos - startPos); // read type arguments: typeParameterCount = 0; if (pos < typeName.Length && typeName[pos] == '`') { // unbound generic type pos++; typeParameterCount = ReflectionHelper.ReadTypeParameterCount(typeName, ref pos); } else if (pos < typeName.Length && typeName[pos] == '{') { // bound generic type do { pos++; typeArguments.Add(ParseTypeName(typeName, ref pos)); typeParameterCount++; if (pos == typeName.Length) throw new ReflectionNameParseException(pos, "Unexpected end"); } while (typeName[pos] == ','); if (typeName[pos] != '}') throw new ReflectionNameParseException(pos, "Expected '}'"); pos++; } return shortTypeName; } #endregion #region FindEntity /// /// Finds the entity in the given type resolve context. /// /// ID string of the entity. /// Type resolve context /// Returns the entity, or null if it is not found. /// The syntax of the ID string is invalid public static IEntity FindEntity(string idString, ITypeResolveContext context) { if (idString == null) throw new ArgumentNullException(nameof(idString)); if (context == null) throw new ArgumentNullException(nameof(context)); if (idString.StartsWith("T:", StringComparison.Ordinal)) { return ParseTypeName(idString.Substring(2)).Resolve(context).GetDefinition(); } else { return ParseMemberIdString(idString).Resolve(context); } } #endregion } }