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. /// internal sealed partial class Context { private readonly Dictionary ids = new Dictionary(); internal 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 = GetNewLabel(); DefineLabel(e); ids.Add(e, e.Label); 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 var previousEntity)) { 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) { var e = primitiveTypes[(int)code]; if (e is null) { e = new PrimitiveType(this, code) { Label = GetNewLabel() }; DefineLabel(e); 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(IGenericContext genericContext, Handle h) => genericHandleFactory[genericContext, h]; private readonly IGenericContext defaultGenericContext; private IExtractedEntity CreateGenericHandle(IGenericContext 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.Context, (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; case HandleKind.StandaloneSignature: var signature = MdReader.GetStandaloneSignature((StandaloneSignatureHandle)handle); var method = signature.DecodeMethodSignature(gc.Context.TypeSignatureDecoder, gc); entity = new FunctionPointerType(this, method); break; default: throw new InternalError("Unhandled handle kind " + handle.Kind); } Populate(entity); return entity; } private IExtractedEntity Create(IGenericContext 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 private readonly CachedFunction namespaceFactory; public Namespace CreateNamespace(StringHandle fqn) => namespaceFactory[fqn]; private 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. private Namespace CreateNamespace(string fqn) => Populate(new Namespace(this, fqn)); private 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]; private Namespace CreateNamespace(NamespaceDefinitionHandle handle) { if (handle.IsNil) return GlobalNamespace; var nd = MdReader.GetNamespaceDefinition(handle); return Populate(new Namespace(this, GetString(nd.Name), Create(nd.Parent))); } #endregion #region Locations private readonly CachedFunction sourceFiles; private readonly CachedFunction folders; private 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 private 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) { var 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); } } }