From ec6a9afc57d89985c50a20235b118da95eaf1df2 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 26 Nov 2021 02:38:42 +0100 Subject: [PATCH] Fix #2448: Decompiler shows some enum values as hexdecimal instead of decimal --- .../Pretty/CompoundAssignmentTest.cs | 8 ++-- .../Pretty/CustomAttributeSamples.cs | 2 +- .../TestCases/Pretty/CustomAttributes.cs | 10 ++--- .../TestCases/Pretty/CustomAttributes2.cs | 10 ++--- .../TestCases/Pretty/EnumTests.cs | 10 ++--- .../CSharp/CSharpDecompiler.cs | 45 +++++++++++++------ ICSharpCode.Decompiler/DecompileRun.cs | 14 +++++- 7 files changed, 63 insertions(+), 36 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index 7f0421a4a..66da6e06c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -26,10 +26,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty [Flags] private enum MyEnum { - None = 0x0, - One = 0x1, - Two = 0x2, - Four = 0x4 + None = 0, + One = 1, + Two = 2, + Four = 4 } public enum ShortEnum : short diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributeSamples.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributeSamples.cs index efd0c969b..e78d0eeb6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributeSamples.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributeSamples.cs @@ -37,7 +37,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.CustomAttributeSamples [Flags] public enum EnumWithFlagsAttribute { - None = 0x0 + None = 0 } [AttributeUsage(AttributeTargets.All)] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes.cs index 5fd807e9c..fd093f84a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes.cs @@ -27,11 +27,11 @@ namespace CustomAttributes public enum EnumWithFlag { All = 0xF, - None = 0x0, - Item1 = 0x1, - Item2 = 0x2, - Item3 = 0x4, - Item4 = 0x8 + None = 0, + Item1 = 1, + Item2 = 2, + Item3 = 4, + Item4 = 8 } [AttributeUsage(AttributeTargets.All)] public class MyAttribute : Attribute diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes2.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes2.cs index 568f528e6..78e271714 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes2.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes2.cs @@ -7,11 +7,11 @@ namespace CustomAttributes2 public enum EnumWithFlag { All = 0xF, - None = 0x0, - Item1 = 0x1, - Item2 = 0x2, - Item3 = 0x4, - Item4 = 0x8 + None = 0, + Item1 = 1, + Item2 = 2, + Item3 = 4, + Item4 = 8 } [AttributeUsage(AttributeTargets.All)] public class MyAttribute : Attribute diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs index 8fc6bb834..169d1c001 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs @@ -58,11 +58,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty [Flags] public enum SimpleFlagsEnum { - None = 0x0, - Item1 = 0x1, - Item2 = 0x2, - Item3 = 0x4, - All = 0x7 + None = 0, + Item1 = 1, + Item2 = 2, + Item3 = 4, + All = 7 } [Flags] diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 00af12fbe..6b2d840e3 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1264,6 +1264,11 @@ namespace ICSharpCode.Decompiler.CSharp typeDecl.Members.Add(nestedType); } } + + decompileRun.EnumValueDisplayMode = typeDef.Kind == TypeKind.Enum + ? DetectBestEnumValueDisplayMode(typeDef, module.PEFile) + : null; + // With C# 9 records, the relative order of fields and properties matters: IEnumerable fieldsAndProperties = recordDecompiler?.FieldsAndProperties ?? typeDef.Fields.Concat(typeDef.Properties); @@ -1331,7 +1336,7 @@ namespace ICSharpCode.Decompiler.CSharp } if (typeDecl.ClassType == ClassType.Enum) { - switch (DetectBestEnumValueDisplayMode(typeDef, module.PEFile)) + switch (decompileRun.EnumValueDisplayMode) { case EnumValueDisplayMode.FirstOnly: foreach (var enumMember in typeDecl.Members.OfType().Skip(1)) @@ -1350,11 +1355,13 @@ namespace ICSharpCode.Decompiler.CSharp } break; case EnumValueDisplayMode.All: + case EnumValueDisplayMode.AllHex: // nothing needs to be changed. break; default: throw new ArgumentOutOfRangeException(); } + decompileRun.EnumValueDisplayMode = null; } return typeDecl; } @@ -1369,21 +1376,14 @@ namespace ICSharpCode.Decompiler.CSharp } } - enum EnumValueDisplayMode - { - None, - All, - FirstOnly - } - EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, PEFile module) { - if (settings.AlwaysShowEnumMemberValues) - return EnumValueDisplayMode.All; if (typeDef.HasAttribute(KnownAttribute.Flags, inherit: false)) - return EnumValueDisplayMode.All; + return EnumValueDisplayMode.AllHex; bool first = true; long firstValue = 0, previousValue = 0; + bool allPowersOfTwo = true; + bool allConsecutive = true; foreach (var field in typeDef.Fields) { if (MemberIsHidden(module, field.MetadataToken, settings)) @@ -1392,17 +1392,35 @@ namespace ICSharpCode.Decompiler.CSharp if (constantValue == null) continue; long currentValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constantValue, false); + allConsecutive = allConsecutive && (first || previousValue + 1 == currentValue); + // N & (N - 1) == 0, iff N is a power of 2, for all N != 0. + // We define that 0 is a power of 2 in the context of enum values. + allPowersOfTwo = allPowersOfTwo && unchecked(currentValue & (currentValue - 1)) == 0; if (first) { firstValue = currentValue; first = false; } - else if (previousValue + 1 != currentValue) + else if (!allConsecutive && !allPowersOfTwo) { + // We already know that the values are neither consecutive nor all powers of 2, + // so we can abort, and just display all values as-is. return EnumValueDisplayMode.All; } previousValue = currentValue; } + if (allPowersOfTwo && previousValue > 2) + { + // If all values are powers of 2, display all enum values, but use hex. + return EnumValueDisplayMode.AllHex; + } + if (settings.AlwaysShowEnumMemberValues) + { + // The user always wants to see all enum values, but we know hex is not necessary. + return EnumValueDisplayMode.All; + } + // We know that all values are consecutive, so if the first value is not 0 + // display the first enum value only. return firstValue == 0 ? EnumValueDisplayMode.None : EnumValueDisplayMode.FirstOnly; } @@ -1712,8 +1730,7 @@ namespace ICSharpCode.Decompiler.CSharp long initValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constantValue, false); enumDec.Initializer = typeSystemAstBuilder.ConvertConstantValue(decompilationContext.CurrentTypeDefinition.EnumUnderlyingType, constantValue); if (enumDec.Initializer is PrimitiveExpression primitive - && initValue >= 0 && (decompilationContext.CurrentTypeDefinition.HasAttribute(KnownAttribute.Flags) - || (initValue > 9 && (unchecked(initValue & (initValue - 1)) == 0 || unchecked(initValue & (initValue + 1)) == 0)))) + && initValue >= 10 && decompileRun.EnumValueDisplayMode == EnumValueDisplayMode.AllHex) { primitive.Format = LiteralFormat.HexadecimalNumber; } diff --git a/ICSharpCode.Decompiler/DecompileRun.cs b/ICSharpCode.Decompiler/DecompileRun.cs index cb9b0f996..4d278ab7f 100644 --- a/ICSharpCode.Decompiler/DecompileRun.cs +++ b/ICSharpCode.Decompiler/DecompileRun.cs @@ -12,8 +12,8 @@ namespace ICSharpCode.Decompiler { internal class DecompileRun { - public HashSet DefinedSymbols { get; private set; } = new HashSet(); - public HashSet Namespaces { get; private set; } = new HashSet(); + public HashSet DefinedSymbols { get; } = new HashSet(); + public HashSet Namespaces { get; } = new HashSet(); public CancellationToken CancellationToken { get; set; } public DecompilerSettings Settings { get; } public IDocumentationProvider DocumentationProvider { get; set; } @@ -47,5 +47,15 @@ namespace ICSharpCode.Decompiler } return usingScope; } + + public EnumValueDisplayMode? EnumValueDisplayMode { get; set; } + } + + enum EnumValueDisplayMode + { + None, + All, + AllHex, + FirstOnly } }