Skip to content

Add missing type name parsing test coverage #98562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System.Collections.Generic;
using System.IO;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using Xunit;

namespace System.Reflection.Tests
Expand Down Expand Up @@ -297,6 +299,142 @@ public void TestTypeIdentifierAttribute()
args = new object[1] { Activator.CreateInstance(otherEquivalentValueType) };
Assert.Equal(42, mi.Invoke(null, args));
}

[Fact]
public void IgnoreLeadingDotForTypeNamesWithoutNamespace()
{
Type typeWithNoNamespace = typeof(NoNamespace);

Assert.Equal(typeWithNoNamespace, Type.GetType($".{typeWithNoNamespace.AssemblyQualifiedName}"));
Assert.Equal(typeWithNoNamespace, Type.GetType(typeWithNoNamespace.AssemblyQualifiedName));

Assert.Equal(typeWithNoNamespace, typeWithNoNamespace.Assembly.GetType($".{typeWithNoNamespace.FullName}"));
Assert.Equal(typeWithNoNamespace, typeWithNoNamespace.Assembly.GetType(typeWithNoNamespace.FullName));

Assert.Equal(typeof(List<NoNamespace>), Type.GetType($"{typeof(List<>).FullName}[[{typeWithNoNamespace.AssemblyQualifiedName}]]"));
Assert.Equal(typeof(List<NoNamespace>), Type.GetType($"{typeof(List<>).FullName}[[.{typeWithNoNamespace.AssemblyQualifiedName}]]"));

Type typeWithNamespace = typeof(int);

Assert.Equal(typeWithNamespace, Type.GetType(typeWithNamespace.AssemblyQualifiedName));
Assert.Null(Type.GetType($".{typeWithNamespace.AssemblyQualifiedName}"));
}

public static IEnumerable<object[]> GetTypesThatRequireEscaping()
{
if (RuntimeFeature.IsDynamicCodeSupported)
{
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("TypeNamesThatRequireEscaping"), AssemblyBuilderAccess.Run);
ModuleBuilder module = assembly.DefineDynamicModule("TypeNamesThatRequireEscapingModule");

yield return new object[] { module.DefineType("TypeNameWith+ThatIsNotNestedType").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith\\TheEscapingCharacter").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith&Ampersand").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith*Asterisk").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith[OpeningSquareBracket").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith]ClosingSquareBracket").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith[]BothSquareBrackets").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith[[]]NestedSquareBrackets").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith,Comma").CreateType(), assembly };
yield return new object[] { module.DefineType("TypeNameWith\\[]+*&,AllSpecialCharacters").CreateType(), assembly };

TypeBuilder containingType = module.DefineType("ContainingTypeWithA+Plus");
_ = containingType.CreateType(); // containing type must exist!
yield return new object[] { containingType.DefineNestedType("NoSpecialCharacters").CreateType(), assembly };
yield return new object[] { containingType.DefineNestedType("Contains+Plus").CreateType(), assembly };
}
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45033", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))]
[MemberData(nameof(GetTypesThatRequireEscaping))]
public void TypeNamesThatRequireEscaping(Type type, Assembly assembly)
{
Assert.Contains('\\', type.FullName);

Assert.Equal(type, assembly.GetType(type.FullName));
Assert.Equal(type, assembly.GetType(type.FullName.ToLower(), throwOnError: true, ignoreCase: true));
Assert.Equal(type, assembly.GetType(type.FullName.ToUpper(), throwOnError: true, ignoreCase: true));
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45033", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))]
public void EscapingCharacterThatDoesNotRequireEscapingIsTreatedAsError()
{
for (char character = (char)0; character <= 255; character++)
{
Func<Type> testCode = () => Type.GetType($"System.\\{character}", throwOnError: true);

if (character is '\\' or '[' or ']' or '+' or '*' or '&' or ',')
{
Assert.Throws<TypeLoadException>(testCode); // such type does not exist
}
else
{
Assert.Throws<ArgumentException>(testCode); // such name is invalid
}

Assert.Null(Type.GetType($"System.\\{character}", throwOnError: false));
}
}

public static IEnumerable<object[]> AllWhitespacesArguments()
{
// leading whitespaces are allowed for type names:
yield return new object[]
{
" \t\r\nSystem.Int32",
typeof(int)
};
yield return new object[]
{
$"System.Collections.Generic.List`1[\r\n\t [\t\r\n {typeof(int).AssemblyQualifiedName}]], {typeof(List<>).Assembly.FullName}",
typeof(List<int>)
};
yield return new object[]
{
$"System.Collections.Generic.List`1[\r\n\t{typeof(int).FullName}]",
typeof(List<int>)
};
// leading whitespaces are NOT allowed for modifiers:
yield return new object[]
{
"System.Int32\t\r\n []",
null
};
yield return new object[]
{
"System.Int32\r\n\t [,]",
null
};
yield return new object[]
{
"System.Int32 \r\n\t [*]",
null
};
yield return new object[]
{
"System.Int32 *",
null
};
yield return new object[]
{
"System.Int32\t&",
null
};
// trailing whitespaces are NOT allowed:
yield return new object[]
{
$"System.Int32 \t\r\n",
null
};
}

[Theory]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45033", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))]
[MemberData(nameof(AllWhitespacesArguments))]
public void AllWhitespaces(string input, Type? expectedType)
=> Assert.Equal(expectedType, Type.GetType(input));
}

namespace MyNamespace1
Expand Down Expand Up @@ -352,3 +490,8 @@ public class MyClass1 { }

public class GenericClass<T> { }
}

public class NoNamespace
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,26 @@ public void GetTypeByName_ValidType_ReturnsExpected(string typeName, Type expect
Assert.Equal(expectedType, Type.GetType(typeName.ToLower(), throwOnError: false, ignoreCase: true));
}

public static IEnumerable<object[]> GetTypeByName_InvalidElementType()
{
Type expectedException = PlatformDetection.IsMonoRuntime
? typeof(ArgumentException) // https://github.com/dotnet/runtime/issues/45033
: typeof(TypeLoadException);

yield return new object[] { "System.Int32&&", expectedException, true };
yield return new object[] { "System.Int32&*", expectedException, true };
yield return new object[] { "System.Int32&[]", expectedException, true };
yield return new object[] { "System.Int32&[*]", expectedException, true };
yield return new object[] { "System.Int32&[,]", expectedException, true };

// https://github.com/dotnet/runtime/issues/45033
if (!PlatformDetection.IsMonoRuntime)
{
yield return new object[] { "System.Void[]", expectedException, true };
yield return new object[] { "System.TypedReference[]", expectedException, true };
}
}

[Theory]
[InlineData("system.nullable`1[system.int32]", typeof(TypeLoadException), false)]
[InlineData("System.NonExistingType", typeof(TypeLoadException), false)]
Expand All @@ -522,6 +542,7 @@ public void GetTypeByName_ValidType_ReturnsExpected(string typeName, Type expect
[InlineData("Outside`1[System.Boolean, System.Int32]", typeof(ArgumentException), true)]
[InlineData(".System.Int32", typeof(TypeLoadException), false)]
[InlineData("..Outside`1", typeof(TypeLoadException), false)]
[MemberData(nameof(GetTypeByName_InvalidElementType))]
public void GetTypeByName_Invalid(string typeName, Type expectedException, bool alwaysThrowsException)
{
if (!alwaysThrowsException)
Expand Down