C#: Extract generic types in CIL attribute extraction

This commit is contained in:
Tamas Vajk
2020-12-04 13:20:38 +01:00
parent 56eb04fe6d
commit db426c1ffe
9 changed files with 313 additions and 140 deletions

View File

@@ -70,10 +70,6 @@ namespace Semmle.Extraction.CIL.Entities
return h;
}
public override IEnumerable<Type> ThisTypeArguments => thisTypeArguments.EnumerateNull();
public override IEnumerable<Type> ThisGenericArguments => thisTypeArguments.EnumerateNull();
public override IEnumerable<IExtractionProduct> Contents
{
get
@@ -98,8 +94,6 @@ namespace Semmle.Extraction.CIL.Entities
public override Namespace ContainingNamespace => unboundGenericType.ContainingNamespace!;
public override int ThisTypeParameterCount => thisTypeArguments == null ? 0 : thisTypeArguments.Length;
public override CilTypeKind Kind => unboundGenericType.Kind;
public override Type Construct(IEnumerable<Type> typeArguments)
@@ -114,6 +108,12 @@ namespace Semmle.Extraction.CIL.Entities
public override void WriteAssemblyPrefix(TextWriter trapFile) => unboundGenericType.WriteAssemblyPrefix(trapFile);
public override int ThisTypeParameterCount => thisTypeArguments?.Length ?? 0;
public override IEnumerable<Type> TypeParameters => GenericArguments;
public override IEnumerable<Type> ThisTypeArguments => thisTypeArguments.EnumerateNull();
public override IEnumerable<Type> ThisGenericArguments => thisTypeArguments.EnumerateNull();
}
}

View File

