Skip to content

Commit db426c1

Browse files
committed
C#: Extract generic types in CIL attribute extraction
1 parent 56eb04f commit db426c1

File tree

9 files changed

+312
-139
lines changed

9 files changed

+312
-139
lines changed

csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ public override int GetHashCode()
7070
return h;
7171
}
7272

73-
public override IEnumerable<Type> ThisTypeArguments => thisTypeArguments.EnumerateNull();
74-
75-
public override IEnumerable<Type> ThisGenericArguments => thisTypeArguments.EnumerateNull();
76-
7773
public override IEnumerable<IExtractionProduct> Contents
7874
{
7975
get
@@ -98,8 +94,6 @@ public override IEnumerable<IExtractionProduct> Contents
9894

9995
public override Namespace ContainingNamespace => unboundGenericType.ContainingNamespace!;
10096

101-
public override int ThisTypeParameterCount => thisTypeArguments == null ? 0 : thisTypeArguments.Length;
102-
10397
public override CilTypeKind Kind => unboundGenericType.Kind;
10498

10599
public override Type Construct(IEnumerable<Type> typeArguments)
@@ -114,6 +108,12 @@ public override void WriteId(TextWriter trapFile, bool inContext)
114108

115109
public override void WriteAssemblyPrefix(TextWriter trapFile) => unboundGenericType.WriteAssemblyPrefix(trapFile);
116110

111+
public override int ThisTypeParameterCount => thisTypeArguments?.Length ?? 0;
112+
117113
public override IEnumerable<Type> TypeParameters => GenericArguments;
114+
115+
public override IEnumerable<Type> ThisTypeArguments => thisTypeArguments.EnumerateNull();
116+
117+
public override IEnumerable<Type> ThisGenericArguments => thisTypeArguments.EnumerateNull();
118118
}
119119
}

csharp/extractor/Semmle.Extraction.CIL/Entities/GenericsHelper.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
using System.Collections.Generic;
12
using System.Reflection.Metadata;
23

