diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/ReflectionHelperTests.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/ReflectionHelperTests.cs index 66b24d03e..fa5ec206b 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/ReflectionHelperTests.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/ReflectionHelperTests.cs @@ -266,6 +266,84 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.Throws(() => ReflectionHelper.ParseReflectionName("System.Action`1[[System.Int32]a]", context)); } + [Test] + public void SplitTypeParameterCountFromName() + { + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("List`1", out int tpc), Is.EqualTo("List")); + Assert.That(tpc, Is.EqualTo(1)); + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("Dictionary`2", out tpc), Is.EqualTo("Dictionary")); + Assert.That(tpc, Is.EqualTo(2)); + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("Foo`12", out tpc), Is.EqualTo("Foo")); + Assert.That(tpc, Is.EqualTo(12)); + } + + [Test] + public void SplitTypeParameterCountWithoutBacktick() + { + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("String", out int tpc), Is.EqualTo("String")); + Assert.That(tpc, Is.EqualTo(0)); + } + + [Test] + public void SplitTypeParameterCountUsesTheLastBacktick() + { + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("Outer`1+Inner`2", out int tpc), Is.EqualTo("Outer`1+Inner")); + Assert.That(tpc, Is.EqualTo(2)); + } + + [Test] + public void SplitTypeParameterCountKeepsNameWhenSuffixIsNotAPlainNumber() + { + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("Foo`", out int tpc), Is.EqualTo("Foo`")); + Assert.That(tpc, Is.EqualTo(0)); + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("Foo`x", out tpc), Is.EqualTo("Foo`x")); + Assert.That(tpc, Is.EqualTo(0)); + // Only plain digits form an arity: a signed suffix is not a legal reflection name. + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("Foo`+1", out tpc), Is.EqualTo("Foo`+1")); + Assert.That(tpc, Is.EqualTo(0)); + // An arity beyond int.MaxValue is rejected, not truncated. + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("Foo`2147483648", out tpc), Is.EqualTo("Foo`2147483648")); + Assert.That(tpc, Is.EqualTo(0)); + Assert.That(ReflectionHelper.SplitTypeParameterCountFromReflectionName("Foo`99999999999999999999", out tpc), Is.EqualTo("Foo`99999999999999999999")); + Assert.That(tpc, Is.EqualTo(0)); + } + + [Test] + public void TopLevelTypeNameParsesNamespaceNameAndArity() + { + var t = new TopLevelTypeName("System.Collections.Generic.List`1"); + Assert.That(t.Namespace, Is.EqualTo("System.Collections.Generic")); + Assert.That(t.Name, Is.EqualTo("List")); + Assert.That(t.TypeParameterCount, Is.EqualTo(1)); + } + + [Test] + public void TopLevelTypeNameWithoutNamespace() + { + var t = new TopLevelTypeName("List`1"); + Assert.That(t.Namespace, Is.EqualTo(string.Empty)); + Assert.That(t.Name, Is.EqualTo("List")); + Assert.That(t.TypeParameterCount, Is.EqualTo(1)); + } + + [Test] + public void TopLevelTypeNameWithoutArity() + { + var t = new TopLevelTypeName("System.String"); + Assert.That(t.Namespace, Is.EqualTo("System")); + Assert.That(t.Name, Is.EqualTo("String")); + Assert.That(t.TypeParameterCount, Is.EqualTo(0)); + } + + [Test] + public void TopLevelTypeNameIgnoresBacktickInsideTheNamespace() + { + var t = new TopLevelTypeName("A`1.B"); + Assert.That(t.Namespace, Is.EqualTo("A`1")); + Assert.That(t.Name, Is.EqualTo("B")); + Assert.That(t.TypeParameterCount, Is.EqualTo(0)); + } + [Test] public void ParseInvalidReflectionName13() { diff --git a/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs b/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs index edc2dfc30..7530bb5cd 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs @@ -83,19 +83,38 @@ namespace ICSharpCode.Decompiler.TypeSystem public static string SplitTypeParameterCountFromReflectionName(string reflectionName, out int typeParameterCount) { int pos = reflectionName.LastIndexOf('`'); - if (pos < 0) + if (pos >= 0 && TryParseTypeParameterCount(reflectionName, pos + 1, out typeParameterCount)) { - typeParameterCount = 0; - return reflectionName; + return reflectionName.Substring(0, pos); } - else + typeParameterCount = 0; + return reflectionName; + } + + /// + /// Parses a type parameter count that starts at and extends to + /// the end of . Only plain ASCII digits are accepted + /// (no sign or whitespace), because that is all a legal reflection name can contain. + /// netstandard2.0 has no span-based int.TryParse, so the digits are accumulated manually + /// to avoid allocating a throwaway substring. + /// + internal static bool TryParseTypeParameterCount(string reflectionName, int start, out int typeParameterCount) + { + typeParameterCount = 0; + if (start >= reflectionName.Length) + return false; + long value = 0; + for (int i = start; i < reflectionName.Length; i++) { - string typeCount = reflectionName.Substring(pos + 1); - if (int.TryParse(typeCount, out typeParameterCount)) - return reflectionName.Substring(0, pos); - else - return reflectionName; + char c = reflectionName[i]; + if (c < '0' || c > '9') + return false; + value = value * 10 + (c - '0'); + if (value > int.MaxValue) + return false; } + typeParameterCount = (int)value; + return true; } #endregion diff --git a/ICSharpCode.Decompiler/TypeSystem/TopLevelTypeName.cs b/ICSharpCode.Decompiler/TypeSystem/TopLevelTypeName.cs index e975d62ac..c6f77f163 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TopLevelTypeName.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TopLevelTypeName.cs @@ -46,18 +46,20 @@ namespace ICSharpCode.Decompiler.TypeSystem public TopLevelTypeName(string reflectionName) { + // Locate both separators up front so that namespaceName and name are each cut + // exactly once, without an intermediate string still carrying the arity suffix. int pos = reflectionName.LastIndexOf('.'); - if (pos < 0) + int tick = reflectionName.LastIndexOf('`'); + if (tick > pos && ReflectionHelper.TryParseTypeParameterCount(reflectionName, tick + 1, out typeParameterCount)) { - namespaceName = string.Empty; - name = reflectionName; + name = reflectionName.Substring(pos + 1, tick - pos - 1); } else { - namespaceName = reflectionName.Substring(0, pos); + typeParameterCount = 0; name = reflectionName.Substring(pos + 1); } - name = ReflectionHelper.SplitTypeParameterCountFromReflectionName(name, out typeParameterCount); + namespaceName = pos < 0 ? string.Empty : reflectionName.Substring(0, pos); } public string Namespace {