Skip to content

Commit 56eb04f

Browse files
committed
C#: Improve attribute argument (type, enum) decoding in CIL extraction
1 parent 0c0ef77 commit 56eb04f

File tree

6 files changed

+261
-51
lines changed

6 files changed

+261
-51
lines changed

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,29 @@ public override IEnumerable<IExtractionProduct> Contents
5151

5252
for (var index = 0; index < decoded.FixedArguments.Length; ++index)
5353
{
54-
var value = decoded.FixedArguments[index].Value;
55-
var stringValue = GetStringValue(value);
54+
var stringValue = GetStringValue(decoded.FixedArguments[index].Type, decoded.FixedArguments[index].Value);
5655
yield return Tuples.cil_attribute_positional_argument(this, index, stringValue);
5756
}
5857

5958
foreach (var p in decoded.NamedArguments)
6059
{
61-
var value = p.Value;
62-
var stringValue = GetStringValue(value);
60+
var stringValue = GetStringValue(p.Type, p.Value);
6361
yield return Tuples.cil_attribute_named_argument(this, p.Name, stringValue);
6462
}
6563
}
6664
}
6765

68-
private static string GetStringValue(object? value)
66+
private static string GetStringValue(Type type, object? value)
6967
{
7068
if (value is System.Collections.Immutable.ImmutableArray<CustomAttributeTypedArgument<Type>> values)
7169
{
72-
return "[" + string.Join(",", values.Select(v => GetStringValue(v.Value))) + "]";
70+
return "[" + string.Join(",", values.Select(v => GetStringValue(v.Type, v.Value))) + "]";
71+
}
72+
73+
if (type.GetQualifiedName() == "System.Type" &&
74+
value is Type t)
75+
{
76+
return t.GetQualifiedName();
7377
}
7478

7579
return value?.ToString() ?? "null";

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal class CustomAttributeDecoder : ICustomAttributeTypeProvider<Type>
1414

1515
public Type GetPrimitiveType(PrimitiveTypeCode typeCode) => cx.Create(typeCode);
1616

17-
public Type GetSystemType() => throw new NotImplementedException();
17+
public Type GetSystemType() => new NoMetadataHandleType(cx, "System.Type");
1818

1919
public Type GetSZArrayType(Type elementType) =>
2020
cx.Populate(new ArrayType(cx, elementType));
@@ -25,10 +25,21 @@ public Type GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle ha
2525
public Type GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) =>
2626
(Type)cx.Create(handle);
2727

