Skip to content

Commit

Permalink
Add missing type name parsing test coverage (#98562)
Browse files Browse the repository at this point in the history
* backward compat: ignore leading dot for types without namespace

* type names that require escaping

* only selected characters should be escaped

* white spaces other than spaces

---------

Co-authored-by: Jan Kotas <jkotas@microsoft.com>
  • Loading branch information
adamsitnik and jkotas committed Feb 22, 2024
1 parent bf4e90f commit 8ec001d
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 0 deletions.
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

0 comments on commit 8ec001d

Please sign in to comment.