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);
}
}
}