28-
public Type GetTypeFromSerializedName(string name) => throw new NotImplementedException();
29-
30-
public PrimitiveTypeCode GetUnderlyingEnumType(Type type) => throw new NotImplementedException();
31-
32-
public bool IsSystemType(Type type) => type is PrimitiveType; // ??
28+
public Type GetTypeFromSerializedName(string name) => new NoMetadataHandleType(cx, name);
29+
30+
public PrimitiveTypeCode GetUnderlyingEnumType(Type type)
31+
{
32+
if (type is TypeDefinitionType tdt &&
33+
tdt.GetUnderlyingEnumType() is var underlying &&
34+
underlying.HasValue)
35+
{
36+
return underlying.Value;
37+
}
38+
39+
// We can't fall back to Int32, because the type returned here defines how many bytes are read from the
40+
// stream and how those bytes are interpreted.
41+
throw new NotImplementedException();
42+
}
43+
public bool IsSystemType(Type type) => type.GetQualifiedName() == "System.Type";
3344
}
3445
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Reflection.Metadata;
2+
3+
namespace Semmle.Extraction.CIL.Entities
4+
{
5+
internal class GenericsHelper
6+
{
7+
public static TypeTypeParameter[] MakeTypeParameters(Type container, int count)
8+
{
9+
var newTypeParams = new TypeTypeParameter[count];
10+
for (var i = 0; i < newTypeParams.Length; ++i)
11+
{
12+
newTypeParams[i] = new TypeTypeParameter(container, container, i);
13+
}
14+
return newTypeParams;
15+
}
16+
17+
public static string GetNonGenericName(StringHandle name, MetadataReader reader)
18+
{
19+
var n = reader.GetString(name);
20+
return GetNonGenericName(n);
21+
}
22+
23+
public static string GetNonGenericName(string name)
24+
{
25+
var tick = name.IndexOf('`');
26+
return tick == -1
27+
? name
28+
: name.Substring(0, tick);
29+
}
30+
31+
public static int GetGenericTypeParameterCount(StringHandle name, MetadataReader reader)
32+
{
33+
var n = reader.GetString(name);
34+
return GetGenericTypeParameterCount(n);
35+
}
36+
37+
public static int GetGenericTypeParameterCount(string name)
38+
{
39+
var tick = name.IndexOf('`');
40+
return tick == -1
41+
? 0
42+
: int.Parse(name.Substring(tick + 1));
43+
}
44+
}
45+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using Semmle.Util;
7+
8+
namespace Semmle.Extraction.CIL.Entities
9+
{
10+
internal sealed class NoMetadataHandleType : Type
11+
{
12+
private readonly string originalName;
13+
private readonly string name;
14+
private readonly string? assemblyName;
15+
private readonly string containerName;
16+
private readonly bool isContainerNamespace;
17+
18+
private readonly Lazy<TypeTypeParameter[]> typeParams;
19+
20+
// Either null or notEmpty
21+
private readonly Type[]? thisTypeArguments;
22+
private readonly Type unboundGenericType;
23+
24+
private readonly NamedTypeIdWriter idWriter;
25+
26+
public NoMetadataHandleType(Context cx, string originalName) : base(cx)
27+
{
28+
this.originalName = originalName;
29+
this.name = originalName;
30+
this.idWriter = new NamedTypeIdWriter(this);
31+
32+
// N1.N2.T1`3+T2`1[T3,[T4, Assembly1, Version=...],T5,T6], Assembly2, Version=...
33+
// for example:
34+
// typeof(System.Collections.Generic.List<int>.Enumerator)
35+
// -> 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
36+
// typeof(System.Collections.Generic.List<>.Enumerator)
37+
// -> System.Collections.Generic.List`1+Enumerator, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
38+
39+
var lastBracketIndex = name.LastIndexOf(']');
40+
var assemblyCommaIndex = name.IndexOf(',', lastBracketIndex < 0 ? 0 : lastBracketIndex);
41+
if (assemblyCommaIndex >= 0)
42+
{
43+
assemblyName = name.Substring(assemblyCommaIndex + 2);
44+
name = name.Substring(0, assemblyCommaIndex);
45+
}
46+
47+
var firstBracketIndex = name.IndexOf('[');
48+
if (firstBracketIndex >= 0)
49+
{
50+
// TODO:
51+
// * create types for arguments.
52+
// * this type is a constructed generic -> create non constructed one too.
53+
// * adjust containing type name, which can also be a constructed generic
54+
55+
// thisTypeArguments =
56+
// unboundGenericType =
57+
// containerName =
58+
59+
throw new NotImplementedException();
60+
// name = name.Substring(0, firstBracketIndex);
61+
}
62+
else
63+
{
64+
thisTypeArguments = null;
65+
unboundGenericType = this;
66+
}
67+
68+
var lastPlusIndex = name.LastIndexOf('+');
69+
if (lastPlusIndex >= 0)
70+
{
71+
containerName = name.Substring(0, lastPlusIndex);
72+
name = name.Substring(lastPlusIndex + 1);
73+
isContainerNamespace = false;
74+
}
75+
else
76+
{
77+
var lastDotIndex = name.LastIndexOf('.');
78+
if (lastDotIndex >= 0)
79+
{
80+
containerName = name.Substring(0, lastDotIndex);
81+
name = name.Substring(lastDotIndex + 1);
82+
}
83+
else
84+
{
85+
containerName = cx.GlobalNamespace.Name;
86+
}
87+
isContainerNamespace = true;
88+
}
89+
90+
this.typeParams = new Lazy<TypeTypeParameter[]>(GenericsHelper.MakeTypeParameters(this, ThisTypeParameterCount));
91+
92+
if (isContainerNamespace &&
93+
!ContainingNamespace!.IsGlobalNamespace)
94+
{
95+
cx.Populate(ContainingNamespace);
96+
}
97+
98+
cx.Populate(this);
99+
}
100+
101+
public override bool Equals(object? obj)
102+
{
103+
return obj is NoMetadataHandleType t && originalName.Equals(t.originalName, StringComparison.Ordinal);
104+
}
105+
106+
public override int GetHashCode()
107+
{
108+
return originalName.GetHashCode(StringComparison.Ordinal);
109+
}
110+
111+
public override IEnumerable<IExtractionProduct> Contents
112+
{
113+
get
114+
{
115+
foreach (var tp in typeParams.Value)
116+
yield return tp;
117+
118+
foreach (var c in base.Contents)
119+
yield return c;
120+
121+
var i = 0;
122+
foreach (var type in ThisTypeArguments)
123+
{
124+
yield return type;
125+
yield return Tuples.cil_type_argument(this, i++, type);
126+
}
127+
}
128+
}
129+
130+
public override CilTypeKind Kind => CilTypeKind.ValueOrRefType;
131+
132+
public override string Name => GenericsHelper.GetNonGenericName(name);
133+
134+
public override Namespace? ContainingNamespace => isContainerNamespace
135+
? containerName == Cx.GlobalNamespace.Name
136+
? Cx.GlobalNamespace
137+
: new Namespace(Cx, containerName)
138+
: null;
139+
140+
public override Type? ContainingType => isContainerNamespace
141+
? null
142+
: new NoMetadataHandleType(
143+
Cx,
144+
string.IsNullOrWhiteSpace(assemblyName)
145+
? containerName
146+
: containerName + ", " + assemblyName);
147+
148+
public override int ThisTypeParameterCount => GenericsHelper.GetGenericTypeParameterCount(name);
149+
150+
public override IEnumerable<Type> TypeParameters => typeParams.Value;
151+
152+
public override IEnumerable<Type> ThisTypeArguments => thisTypeArguments.EnumerateNull();
153+
154+
public override Type SourceDeclaration => unboundGenericType;
155+
156+
public override Type Construct(IEnumerable<Type> typeArguments)
157+
{
158+
if (TotalTypeParametersCount != typeArguments.Count())
159+
throw new InternalError("Mismatched type arguments");
160+
161+
return Cx.Populate(new ConstructedType(Cx, this, typeArguments));
162+
}
163+
164+
public override void WriteAssemblyPrefix(TextWriter trapFile)
165+
{
166+
if (!string.IsNullOrWhiteSpace(assemblyName))
167+
{
168+
var an = new AssemblyName(assemblyName);
169+
trapFile.Write(an.Name);
170+
trapFile.Write('_');
171+
trapFile.Write((an.Version ?? new Version(0, 0, 0, 0)).ToString());
172+
trapFile.Write(Type.AssemblyTypeNameSeparator);
173+
}
174+
else
175+
{
176+
Cx.WriteAssemblyPrefix(trapFile);
177+
}
178+
}
179+
180+
public override void WriteId(TextWriter trapFile, bool inContext)
181+
{
182+
idWriter.WriteId(trapFile, inContext);
183+
}
184+
}
185+
}

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,7 @@ public override void WriteId(TextWriter trapFile, bool inContext)
4747
idWriter.WriteId(trapFile, inContext);
4848
}
4949

