using System; using System.Collections.Generic; using System.Reflection.Metadata; namespace Semmle.Extraction.CIL { /// /// Provides methods for creating and caching various entities. /// public partial class Context { readonly Dictionary ids = new Dictionary(); public T Populate(T e) where T : ILabelledEntity { Id id = e.ShortId; if (ids.TryGetValue(id, out var existing)) { // It exists already e.Label = existing.Item1; e.ShortId = existing.Item2; // Reuse ID for efficiency } else { cx.DefineLabel(e); ids.Add(id, (e.Label, id)); cx.PopulateLater(() => { foreach (var c in e.Contents) c.Extract(this); }); } return e; } public IExtractedEntity Create(Handle h) { var entity = CreateGeneric(defaultGenericContext, h); return entity; } /// /// Creates an entity from a Handle in a GenericContext. /// The type of the returned entity depends on the type of the handle. /// The GenericContext is needed because some handles are generics which /// need to be expanded in terms of the current instantiation. If this sounds /// complex, you are right. /// /// The pair (h,genericContext) is cached in case it is needed again. /// /// The handle of the entity. /// The generic context. /// public ILabelledEntity CreateGeneric(GenericContext genericContext, Handle h) => genericHandleFactory[genericContext, h]; readonly GenericContext defaultGenericContext; ILabelledEntity CreateGenericHandle(GenericContext gc, Handle handle) { ILabelledEntity entity; switch (handle.Kind) { case HandleKind.MethodDefinition: entity = new Entities.DefinitionMethod(gc, (MethodDefinitionHandle)handle); break; case HandleKind.MemberReference: entity = Create(gc, (MemberReferenceHandle)handle); break; case HandleKind.MethodSpecification: entity = new Entities.MethodSpecificationMethod(gc, (MethodSpecificationHandle)handle); break; case HandleKind.FieldDefinition: entity = new Entities.DefinitionField(gc, (FieldDefinitionHandle)handle); break; case HandleKind.TypeReference: entity = new Entities.TypeReferenceType(this, (TypeReferenceHandle)handle); break; case HandleKind.TypeSpecification: entity = new Entities.TypeSpecificationType(gc, (TypeSpecificationHandle)handle); break; case HandleKind.TypeDefinition: entity = new Entities.TypeDefinitionType(this, (TypeDefinitionHandle)handle); break; default: throw new InternalError("Unhandled handle kind " + handle.Kind); } Populate(entity); return entity; } ILabelledEntity Create(GenericContext gc, MemberReferenceHandle handle) { var mr = mdReader.GetMemberReference(handle); switch (mr.GetKind()) { case MemberReferenceKind.Method: return new Entities.MemberReferenceMethod(gc, handle); case MemberReferenceKind.Field: return new Entities.MemberReferenceField(gc, handle); default: throw new InternalError("Unhandled member reference handle"); } } #region Strings readonly Dictionary stringHandleIds = new Dictionary(); readonly Dictionary stringIds = new Dictionary(); /// /// Return an ID containing the given string. /// /// The string handle. /// An ID. public StringId GetId(StringHandle h) { StringId result; if (!stringHandleIds.TryGetValue(h, out result)) { result = new StringId(mdReader.GetString(h)); stringHandleIds.Add(h, result); } return result; } public readonly StringId Dot = new StringId("."); /// /// Gets an ID containing the given string. /// Caches existing IDs for more compact storage. /// /// The string. /// An ID containing the string. public StringId GetId(string str) { StringId result; if (!stringIds.TryGetValue(str, out result)) { result = new StringId(str); stringIds.Add(str, result); } return result; } #endregion #region Namespaces readonly CachedFunction namespaceFactory; public Entities.Namespace CreateNamespace(StringHandle fqn) => namespaceFactory[fqn]; readonly Lazy globalNamespace, systemNamespace; /// /// The entity representing the global namespace. /// public Entities.Namespace GlobalNamespace => globalNamespace.Value; /// /// The entity representing the System namespace. /// public Entities.Namespace SystemNamespace => systemNamespace.Value; /// /// Creates a namespace from a fully-qualified name. /// /// The fully-qualified namespace name. /// The namespace entity. Entities.Namespace CreateNamespace(string fqn) => Populate(new Entities.Namespace(this, fqn)); readonly CachedFunction namespaceDefinitionFactory; /// /// Creates a namespace from a namespace handle. /// /// The handle of the namespace. /// The namespace entity. public Entities.Namespace Create(NamespaceDefinitionHandle handle) => namespaceDefinitionFactory[handle]; Entities.Namespace CreateNamespace(NamespaceDefinitionHandle handle) { if (handle.IsNil) return GlobalNamespace; NamespaceDefinition nd = mdReader.GetNamespaceDefinition(handle); return Populate(new Entities.Namespace(this, GetId(nd.Name), Create(nd.Parent))); } #endregion #region Locations readonly CachedFunction sourceFiles; readonly CachedFunction folders; readonly CachedFunction sourceLocations; /// /// Creates a source file entity from a PDB source file. /// /// The PDB source file. /// A source file entity. public Entities.PdbSourceFile CreateSourceFile(PDB.ISourceFile file) => sourceFiles[file]; /// /// Creates a folder entitiy with the given path. /// /// The path of the folder. /// A folder entity. public Entities.Folder CreateFolder(string path) => folders[path]; /// /// Creates a source location. /// /// The source location from PDB. /// A source location entity. public Entities.PdbSourceLocation CreateSourceLocation(PDB.Location loc) => sourceLocations[loc]; #endregion readonly CachedFunction genericHandleFactory; /// /// Gets the short name of a member, without the preceding interface qualifier. /// /// The handle of the name. /// The short name. public string ShortName(StringHandle handle) { string str = mdReader.GetString(handle); if (str.EndsWith(".ctor")) return ".ctor"; if (str.EndsWith(".cctor")) return ".cctor"; var dot = str.LastIndexOf('.'); return dot == -1 ? str : str.Substring(dot + 1); } } }