using Semmle.Extraction.CIL.Entities; using System; using System.Collections.Generic; using System.IO; 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 : IExtractedEntity { if (e.Label.Valid) { return e; // Already populated } if (ids.TryGetValue(e, out var existing)) { // It exists already e.Label = existing; } else { e.Label = cx.GetNewLabel(); cx.DefineLabel(e, cx.TrapWriter.Writer, cx.Extractor); ids.Add(e, e.Label); cx.PopulateLater(() => { foreach (var c in e.Contents) c.Extract(this); }); #if DEBUG_LABELS using (var writer = new StringWriter()) { e.WriteId(writer); var id = writer.ToString(); if (debugLabels.TryGetValue(id, out IExtractedEntity? previousEntity)) { cx.Extractor.Message(new Message("Duplicate trap ID", id, null, severity: Util.Logging.Severity.Warning)); } else { debugLabels.Add(id, e); } } #endif } return e; } #if DEBUG_LABELS private readonly Dictionary debugLabels = new Dictionary(); #endif public IExtractedEntity Create(Handle h) { var entity = CreateGeneric(defaultGenericContext, h); return entity; } // Lazily cache primitive types. private readonly PrimitiveType[] primitiveTypes = new PrimitiveType[(int)PrimitiveTypeCode.Object + 1]; public PrimitiveType Create(PrimitiveTypeCode code) { PrimitiveType e = primitiveTypes[(int)code]; if (e is null) { e = new PrimitiveType(this, code); e.Label = cx.GetNewLabel(); cx.DefineLabel(e, cx.TrapWriter.Writer, cx.Extractor); primitiveTypes[(int)code] = e; } return e; } /// /// 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 IExtractedEntity CreateGeneric(GenericContext genericContext, Handle h) => genericHandleFactory[genericContext, h]; readonly GenericContext defaultGenericContext; IExtractedEntity CreateGenericHandle(GenericContext gc, Handle handle) { IExtractedEntity entity; switch (handle.Kind) { case HandleKind.MethodDefinition: entity = new DefinitionMethod(gc, (MethodDefinitionHandle)handle); break; case HandleKind.MemberReference: entity = Create(gc, (MemberReferenceHandle)handle); break; case HandleKind.MethodSpecification: entity = new MethodSpecificationMethod(gc, (MethodSpecificationHandle)handle); break; case HandleKind.FieldDefinition: entity = new DefinitionField(gc, (FieldDefinitionHandle)handle); break; case HandleKind.TypeReference: var tr = new TypeReferenceType(this, (TypeReferenceHandle)handle); if (tr.TryGetPrimitiveType(out var pt)) // Map special names like `System.Int32` to `int` return pt; entity = tr; break; case HandleKind.TypeSpecification: return Entities.Type.DecodeType(gc, (TypeSpecificationHandle)handle); case HandleKind.TypeDefinition: entity = new TypeDefinitionType(this, (TypeDefinitionHandle)handle); break; default: throw new InternalError("Unhandled handle kind " + handle.Kind); } Populate(entity); return entity; } IExtractedEntity Create(GenericContext gc, MemberReferenceHandle handle) { var mr = mdReader.GetMemberReference(handle); switch (mr.GetKind()) { case MemberReferenceKind.Method: return new MemberReferenceMethod(gc, handle); case MemberReferenceKind.Field: return new MemberReferenceField(gc, handle); default: throw new InternalError("Unhandled member reference handle"); } } /// /// Gets the string for a string handle. /// /// The string handle. /// The string. public string GetString(StringHandle h) => mdReader.GetString(h); #region Namespaces readonly CachedFunction namespaceFactory; public Namespace CreateNamespace(StringHandle fqn) => namespaceFactory[fqn]; readonly Lazy globalNamespace, systemNamespace; /// /// The entity representing the global namespace. /// public Namespace GlobalNamespace => globalNamespace.Value; /// /// The entity representing the System namespace. /// public Namespace SystemNamespace => systemNamespace.Value; /// /// Creates a namespace from a fully-qualified name. /// /// The fully-qualified namespace name. /// The namespace entity. Namespace CreateNamespace(string fqn) => Populate(new Namespace(this, fqn)); readonly CachedFunction namespaceDefinitionFactory; /// /// Creates a namespace from a namespace handle. /// /// The handle of the namespace. /// The namespace entity. public Namespace Create(NamespaceDefinitionHandle handle) => namespaceDefinitionFactory[handle]; Namespace CreateNamespace(NamespaceDefinitionHandle handle) { if (handle.IsNil) return GlobalNamespace; NamespaceDefinition nd = mdReader.GetNamespaceDefinition(handle); return Populate(new Namespace(this, GetString(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 PdbSourceFile CreateSourceFile(PDB.ISourceFile file) => sourceFiles[file]; /// /// Creates a folder entitiy with the given path. /// /// The path of the folder. /// A folder entity. public Folder CreateFolder(PathTransformer.ITransformedPath path) => folders[path]; /// /// Creates a source location. /// /// The source location from PDB. /// A source location entity. public 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); } } }