34
namespace Semmle.Extraction.CIL.Entities
45
{
5-
internal class GenericsHelper
6+
internal static class GenericsHelper
67
{
78
public static TypeTypeParameter[] MakeTypeParameters(Type container, int count)
89
{
@@ -22,7 +23,7 @@ public static string GetNonGenericName(StringHandle name, MetadataReader reader)
2223

2324
public static string GetNonGenericName(string name)
2425
{
25-
var tick = name.IndexOf('`');
26+
var tick = name.LastIndexOf('`');
2627
return tick == -1
2728
? name
2829
: name.Substring(0, tick);
@@ -36,10 +37,23 @@ public static int GetGenericTypeParameterCount(StringHandle name, MetadataReader
3637

3738
public static int GetGenericTypeParameterCount(string name)
3839
{
39-
var tick = name.IndexOf('`');
40+
var tick = name.LastIndexOf('`');
4041
return tick == -1
4142
? 0
4243
: int.Parse(name.Substring(tick + 1));
4344
}
45+
46+
public static IEnumerable<Type> GetAllTypeParameters(Type? container, IEnumerable<TypeTypeParameter> thisTypeParameters)
47+
{
48+
if (container != null)
49+
{
50+
foreach (var t in container.TypeParameters)
51+
yield return t;
52+
}
53+
54+
foreach (var t in thisTypeParameters)
55+
yield return t;
56+
57+
}
4458
}
4559
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Semmle.Extraction.CIL.Entities
6+
{
7+
internal sealed partial class NoMetadataHandleType
8+
{
9+
/// <summary>
10+
/// Parser to split a fully qualified name into short name, namespace or declaring type name, assembly name, and
11+
/// type argument names. Names are in the following format:
12+
/// <c>N1.N2.T1`2+T2`2[T3,[T4, A1, Version=...],T5,T6], A2, Version=...</c>
13+
/// </summary>
14+
/// <example>
15+
/// <code>typeof(System.Collections.Generic.List<int>.Enumerator)
16+
/// -> 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
17+
/// typeof(System.Collections.Generic.List<>.Enumerator)
18+
/// -> System.Collections.Generic.List`1+Enumerator, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
19+
/// </code>
20+
/// </example>
21+
private class FullyQualifiedNameParser
22+
{
23+
public string ShortName { get; internal set; }
24+
public string? AssemblyName { get; internal set; }
25+
public IEnumerable<string>? TypeArguments { get; internal set; }
26+
public string? UnboundGenericTypeName { get; internal set; }
27+
public string ContainerName { get; internal set; }
28+
public bool IsContainerNamespace { get; internal set; }
29+
30+
private string AssemblySuffix => string.IsNullOrWhiteSpace(AssemblyName) ? "" : $", {AssemblyName}";
31+
32+
public FullyQualifiedNameParser(string name)
33+
{
34+
ExtractAssemblyName(ref name, out var lastBracketIndex);
35+
ExtractTypeArguments(ref name, lastBracketIndex, out var containerTypeArguments);
36+
ExtractContainer(ref name, containerTypeArguments);
37+
38+
ShortName = name;
39+
}
40+
41+
private void ExtractTypeArguments(ref string name, int lastBracketIndex, out string containerTypeArguments)
42+
{
43+
var firstBracketIndex = name.IndexOf('[');
44+
if (firstBracketIndex < 0)
45+
{
46+
// not generic or non-constructed generic
47+
TypeArguments = null;
48+
containerTypeArguments = "";
49+
UnboundGenericTypeName = null;
50+
return;
51+
}
52+
53+
// "T3,[T4, Assembly1, Version=...],T5,T6"
54+
string typeArgs;
55+
(name, _, typeArgs, _) = name.Split(firstBracketIndex, firstBracketIndex + 1, lastBracketIndex - firstBracketIndex - 1);
56+
57+
var thisTypeArgCount = GenericsHelper.GetGenericTypeParameterCount(name);
58+
if (thisTypeArgCount == 0)
59+
{
60+
// not generic or non-constructed generic; container is constructed
61+
TypeArguments = null;
62+
containerTypeArguments = $"[{typeArgs}]";
63+
UnboundGenericTypeName = null;
64+
return;
65+
}
66+
67+
// constructed generic
68+
// "T3,[T4, Assembly1, Version=...]", ["T5", "T6"]
69+
var (containerTypeArgs, thisTypeArgs) = ParseTypeArgumentStrings(typeArgs, thisTypeArgCount);
70+
71+
TypeArguments = thisTypeArgs;
72+
73+
if (string.IsNullOrWhiteSpace(containerTypeArgs))
74+
{
75+
// containing type is not constructed generics
76+
containerTypeArguments = "";
77+
}
78+
else
79+
{
80+
// "T3,[T4, Assembly1, Version=...],,]"
81+
containerTypeArguments = $"[{containerTypeArgs}]";
82+
}
83+
84+
UnboundGenericTypeName = $"{name}{AssemblySuffix}";
85+
}
86+
87+
private void ExtractContainer(ref string name, string containerTypeArguments)
88+
{
89+
var lastPlusIndex = name.LastIndexOf('+');
90+
IsContainerNamespace = lastPlusIndex < 0;
91+
if (IsContainerNamespace)
92+
{
93+
ExtractContainerNamespace(ref name);
94+
}
95+
else
96+
{
97+
ExtractContainerType(ref name, containerTypeArguments, lastPlusIndex);
98+
}
99+
}
100+
101+
private void ExtractContainerNamespace(ref string name)
102+
{
103+
var lastDotIndex = name.LastIndexOf('.');
104+
if (lastDotIndex >= 0)
105+
{
106+
(ContainerName, _, name) = name.Split(lastDotIndex, lastDotIndex + 1);
107+
}
108+
else
109+
{
110+
ContainerName = ""; // global namespace name
111+
}
112+
}
113+
114+
private void ExtractContainerType(ref string name, string containerTypeArguments, int lastPlusIndex)
115+
{
116+
(ContainerName, _, name) = name.Split(lastPlusIndex, lastPlusIndex + 1);
117+
ContainerName = $"{ContainerName}{containerTypeArguments}{AssemblySuffix}";
118+
}
119+
120+
private void ExtractAssemblyName(ref string name, out int lastBracketIndex)
121+
{
122+
lastBracketIndex = name.LastIndexOf(']');
123+
var assemblyCommaIndex = name.IndexOf(',', lastBracketIndex < 0 ? 0 : lastBracketIndex);
124+
if (assemblyCommaIndex >= 0)
125+
{
126+
// "Assembly2, Version=..."
127+
(name, _, AssemblyName) = name.Split(assemblyCommaIndex, assemblyCommaIndex + 2);
128+
}
129+
}
130+
131+
private static (string, IEnumerable<string>) ParseTypeArgumentStrings(string typeArgs, int thisTypeArgCount)
132+
{
133+
var thisTypeArgs = new Stack<string>(thisTypeArgCount);
134+
while (typeArgs.Length > 0 && thisTypeArgCount > 0)
135+
{
136+
int startCurrentType;
137+
if (typeArgs[^1] != ']')
138+
{
139+
startCurrentType = typeArgs.LastIndexOf(',') + 1;
140+
thisTypeArgs.Push(typeArgs.Substring(startCurrentType));
141+
}
142+
else
143+
{
144+
var bracketCount = 1;
145+
for (startCurrentType = typeArgs.Length - 2; startCurrentType >= 0 && bracketCount > 0; startCurrentType--)
146+
{
147+
if (typeArgs[startCurrentType] == ']')
148+
{
149+
bracketCount++;
150+
}
151+
else if (typeArgs[startCurrentType] == '[')
152+
{
153+
bracketCount--;
154+
}
155+
}
156+
startCurrentType++;
157+
thisTypeArgs.Push(typeArgs[(startCurrentType + 1)..^1]);
158+
}
159+
160+
if (startCurrentType != 0)
161+
{
162+
typeArgs = typeArgs.Substring(0, startCurrentType - 1);
163+
}
164+
else
165+
{
166+
typeArgs = "";
167+
}
168+
thisTypeArgCount--;
169+
}
170+
return (typeArgs, thisTypeArgs.ToList());
171+
}
172+
}
173+
}
174+
}

0 commit comments

Comments
 (0)