|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.UnitTests
{
public class MetadataHelpersTests
{
[Fact]
public void IsValidMetadataIdentifier()
{
string lowSurrogate = "\uDC00";
string highSurrogate = "\uD800";
Assert.True(Char.IsLowSurrogate(lowSurrogate, 0));
Assert.True(Char.IsHighSurrogate(highSurrogate, 0));
Assert.True(Char.IsSurrogatePair(highSurrogate + lowSurrogate, 0));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(null));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(""));
Assert.True(MetadataHelpers.IsValidMetadataIdentifier("x"));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier("\0"));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier("x\0"));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier("\0x"));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier("abc\0xyz\0uwq"));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(lowSurrogate));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(highSurrogate));
Assert.True(MetadataHelpers.IsValidMetadataIdentifier(highSurrogate + lowSurrogate));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(lowSurrogate + highSurrogate));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(highSurrogate + "x" + lowSurrogate));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(lowSurrogate + "x" + highSurrogate));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(highSurrogate + "xxx"));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(lowSurrogate + "xxx"));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(lowSurrogate + "\0" + highSurrogate));
Assert.False(MetadataHelpers.IsValidMetadataIdentifier(highSurrogate + "\0" + lowSurrogate));
}
private enum ArrayKind
{
None,
SzArray,
SingleDimensional,
MultiDimensional,
JaggedSzArray,
Jagged
};
private readonly struct TypeNameConfig
{
public readonly int NestingLevel;
public readonly TypeNameConfig[] GenericParamsConfig;
public readonly int PointerCount;
public readonly ArrayKind ArrayKind;
public readonly bool AssemblyQualified;
public TypeNameConfig(int nestingLevel, TypeNameConfig[] genericParamsConfig, int pointerCount, ArrayKind arrayKind, bool assemblyQualified)
{
this.NestingLevel = nestingLevel;
this.GenericParamsConfig = genericParamsConfig;
this.PointerCount = pointerCount;
this.ArrayKind = arrayKind;
this.AssemblyQualified = assemblyQualified;
}
}
private static TypeNameConfig[] GenerateTypeNameConfigs(int typeParamStackDepth)
{
var builder = ArrayBuilder<TypeNameConfig>.GetInstance();
for (int nestingLevel = 0; nestingLevel <= 2; nestingLevel++)
{
for (int pointerCount = 0; pointerCount <= 2; pointerCount++)
{
foreach (ArrayKind arrayKind in Enum.GetValues(typeof(ArrayKind)))
{
var genericParamsConfigBuilder = ArrayBuilder<TypeNameConfig[]>.GetInstance();
genericParamsConfigBuilder.Add(null);
if (typeParamStackDepth < 2)
{
genericParamsConfigBuilder.Add(GenerateTypeNameConfigs(typeParamStackDepth + 1));
}
foreach (var genericParamsConfig in genericParamsConfigBuilder.ToImmutableAndFree())
{
builder.Add(new TypeNameConfig(nestingLevel, genericParamsConfig, pointerCount, arrayKind, assemblyQualified: true));
builder.Add(new TypeNameConfig(nestingLevel, genericParamsConfig, pointerCount, arrayKind, assemblyQualified: false));
}
}
}
}
return builder.ToArrayAndFree();
}
private static string[] GenerateTypeNamesToDecode(TypeNameConfig[] typeNameConfigs, out MetadataHelpers.AssemblyQualifiedTypeName[] expectedDecodeNames)
{
var pooledStrBuilder = PooledStringBuilder.GetInstance();
StringBuilder typeNameBuilder = pooledStrBuilder.Builder;
var typeNamesToDecode = new string[typeNameConfigs.Length];
expectedDecodeNames = new MetadataHelpers.AssemblyQualifiedTypeName[typeNameConfigs.Length];
for (int index = 0; index < typeNameConfigs.Length; index++)
{
TypeNameConfig typeNameConfig = typeNameConfigs[index];
string expectedTopLevelTypeName = "X";
typeNameBuilder.Append('X');
string[] expectedNestedTypes = null;
if (typeNameConfig.NestingLevel > 0)
{
expectedNestedTypes = new string[typeNameConfig.NestingLevel];
for (int i = 0; i < typeNameConfig.NestingLevel; i++)
{
expectedNestedTypes[i] = "Y" + i;
typeNameBuilder.Append("+" + expectedNestedTypes[i]);
}
}
MetadataHelpers.AssemblyQualifiedTypeName[] expectedTypeArguments;
if (typeNameConfig.GenericParamsConfig == null)
{
expectedTypeArguments = null;
}
else
{
string[] genericParamsToDecode = GenerateTypeNamesToDecode(typeNameConfig.GenericParamsConfig, out expectedTypeArguments);
var genericArityStr = "`" + genericParamsToDecode.Length.ToString();
typeNameBuilder.Append(genericArityStr);
if (typeNameConfig.NestingLevel == 0)
{
expectedTopLevelTypeName += genericArityStr;
}
else
{
expectedNestedTypes[typeNameConfig.NestingLevel - 1] += genericArityStr;
}
typeNameBuilder.Append('[');
for (int i = 0; i < genericParamsToDecode.Length; i++)
{
if (i > 0)
{
typeNameBuilder.Append(", ");
}
if (typeNameConfig.GenericParamsConfig[i].AssemblyQualified)
{
typeNameBuilder.Append('[');
typeNameBuilder.Append(genericParamsToDecode[i]);
typeNameBuilder.Append(']');
}
else
{
typeNameBuilder.Append(genericParamsToDecode[i]);
}
}
typeNameBuilder.Append(']');
}
int expectedPointerCount = typeNameConfig.PointerCount;
typeNameBuilder.Append('*', expectedPointerCount);
int[] expectedArrayRanks = null;
switch (typeNameConfig.ArrayKind)
{
case ArrayKind.SzArray:
typeNameBuilder.Append("[]");
expectedArrayRanks = new[] { 0 };
break;
case ArrayKind.SingleDimensional:
typeNameBuilder.Append("[*]");
expectedArrayRanks = new[] { 1 };
break;
case ArrayKind.MultiDimensional:
typeNameBuilder.Append("[,]");
expectedArrayRanks = new[] { 2 };
break;
case ArrayKind.JaggedSzArray:
typeNameBuilder.Append("[,][]");
expectedArrayRanks = new[] { 2, 0 };
break;
case ArrayKind.Jagged:
typeNameBuilder.Append("[,][*]");
expectedArrayRanks = new[] { 2, 1 };
break;
}
string expectedAssemblyName;
if (typeNameConfig.AssemblyQualified)
{
expectedAssemblyName = "Assembly, Version=0.0.0.0, Culture=neutral, null";
typeNameBuilder.Append(", " + expectedAssemblyName);
}
else
{
expectedAssemblyName = null;
}
typeNamesToDecode[index] = typeNameBuilder.ToString();
expectedDecodeNames[index] = new MetadataHelpers.AssemblyQualifiedTypeName(expectedTopLevelTypeName, expectedNestedTypes, expectedTypeArguments, expectedPointerCount, expectedArrayRanks, expectedAssemblyName);
typeNameBuilder.Clear();
}
pooledStrBuilder.Free();
return typeNamesToDecode;
}
private static void VerifyDecodedTypeName(
MetadataHelpers.AssemblyQualifiedTypeName decodedName,
string expectedTopLevelType,
string expectedAssemblyName,
string[] expectedNestedTypes,
MetadataHelpers.AssemblyQualifiedTypeName[] expectedTypeArguments,
int expectedPointerCount,
int[] expectedArrayRanks)
{
Assert.Equal(expectedTopLevelType, decodedName.TopLevelType);
Assert.Equal(expectedAssemblyName, decodedName.AssemblyName);
Assert.Equal(expectedNestedTypes, decodedName.NestedTypes);
if (decodedName.TypeArguments == null)
{
Assert.Null(expectedTypeArguments);
}
else
{
var decodedTypeArguments = decodedName.TypeArguments;
for (int i = 0; i < decodedTypeArguments.Length; i++)
{
var expectedTypeArgument = expectedTypeArguments[i];
VerifyDecodedTypeName(decodedTypeArguments[i], expectedTypeArgument.TopLevelType, expectedTypeArgument.AssemblyName,
expectedTypeArgument.NestedTypes, expectedTypeArgument.TypeArguments, expectedTypeArgument.PointerCount, expectedTypeArgument.ArrayRanks);
}
}
Assert.Equal(expectedPointerCount, decodedName.PointerCount);
AssertEx.Equal(expectedArrayRanks, decodedName.ArrayRanks);
}
private static void DecodeTypeNameAndVerify(
string nameToDecode,
string expectedTopLevelType,
string expectedAssemblyName = null,
string[] expectedNestedTypes = null,
MetadataHelpers.AssemblyQualifiedTypeName[] expectedTypeArguments = null,
int expectedPointerCount = 0,
int[] expectedArrayRanks = null)
{
MetadataHelpers.AssemblyQualifiedTypeName decodedName = MetadataHelpers.DecodeTypeName(nameToDecode);
VerifyDecodedTypeName(decodedName, expectedTopLevelType, expectedAssemblyName, expectedNestedTypes, expectedTypeArguments, expectedPointerCount, expectedArrayRanks);
}
private static void DecodeTypeNamesAndVerify(string[] namesToDecode, MetadataHelpers.AssemblyQualifiedTypeName[] expectedDecodedNames)
{
Assert.Equal(namesToDecode.Length, expectedDecodedNames.Length);
for (int i = 0; i < namesToDecode.Length; i++)
{
var expectedDecodedName = expectedDecodedNames[i];
DecodeTypeNameAndVerify(namesToDecode[i], expectedDecodedName.TopLevelType, expectedDecodedName.AssemblyName,
expectedDecodedName.NestedTypes, expectedDecodedName.TypeArguments, expectedDecodedName.PointerCount, expectedDecodedName.ArrayRanks);
}
}
[WorkItem(546277, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546277")]
[Fact]
public void TestDecodeTypeNameMatrix()
{
TypeNameConfig[] configsToTest = GenerateTypeNameConfigs(0);
MetadataHelpers.AssemblyQualifiedTypeName[] expectedDecodedNames;
string[] namesToDecode = GenerateTypeNamesToDecode(configsToTest, out expectedDecodedNames);
DecodeTypeNamesAndVerify(namesToDecode, expectedDecodedNames);
}
[WorkItem(546277, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546277")]
[Fact]
public void TestDecodeArrayTypeName_Bug15478()
{
DecodeTypeNameAndVerify("System.Int32[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
expectedTopLevelType: "System.Int32",
expectedAssemblyName: "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
expectedArrayRanks: new[] { 0 });
DecodeTypeNameAndVerify("System.Int32[*], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
expectedTopLevelType: "System.Int32",
expectedAssemblyName: "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
expectedArrayRanks: new[] { 1 });
}
[WorkItem(546277, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546277")]
[Fact]
public void TestDecodeArrayTypeName_Valid()
{
// Single-D Array
DecodeTypeNameAndVerify("W[]",
expectedTopLevelType: "W",
expectedArrayRanks: new[] { 0 });
// Multi-D Array
DecodeTypeNameAndVerify("W[,]",
expectedTopLevelType: "W",
expectedArrayRanks: new[] { 2 });
// Jagged Array
DecodeTypeNameAndVerify("W[][,]",
expectedTopLevelType: "W",
expectedArrayRanks: new[] { 0, 2 });
// Generic Type Jagged Array
DecodeTypeNameAndVerify("Y`1[W][][,]",
expectedTopLevelType: "Y`1",
expectedTypeArguments: new[] { new MetadataHelpers.AssemblyQualifiedTypeName("W", null, null, 0, null, null) },
expectedArrayRanks: new[] { 0, 2 });
// Nested Generic Type Jagged Array with Array type argument
DecodeTypeNameAndVerify("Y`1+F[[System.Int32[], mscorlib]][,,][][,]",
expectedTopLevelType: "Y`1",
expectedNestedTypes: new[] { "F" },
expectedTypeArguments: new[] { new MetadataHelpers.AssemblyQualifiedTypeName(
"System.Int32",
nestedTypes: null,
typeArguments: null,
pointerCount: 0,
arrayRanks: new[] { 0 },
assemblyName: "mscorlib") },
expectedArrayRanks: new[] { 3, 0, 2 });
// Nested Generic Type Jagged Array with type arguments from nested type and outer type
DecodeTypeNameAndVerify("Y`1+Z`1[[System.Int32[], mscorlib], W][][,]",
expectedTopLevelType: "Y`1",
expectedNestedTypes: new[] { "Z`1" },
expectedTypeArguments: new[] { new MetadataHelpers.AssemblyQualifiedTypeName(
"System.Int32",
nestedTypes: null,
typeArguments: null,
pointerCount: 0,
arrayRanks: new[] { 0 },
assemblyName: "mscorlib"),
new MetadataHelpers.AssemblyQualifiedTypeName("W", null, null, 0, null, null) },
expectedArrayRanks: new[] { 0, 2 });
}
[WorkItem(546277, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546277")]
[Fact]
public void TestDecodeArrayTypeName_Invalid()
{
// Error case, array shape before nested type
DecodeTypeNameAndVerify("X[]+Y",
expectedTopLevelType: "X+Y",
expectedNestedTypes: null,
expectedArrayRanks: new[] { 0 });
// Error case, array shape before generic type arguments
DecodeTypeNameAndVerify("X[]`1[T]",
expectedTopLevelType: "X`1[T]",
expectedTypeArguments: null,
expectedArrayRanks: new[] { 0 });
// Error case, invalid array shape
DecodeTypeNameAndVerify("X[T]",
expectedTopLevelType: "X[T]",
expectedTypeArguments: null,
expectedArrayRanks: null);
DecodeTypeNameAndVerify("X[,",
expectedTopLevelType: "X[,",
expectedTypeArguments: null,
expectedArrayRanks: null);
// Incomplete type argument assembly name
DecodeTypeNameAndVerify("X`1[[T, Assembly",
expectedTopLevelType: "X`1",
expectedAssemblyName: null,
expectedTypeArguments: new[] { new MetadataHelpers.AssemblyQualifiedTypeName("T", null, null, 0, null, "Assembly") },
expectedArrayRanks: null);
}
[WorkItem(1140387, "DevDiv")]
[Fact]
public void TestDecodePointerType_Invalid()
{
// Error case, star before nested type
DecodeTypeNameAndVerify("X*+Y",
expectedTopLevelType: "X+Y",
expectedNestedTypes: null,
expectedPointerCount: 1);
// Error case, star before generic type arguments
DecodeTypeNameAndVerify("X*`1[T]",
expectedTopLevelType: "X`1[T]",
expectedTypeArguments: null,
expectedPointerCount: 1);
// Unsupported case, star after array shape
// This is supported in the AQN grammar, but not in C# or VB.
DecodeTypeNameAndVerify("W[]*",
expectedTopLevelType: "W*",
expectedTypeArguments: null,
expectedPointerCount: 0,
expectedArrayRanks: new[] { 0 });
}
[Fact, WorkItem(7396, "https://github.com/dotnet/roslyn/issues/7396")]
public void ObfuscatedNamespaceNames_01()
{
var result = new ArrayBuilder<IGrouping<string, TypeDefinitionHandle>>();
foreach (var namespaceName in new[] { "A.", "A.a", "A..", "A.-" })
{
result.Add(new Grouping<string, TypeDefinitionHandle>(namespaceName, new[] { new TypeDefinitionHandle() }));
}
result.Sort(new PEModule.TypesByNamespaceSortComparer(StringComparer.Ordinal));
// This is equivalent to the result of PEModule.GroupTypesByNamespaceOrThrow
IEnumerable<IGrouping<string, TypeDefinitionHandle>> typesByNS = result;
// The following code is equivalent to code in PENamespaceSymbol.LoadAllMembers
IEnumerable<IGrouping<string, TypeDefinitionHandle>> nestedTypes = null;
IEnumerable<KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>> nestedNamespaces = null;
MetadataHelpers.GetInfoForImmediateNamespaceMembers(
false,
"A".Length,
typesByNS,
StringComparer.Ordinal,
out nestedTypes, out nestedNamespaces);
// We don't expect duplicate keys in nestedNamespaces at this point.
Assert.False(nestedNamespaces.GroupBy(pair => pair.Key).Where(g => g.Count() > 1).Any());
var array = nestedNamespaces.ToArray();
Assert.Equal(3, array.Length);
Assert.Equal("", array[0].Key);
Assert.Equal(2, array[0].Value.Count());
Assert.Equal("-", array[1].Key);
Assert.Equal(1, array[1].Value.Count());
Assert.Equal("a", array[2].Key);
Assert.Equal(1, array[2].Value.Count());
}
[Fact, WorkItem(7396, "https://github.com/dotnet/roslyn/issues/7396")]
public void ObfuscatedNamespaceNames_02()
{
var result = new ArrayBuilder<IGrouping<string, TypeDefinitionHandle>>();
foreach (var namespaceName in new[] { ".a", ".b" })
{
result.Add(new Grouping<string, TypeDefinitionHandle>(namespaceName, new[] { new TypeDefinitionHandle() }));
}
result.Sort(new PEModule.TypesByNamespaceSortComparer(StringComparer.Ordinal));
// This is equivalent to the result of PEModule.GroupTypesByNamespaceOrThrow
IEnumerable<IGrouping<string, TypeDefinitionHandle>> typesByNS = result;
// The following code is equivalent to code in PENamespaceSymbol.LoadAllMembers
IEnumerable<IGrouping<string, TypeDefinitionHandle>> nestedTypes = null;
IEnumerable<KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>> nestedNamespaces = null;
MetadataHelpers.GetInfoForImmediateNamespaceMembers(
true, // global namespace
0, // global namespace
typesByNS,
StringComparer.Ordinal,
out nestedTypes, out nestedNamespaces);
var nestedNS = nestedNamespaces.Single();
Assert.Equal("", nestedNS.Key);
Assert.Equal(2, nestedNS.Value.Count());
MetadataHelpers.GetInfoForImmediateNamespaceMembers(
false,
nestedNS.Key.Length,
nestedNS.Value,
StringComparer.Ordinal,
out nestedTypes, out nestedNamespaces);
Assert.Equal(2, nestedNamespaces.Count());
Assert.Equal("a", nestedNamespaces.ElementAt(0).Key);
Assert.Equal("b", nestedNamespaces.ElementAt(1).Key);
}
}
}
|