50-
public override string Name
51-
{
52-
get
53-
{
54-
var name = Cx.GetString(td.Name);
55-
var tick = name.IndexOf('`');
56-
return tick == -1 ? name : name.Substring(0, tick);
57-
}
58-
}
50+
public override string Name => GenericsHelper.GetNonGenericName(td.Name, Cx.MdReader);
5951

6052
public override Namespace ContainingNamespace => Cx.Create(td.NamespaceDefinition);
6153

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

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public sealed class TypeReferenceType : Type
2121
public TypeReferenceType(Context cx, TypeReferenceHandle handle) : base(cx)
2222
{
2323
this.idWriter = new NamedTypeIdWriter(this);
24-
this.typeParams = new Lazy<TypeTypeParameter[]>(MakeTypeParameters);
2524
this.handle = handle;
2625
this.tr = cx.MdReader.GetTypeReference(handle);
26+
this.typeParams = new Lazy<TypeTypeParameter[]>(GenericsHelper.MakeTypeParameters(this, ThisTypeParameterCount));
2727
}
2828

2929
public override bool Equals(object? obj)
@@ -36,16 +36,6 @@ public override int GetHashCode()
3636
return handle.GetHashCode();
3737
}
3838

39-
private TypeTypeParameter[] MakeTypeParameters()
40-
{
41-
var newTypeParams = new TypeTypeParameter[ThisTypeParameterCount];
42-
for (var i = 0; i < newTypeParams.Length; ++i)
43-
{
44-
newTypeParams[i] = new TypeTypeParameter(this, this, i);
45-
}
46-
return newTypeParams;
47-
}
48-
4939
public override IEnumerable<IExtractionProduct> Contents
5040
{
5141
get
@@ -58,28 +48,11 @@ public override IEnumerable<IExtractionProduct> Contents
5848
}
5949
}
6050

61-
public override string Name
62-
{
63-
get
64-
{
65-
var name = Cx.GetString(tr.Name);
66-
var tick = name.IndexOf('`');
67-
return tick == -1 ? name : name.Substring(0, tick);
68-
}
69-
}
51+
public override string Name => GenericsHelper.GetNonGenericName(tr.Name, Cx.MdReader);
7052

7153
public override Namespace ContainingNamespace => Cx.CreateNamespace(tr.Namespace);
7254

73-
public override int ThisTypeParameterCount
74-
{
75-
get
76-
{
77-
// Parse the name
78-
var name = Cx.GetString(tr.Name);
79-
var tick = name.IndexOf('`');
80-
return tick == -1 ? 0 : int.Parse(name.Substring(tick + 1));
81-
}
82-
}
55+
public override int ThisTypeParameterCount => GenericsHelper.GetGenericTypeParameterCount(tr.Name, Cx.MdReader);
8356

8457
public override IEnumerable<Type> ThisGenericArguments
8558
{

0 commit comments

Comments
 (0)