From b5b40d1228ba0cf3b16ccba28ec5482600054d30 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Mon, 17 Apr 2023 17:54:06 -0700 Subject: [PATCH 1/4] Fix Type.GetType for global type names with leading '.' Ignore leading '.' for global typenames for compatibility with earlier .NET versions. Fixes #84644 --- .../src/System/Reflection/TypeNameParser.cs | 17 +++++++++++++++++ .../tests/System/Type/TypeTests.cs | 14 ++++++++++++++ .../tests/TestStructs/System.TestStructs.il | 10 ++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/libraries/Common/src/System/Reflection/TypeNameParser.cs b/src/libraries/Common/src/System/Reflection/TypeNameParser.cs index 93a9f30468c42e..0b69e4e18aaae4 100644 --- a/src/libraries/Common/src/System/Reflection/TypeNameParser.cs +++ b/src/libraries/Common/src/System/Reflection/TypeNameParser.cs @@ -167,6 +167,8 @@ private TypeNameParser(ReadOnlySpan name) if (fullName is null) return null; + fullName = ApplyLeadingDotCompatQuirk(fullName); + if (Peek == TokenType.Plus) { string[] nestedNames = new string[1]; @@ -180,6 +182,8 @@ private TypeNameParser(ReadOnlySpan name) if (nestedName is null) return null; + nestedName = ApplyLeadingDotCompatQuirk(nestedName); + if (nestedNamesCount >= nestedNames.Length) Array.Resize(ref nestedNames, 2 * nestedNamesCount); nestedNames[nestedNamesCount++] = nestedName; @@ -192,6 +196,19 @@ private TypeNameParser(ReadOnlySpan name) { return new NamespaceTypeName(fullName); } + + // Compat: Ignore leading '.' for type names without namespace. .NET Framework historically ignored leading '.' here. It is likely + // that code out there depends on this behavior. For example, type names formed by concatenating namespace and name, without checking for + // empty namespace (bug), are going to have superfluous leading '.'. + // This behavior means that types that start with '.' are not round-trippable via type name. + static string ApplyLeadingDotCompatQuirk(string typeName) + { +#if NETCOREAPP + return (typeName.StartsWith('.') && !typeName.AsSpan(1).Contains('.')) ? typeName.Substring(1) : typeName; +#else + return ((typeName.Length > 0) && (typeName[0] == '.') && typeName.LastIndexOf('.') == 0) ? typeName.Substring(1) : typeName; +#endif + } } // diff --git a/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs b/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs index 7a92fc1be38b18..0f37e125e4e669 100644 --- a/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs @@ -507,6 +507,8 @@ public void MakePointerType_ByRef_ThrowsTypeLoadException() [InlineData("Outside[,,]", typeof(Outside[,,]))] [InlineData("Outside[][]", typeof(Outside[][]))] [InlineData("Outside`1[System.Nullable`1[System.Boolean]]", typeof(Outside))] + [InlineData(".Outside`1", typeof(Outside<>))] + [InlineData(".Outside`1+.Inside`1", typeof(Outside<>.Inside<>))] public void GetTypeByName_ValidType_ReturnsExpected(string typeName, Type expectedType) { Assert.Equal(expectedType, Type.GetType(typeName, throwOnError: false, ignoreCase: false)); @@ -520,6 +522,8 @@ public void GetTypeByName_ValidType_ReturnsExpected(string typeName, Type expect [InlineData("System.Int32[,*,]", typeof(ArgumentException), false)] [InlineData("Outside`2", typeof(TypeLoadException), false)] [InlineData("Outside`1[System.Boolean, System.Int32]", typeof(ArgumentException), true)] + [InlineData(".System.Int32", typeof(TypeLoadException), false)] + [InlineData("..Outside`1", typeof(TypeLoadException), false)] public void GetTypeByName_Invalid(string typeName, Type expectedException, bool alwaysThrowsException) { if (!alwaysThrowsException) @@ -530,6 +534,16 @@ public void GetTypeByName_Invalid(string typeName, Type expectedException, bool Assert.Throws(expectedException, () => Type.GetType(typeName, throwOnError: true, ignoreCase: false)); } + [Theory] + [InlineData(".GlobalStructStartingWithDot")] + [InlineData(" GlobalStructStartingWithSpace")] + public void GetTypeByName_NonRountripable(string typeName) + { + Type type = Assembly.Load("System.TestStructs").GetTypes().Single((t) => t.FullName == typeName); + string assemblyQualifiedName = type.AssemblyQualifiedName; + Assert.Null(Type.GetType(assemblyQualifiedName)); + } + [Fact] [ActiveIssue("https://github.com/dotnet/runtimelab/issues/155", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] [ActiveIssue("https://github.com/dotnet/runtime/issues/52393", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] diff --git a/src/libraries/System.Runtime/tests/TestStructs/System.TestStructs.il b/src/libraries/System.Runtime/tests/TestStructs/System.TestStructs.il index dabc5ac4a1b452..a7573c0116d651 100644 --- a/src/libraries/System.Runtime/tests/TestStructs/System.TestStructs.il +++ b/src/libraries/System.Runtime/tests/TestStructs/System.TestStructs.il @@ -70,3 +70,13 @@ } } } + +.class public sequential sealed '.GlobalStructStartingWithDot' + extends [System.Runtime]System.ValueType +{ +} + +.class public sequential sealed ' GlobalStructStartingWithSpace' + extends [System.Runtime]System.ValueType +{ +} From 7a407ad8f0a0479349f638f23240b4aaba9ae62e Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Mon, 17 Apr 2023 19:11:34 -0700 Subject: [PATCH 2/4] Update src/libraries/System.Runtime/tests/System/Type/TypeTests.cs --- src/libraries/System.Runtime/tests/System/Type/TypeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs b/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs index 0f37e125e4e669..77c8344c48384e 100644 --- a/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs @@ -537,7 +537,7 @@ public void GetTypeByName_Invalid(string typeName, Type expectedException, bool [Theory] [InlineData(".GlobalStructStartingWithDot")] [InlineData(" GlobalStructStartingWithSpace")] - public void GetTypeByName_NonRountripable(string typeName) + public void GetTypeByName_NonRoundtrippable(string typeName) { Type type = Assembly.Load("System.TestStructs").GetTypes().Single((t) => t.FullName == typeName); string assemblyQualifiedName = type.AssemblyQualifiedName; From 822086a1a0ef9dea1874b69ad9fa91b266c5c366 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Mon, 17 Apr 2023 20:38:47 -0700 Subject: [PATCH 3/4] Update src/libraries/System.Runtime/tests/System/Type/TypeTests.cs --- src/libraries/System.Runtime/tests/System/Type/TypeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs b/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs index 77c8344c48384e..6f4a61c9b596b1 100644 --- a/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs @@ -534,7 +534,7 @@ public void GetTypeByName_Invalid(string typeName, Type expectedException, bool Assert.Throws(expectedException, () => Type.GetType(typeName, throwOnError: true, ignoreCase: false)); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBuiltWithAggressiveTrimming))] [InlineData(".GlobalStructStartingWithDot")] [InlineData(" GlobalStructStartingWithSpace")] public void GetTypeByName_NonRoundtrippable(string typeName) From e22d00dca0163c9a55e01f2f1373bef525366255 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Mon, 17 Apr 2023 20:39:20 -0700 Subject: [PATCH 4/4] Update src/libraries/System.Runtime/tests/System/Type/TypeTests.cs --- src/libraries/System.Runtime/tests/System/Type/TypeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs b/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs index 6f4a61c9b596b1..3db956b0715e97 100644 --- a/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Type/TypeTests.cs @@ -534,7 +534,7 @@ public void GetTypeByName_Invalid(string typeName, Type expectedException, bool Assert.Throws(expectedException, () => Type.GetType(typeName, throwOnError: true, ignoreCase: false)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBuiltWithAggressiveTrimming))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBuiltWithAggressiveTrimming))] [InlineData(".GlobalStructStartingWithDot")] [InlineData(" GlobalStructStartingWithSpace")] public void GetTypeByName_NonRoundtrippable(string typeName)