@@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Reflection.Metadata;
namespace Semmle.Extraction.CIL.Entities
{
internal class GenericsHelper
internal static class GenericsHelper
{
public static TypeTypeParameter[] MakeTypeParameters(Type container, int count)
{
@@ -22,7 +23,7 @@ namespace Semmle.Extraction.CIL.Entities
public static string GetNonGenericName(string name)
{
var tick = name.IndexOf('`');
var tick = name.LastIndexOf('`');
return tick == -1
? name
: name.Substring(0, tick);
@@ -36,10 +37,23 @@ namespace Semmle.Extraction.CIL.Entities
public static int GetGenericTypeParameterCount(string name)
{
var tick = name.IndexOf('`');
var tick = name.LastIndexOf('`');
return tick == -1
? 0
: int.Parse(name.Substring(tick + 1));
}
public static IEnumerable<Type> GetAllTypeParameters(Type? container, IEnumerable<TypeTypeParameter> thisTypeParameters)
{
if (container != null)
{
foreach (var t in container.TypeParameters)
yield return t;
}
foreach (var t in thisTypeParameters)
yield return t;
}
}
}

View File

@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Semmle.Extraction.CIL.Entities
{
internal sealed partial class NoMetadataHandleType
{
/// <summary>
/// Parser to split a fully qualified name into short name, namespace or declaring type name, assembly name, and
/// type argument names. Names are in the following format:
/// <c>N1.N2.T1`2+T2`2[T3,[T4, A1, Version=...],T5,T6], A2, Version=...</c>
/// </summary>
/// <example>
/// <code>typeof(System.Collections.Generic.List<int>.Enumerator)
/// -> System.Collections.Generic.List`1+Enumerator[[System.Int32, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
/// typeof(System.Collections.Generic.List<>.Enumerator)
/// -> System.Collections.Generic.List`1+Enumerator, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
/// </code>
/// </example>
private class FullyQualifiedNameParser
{
public string ShortName { get; internal set; }
public string? AssemblyName { get; internal set; }
public IEnumerable<string>? TypeArguments { get; internal set; }
public string? UnboundGenericTypeName { get; internal set; }
public string ContainerName { get; internal set; }
public bool IsContainerNamespace { get; internal set; }
private string AssemblySuffix => string.IsNullOrWhiteSpace(AssemblyName) ? "" : $", {AssemblyName}";
public FullyQualifiedNameParser(string name)
{
ExtractAssemblyName(ref name, out var lastBracketIndex);
ExtractTypeArguments(ref name, lastBracketIndex, out var containerTypeArguments);
ExtractContainer(ref name, containerTypeArguments);
ShortName = name;
}
private void ExtractTypeArguments(ref string name, int lastBracketIndex, out string containerTypeArguments)
{
var firstBracketIndex = name.IndexOf('[');
if (firstBracketIndex < 0)
{
// not generic or non-constructed generic
TypeArguments = null;
containerTypeArguments = "";
UnboundGenericTypeName = null;
return;
}
// "T3,[T4, Assembly1, Version=...],T5,T6"
string typeArgs;
(name, _, typeArgs, _) = name.Split(firstBracketIndex, firstBracketIndex + 1, lastBracketIndex - firstBracketIndex - 1);
var thisTypeArgCount = GenericsHelper.GetGenericTypeParameterCount(name);
if (thisTypeArgCount == 0)
{
// not generic or non-constructed generic; container is constructed
TypeArguments = null;
containerTypeArguments = $"[{typeArgs}]";
UnboundGenericTypeName = null;
return;
}
// constructed generic
// "T3,[T4, Assembly1, Version=...]", ["T5", "T6"]
var (containerTypeArgs, thisTypeArgs) = ParseTypeArgumentStrings(typeArgs, thisTypeArgCount);
TypeArguments = thisTypeArgs;
if (string.IsNullOrWhiteSpace(containerTypeArgs))
{
// containing type is not constructed generics
containerTypeArguments = "";
}
else
{
// "T3,[T4, Assembly1, Version=...],,]"
containerTypeArguments = $"[{containerTypeArgs}]";
}
UnboundGenericTypeName = $"{name}{AssemblySuffix}";
}
private void ExtractContainer(ref string name, string containerTypeArguments)
{
var lastPlusIndex = name.LastIndexOf('+');
IsContainerNamespace = lastPlusIndex < 0;
if (IsContainerNamespace)
{
ExtractContainerNamespace(ref name);
}
else
{
ExtractContainerType(ref name, containerTypeArguments, lastPlusIndex);
}
}
private void ExtractContainerNamespace(ref string name)
{
var lastDotIndex = name.LastIndexOf('.');
if (lastDotIndex >= 0)
{
(ContainerName, _, name) = name.Split(lastDotIndex, lastDotIndex + 1);
}
else
{
ContainerName = ""; // global namespace name
}
}
private void ExtractContainerType(ref string name, string containerTypeArguments, int lastPlusIndex)
{
(ContainerName, _, name) = name.Split(lastPlusIndex, lastPlusIndex + 1);
ContainerName = $"{ContainerName}{containerTypeArguments}{AssemblySuffix}";
}
private void ExtractAssemblyName(ref string name, out int lastBracketIndex)
{
lastBracketIndex = name.LastIndexOf(']');
var assemblyCommaIndex = name.IndexOf(',', lastBracketIndex < 0 ? 0 : lastBracketIndex);
if (assemblyCommaIndex >= 0)
{
// "Assembly2, Version=..."
(name, _, AssemblyName) = name.Split(assemblyCommaIndex, assemblyCommaIndex + 2);
}
}
private static (string, IEnumerable<string>) ParseTypeArgumentStrings(string typeArgs, int thisTypeArgCount)
{
var thisTypeArgs = new Stack<string>(thisTypeArgCount);
while (typeArgs.Length > 0 && thisTypeArgCount > 0)
{
int startCurrentType;
if (typeArgs[^1] != ']')
{
startCurrentType = typeArgs.LastIndexOf(',') + 1;
thisTypeArgs.Push(typeArgs.Substring(startCurrentType));
}
else
{
var bracketCount = 1;
for (startCurrentType = typeArgs.Length - 2; startCurrentType >= 0 && bracketCount > 0; startCurrentType--)
{
if (typeArgs[startCurrentType] == ']')
{
bracketCount++;
}
else if (typeArgs[startCurrentType] == '[')
{
bracketCount--;
}
}
startCurrentType++;
thisTypeArgs.Push(typeArgs[(startCurrentType + 1)..^1]);
}
if (startCurrentType != 0)
{
typeArgs = typeArgs.Substring(0, startCurrentType - 1);
}
else
{
typeArgs = "";
}
thisTypeArgCount--;
}
return (typeArgs, thisTypeArgs.ToList());
}
}
}
}

View File

@@ -7,7 +7,7 @@ using Semmle.Util;
namespace Semmle.Extraction.CIL.Entities
{
internal sealed class NoMetadataHandleType : Type
internal sealed partial class NoMetadataHandleType : Type
{
private readonly string originalName;
private readonly string name;
@@ -15,87 +15,56 @@ namespace Semmle.Extraction.CIL.Entities
private readonly string containerName;
private readonly bool isContainerNamespace;
private readonly Lazy<TypeTypeParameter[]> typeParams;
private readonly Lazy<TypeTypeParameter[]>? typeParams;
// Either null or notEmpty
private readonly Type[]? thisTypeArguments;
private readonly Type unboundGenericType;
private readonly Type? containingType;
private readonly NamedTypeIdWriter idWriter;
public NoMetadataHandleType(Context cx, string originalName) : base(cx)
{
this.originalName = originalName;
this.name = originalName;
this.idWriter = new NamedTypeIdWriter(this);
// N1.N2.T1`3+T2`1[T3,[T4, Assembly1, Version=...],T5,T6], Assembly2, Version=...
// for example:
// typeof(System.Collections.Generic.List<int>.Enumerator)
// -> System.Collections.Generic.List`1+Enumerator[[System.Int32, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
// typeof(System.Collections.Generic.List<>.Enumerator)
// -> System.Collections.Generic.List`1+Enumerator, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
var nameParser = new FullyQualifiedNameParser(originalName);
var lastBracketIndex = name.LastIndexOf(']');
var assemblyCommaIndex = name.IndexOf(',', lastBracketIndex < 0 ? 0 : lastBracketIndex);
if (assemblyCommaIndex >= 0)
name = nameParser.ShortName;
assemblyName = nameParser.AssemblyName;
isContainerNamespace = nameParser.IsContainerNamespace;
containerName = nameParser.ContainerName;
unboundGenericType = nameParser.UnboundGenericTypeName == null
? this
: new NoMetadataHandleType(Cx, nameParser.UnboundGenericTypeName);
if (nameParser.TypeArguments != null)
{
assemblyName = name.Substring(assemblyCommaIndex + 2);
name = name.Substring(0, assemblyCommaIndex);
}
var firstBracketIndex = name.IndexOf('[');
if (firstBracketIndex >= 0)
{
// TODO:
// * create types for arguments.
// * this type is a constructed generic -> create non constructed one too.
// * adjust containing type name, which can also be a constructed generic
// thisTypeArguments =
// unboundGenericType =
// containerName =
throw new NotImplementedException();
// name = name.Substring(0, firstBracketIndex);
thisTypeArguments = nameParser.TypeArguments.Select(t => new NoMetadataHandleType(Cx, t)).ToArray();
}
else
{
thisTypeArguments = null;
unboundGenericType = this;
typeParams = new Lazy<TypeTypeParameter[]>(GenericsHelper.MakeTypeParameters(this, ThisTypeParameterCount));
}
var lastPlusIndex = name.LastIndexOf('+');
if (lastPlusIndex >= 0)
{
containerName = name.Substring(0, lastPlusIndex);
name = name.Substring(lastPlusIndex + 1);
isContainerNamespace = false;
}
else
{
var lastDotIndex = name.LastIndexOf('.');
if (lastDotIndex >= 0)
{
containerName = name.Substring(0, lastDotIndex);
name = name.Substring(lastDotIndex + 1);
}
else
{
containerName = cx.GlobalNamespace.Name;
}
isContainerNamespace = true;
}
containingType = isContainerNamespace
? null
: new NoMetadataHandleType(Cx, containerName);
this.typeParams = new Lazy<TypeTypeParameter[]>(GenericsHelper.MakeTypeParameters(this, ThisTypeParameterCount));
Populate();
}
private void Populate()
{
if (isContainerNamespace &&
!ContainingNamespace!.IsGlobalNamespace)
{
cx.Populate(ContainingNamespace);
Cx.Populate(ContainingNamespace);
}
cx.Populate(this);
Cx.Populate(this);
}
public override bool Equals(object? obj)
@@ -112,7 +81,7 @@ namespace Semmle.Extraction.CIL.Entities
{
get
{
foreach (var tp in typeParams.Value)
foreach (var tp in typeParams?.Value ?? Array.Empty<TypeTypeParameter>())
yield return tp;
foreach (var c in base.Contents)
@@ -137,19 +106,7 @@ namespace Semmle.Extraction.CIL.Entities
: new Namespace(Cx, containerName)
: null;
public override Type? ContainingType => isContainerNamespace
? null
: new NoMetadataHandleType(
Cx,
string.IsNullOrWhiteSpace(assemblyName)
? containerName
: containerName + ", " + assemblyName);
public override int ThisTypeParameterCount => GenericsHelper.GetGenericTypeParameterCount(name);
public override IEnumerable<Type> TypeParameters => typeParams.Value;
public override IEnumerable<Type> ThisTypeArguments => thisTypeArguments.EnumerateNull();
public override Type? ContainingType => containingType;
public override Type SourceDeclaration => unboundGenericType;
@@ -181,5 +138,21 @@ namespace Semmle.Extraction.CIL.Entities
{
idWriter.WriteId(trapFile, inContext);
}
public override int ThisTypeParameterCount => unboundGenericType == this
? GenericsHelper.GetGenericTypeParameterCount(name)
: thisTypeArguments!.Length;
public override IEnumerable<Type> TypeParameters => unboundGenericType == this
? GenericsHelper.GetAllTypeParameters(containingType, typeParams!.Value)
: GenericArguments;
public override IEnumerable<Type> ThisTypeArguments => unboundGenericType == this
? base.ThisTypeArguments
: thisTypeArguments!;
public override IEnumerable<Type> ThisGenericArguments => unboundGenericType == this
? typeParams!.Value
: thisTypeArguments!;
}
}

View File

@@ -31,29 +31,6 @@ namespace Semmle.Extraction.CIL.Entities
return null;
}
public IEnumerable<Type> TypeArguments
{
get
{
if (ContainingType != null)
{
foreach (var t in ContainingType.TypeArguments)
yield return t;
}
foreach (var t in ThisTypeArguments)
yield return t;
}
}
public virtual IEnumerable<Type> ThisTypeArguments
{
get
{
yield break;
}
}
/// <summary>
/// Writes the assembly identifier of this type.
/// </summary>
@@ -109,13 +86,25 @@ namespace Semmle.Extraction.CIL.Entities
public abstract Type Construct(IEnumerable<Type> typeArguments);
/// <summary>
/// The number of type arguments, or 0 if this isn't generic.
/// The containing type may also have type arguments.
/// Returns the type arguments of constructed types. For non-constructed types it returns an
/// empty collection.
/// </summary>
public virtual IEnumerable<Type> ThisTypeArguments
{
get
{
yield break;
}
}
/// <summary>
/// The number of type parameters for non-constructed generic types, the number of type arguments
/// for constructed types, or 0.
/// </summary>
public abstract int ThisTypeParameterCount { get; }
/// <summary>
/// The total number of type parameters (including parent types).
/// The total number of type parameters/type arguments (including parent types).
/// This is used for internal consistency checking only.
/// </summary>
public int TotalTypeParametersCount => ContainingType == null
@@ -123,8 +112,7 @@ namespace Semmle.Extraction.CIL.Entities
: ThisTypeParameterCount + ContainingType.TotalTypeParametersCount;
/// <summary>
/// Returns all bound/unbound generic arguments
/// of a constructed/unbound generic type.
/// Returns all bound/unbound generic arguments of a constructed/unbound generic type.
/// </summary>
public virtual IEnumerable<Type> ThisGenericArguments
{

View File

@@ -53,18 +53,6 @@ namespace Semmle.Extraction.CIL.Entities
public override Type? ContainingType => declType;
public override int ThisTypeParameterCount
{
get
{
var containingType = td.GetDeclaringType();
var parentTypeParameters = containingType.IsNil ? 0 :
Cx.MdReader.GetTypeDefinition(containingType).GetGenericParameters().Count;
return td.GetGenericParameters().Count - parentTypeParameters;
}
}
public override CilTypeKind Kind => CilTypeKind.ValueOrRefType;
public override Type Construct(IEnumerable<Type> typeArguments)
@@ -103,21 +91,23 @@ namespace Semmle.Extraction.CIL.Entities
return newTypeParams;
}
public override IEnumerable<Type> TypeParameters
public override int ThisTypeParameterCount
{
get
{
if (declType != null)
{
foreach (var t in declType.TypeParameters)
yield return t;
}
var containingType = td.GetDeclaringType();
var parentTypeParameters = containingType.IsNil
? 0
: Cx.MdReader.GetTypeDefinition(containingType).GetGenericParameters().Count;
foreach (var t in typeParams.Value)
yield return t;
return td.GetGenericParameters().Count - parentTypeParameters;
}
}
public override IEnumerable<Type> TypeParameters => GenericsHelper.GetAllTypeParameters(declType, typeParams!.Value);
public override IEnumerable<Type> ThisGenericArguments => typeParams.Value;
public override IEnumerable<IExtractionProduct> Contents
{
get

View File

@@ -52,17 +52,6 @@ namespace Semmle.Extraction.CIL.Entities
public override Namespace ContainingNamespace => Cx.CreateNamespace(tr.Namespace);
public override int ThisTypeParameterCount => GenericsHelper.GetGenericTypeParameterCount(tr.Name, Cx.MdReader);
public override IEnumerable<Type> ThisGenericArguments
{
get
{
foreach (var t in typeParams.Value)
yield return t;
}
}
public override Type? ContainingType
{
get
@@ -95,7 +84,11 @@ namespace Semmle.Extraction.CIL.Entities
}
}
public override IEnumerable<Type> TypeParameters => typeParams.Value;
public override int ThisTypeParameterCount => GenericsHelper.GetGenericTypeParameterCount(tr.Name, Cx.MdReader);
public override IEnumerable<Type> TypeParameters => GenericsHelper.GetAllTypeParameters(ContainingType, typeParams!.Value);
public override IEnumerable<Type> ThisGenericArguments => typeParams.Value;
public override void WriteId(TextWriter trapFile, bool inContext)
{

View File

@@ -17,7 +17,7 @@ namespace Semmle.Extraction.CIL
}
/// <summary>
/// The list of generic type parameters, including type parameters of
/// The list of generic type parameters/arguments, including type parameters/arguments of
/// containing types.
/// </summary>
public abstract IEnumerable<Entities.Type> TypeParameters { get; }

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
namespace Semmle.Extraction.CIL
{
public static class StringExtensions
{
public static (string, string) Split(this string self, int index0)
{
var split = self.Split(new[] { index0 });
return (split[0], split[1]);
}
public static (string, string, string) Split(this string self, int index0, int index1)
{
var split = self.Split(new[] { index0, index1 });
return (split[0], split[1], split[2]);
}
public static (string, string, string, string) Split(this string self, int index0, int index1, int index2)
{
var split = self.Split(new[] { index0, index1, index2 });
return (split[0], split[1], split[2], split[4]);
}
private static List<string> Split(this string self, params int[] indices)
{
var ret = new List<string>();
var previousIndex = 0;
foreach (var index in indices.OrderBy(i => i))
{
ret.Add(self.Substring(previousIndex, index - previousIndex));
previousIndex = index;
}
ret.Add(self.Substring(previousIndex));
return ret;
}
}
}