From fa409a8186996e56938b276aad0fd4eee9d05793 Mon Sep 17 00:00:00 2001 From: Snorri Gislason <snbeck@microsoft.com> Date: Fri, 7 Feb 2025 03:29:25 +0000 Subject: [PATCH 01/49] Load resource stream outside of delegate --- ILSpy/TextView/DecompilerTextView.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 4e748fd2a..3b9ea493c 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -1447,17 +1447,20 @@ namespace ICSharpCode.ILSpy.TextView if (resourceStream != null) { + IHighlightingDefinition highlightingDefinition; + + using (resourceStream) + using (XmlTextReader reader = new XmlTextReader(resourceStream)) + { + highlightingDefinition = HighlightingLoader.Load(reader, manager); + } + manager.RegisterHighlighting( - name, extensions, - delegate { - using (resourceStream) - using (XmlTextReader reader = new XmlTextReader(resourceStream)) - { - var highlightingDefinition = HighlightingLoader.Load(reader, manager); - ThemeManager.Current.ApplyHighlightingColors(highlightingDefinition); - return highlightingDefinition; - } - }); + name, extensions, + delegate { + ThemeManager.Current.ApplyHighlightingColors(highlightingDefinition); + return highlightingDefinition; + }); } } } From 15b826fb7060a648088bc3e3cd3b20ee90521c38 Mon Sep 17 00:00:00 2001 From: nil4 <nil4@users.noreply.github.com> Date: Sun, 9 Feb 2025 10:41:16 +0100 Subject: [PATCH 02/49] Enable `<RollForward>major</RollForward>` for `ILSpy.exe` Allow `ILSpy.exe` to run when .NET 8 (the version it currently targets) is not installed, but a later major version, e.g. .NET 9, is available. ref. https://learn.microsoft.com/en-us/dotnet/core/versions/selection#control-roll-forward-behavior ref. https://github.com/icsharpcode/ILSpy/issues/3390 --- ILSpy/ILSpy.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index ba2e33f5b..aa5a7264a 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -3,6 +3,7 @@ <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net8.0-windows</TargetFramework> + <RollForward>major</RollForward> <RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers> <GenerateAssemblyInfo>False</GenerateAssemblyInfo> <AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects> From fa50e8d8b3602a8b56bef2f06b8f7024be28440a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Wed, 12 Feb 2025 22:53:22 +0100 Subject: [PATCH 03/49] Add non-embedded attributes to all tests that use older framework versions. --- .../Helpers/Tester.cs | 45 ++++++++++++++++++- .../ICSharpCode.Decompiler.Tests.csproj | 2 +- .../TestCases/Pretty/Records.cs | 28 +----------- .../TestCases/Pretty/Structs.cs | 18 +------- .../OutputVisitor/CSharpOutputVisitor.cs | 4 +- .../Transforms/EscapeInvalidIdentifiers.cs | 18 +++++++- 6 files changed, 66 insertions(+), 49 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index bdea4570f..ed9e2739f 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection.PortableExecutable; @@ -330,6 +329,45 @@ namespace ICSharpCode.Decompiler.Tests.Helpers return tempFile; } + const string nonEmbeddedAttributesSnippet = @" +using System; + +#if !NET60 +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + } + } + + internal class IsExternalInit + { + } +#endif +#if !NET70 + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal sealed class RequiredMemberAttribute : Attribute + { + } +#endif +#if !NET60 +} +#endif +"; + + static readonly Lazy<string> nonEmbeddedAttributesSnippetFile = new Lazy<string>(GetNonEmbeddedAttributesSnippetFile); + + static string GetNonEmbeddedAttributesSnippetFile() + { + // Note: this leaks a temporary file, we're not attempting to delete it, because it is only one. + var tempFile = Path.GetTempFileName(); + File.WriteAllText(tempFile, nonEmbeddedAttributesSnippet); + return tempFile; + } + public static List<string> GetPreprocessorSymbols(CompilerOptions flags) { var preprocessorSymbols = new List<string>(); @@ -419,6 +457,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers sourceFileNames.Add(targetFrameworkAttributeSnippetFile.Value); } + if (targetNet40) + { + sourceFileNames.Add(nonEmbeddedAttributesSnippetFile.Value); + } + var preprocessorSymbols = GetPreprocessorSymbols(flags); if ((flags & CompilerOptions.UseMcsMask) == 0) diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 305d22d96..0ec42ecbe 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -151,7 +151,7 @@ <None Include="TestCases\Pretty\CovariantReturns.cs" /> <Compile Include="TestCases\VBPretty\VBPropertiesTest.cs" /> <None Include="TestCases\ILPretty\Issue2260SwitchString.cs" /> - <None Include="TestCases\Pretty\Records.cs" /> + <Compile Include="TestCases\Pretty\Records.cs" /> <Compile Include="TestCases\VBPretty\Issue2192.cs" /> <Compile Include="Util\FileUtilityTests.cs" /> <Compile Include="TestCases\Pretty\FunctionPointers.cs" /> diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs index 49a4fba3e..f6937aa02 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public record PairWithPrimaryCtor<A, B>(A First, B Second); public record PrimaryCtor(int A, string B); - public record PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a); + public record PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a); public record PrimaryCtorWithField(int A, string B) { public double C = 1.0; @@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public record struct PairWithPrimaryCtor<A, B>(A First, B Second); public record struct PrimaryCtor(int A, string B); - public record struct PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a); + public record struct PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a); public record struct PrimaryCtorWithField(int A, string B) { public double C = 1.0; @@ -242,27 +242,3 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } #endif } -#if !NET60 -namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class CompilerFeatureRequiredAttribute : Attribute - { - public CompilerFeatureRequiredAttribute(string featureName) - { - } - } - - internal class IsExternalInit - { - } -#endif -#if !NET70 - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] - internal sealed class RequiredMemberAttribute : Attribute - { - } -#endif -#if !NET60 -} -#endif diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs index c796990eb..1e90c8382 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs @@ -59,20 +59,4 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public required string LastName { get; set; } } #endif -} - -namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class CompilerFeatureRequiredAttribute : Attribute - { - public CompilerFeatureRequiredAttribute(string featureName) - { - } - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] - internal sealed class RequiredMemberAttribute : Attribute - { - } -} +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 96d591dab..a462ccb9f 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1530,8 +1530,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor WriteToken(Roles.RBracket); switch (attributeSection.Parent) { - case ParameterDeclaration _: - if (attributeSection.NextSibling is AttributeSection) + case ParameterDeclaration pd: + if (pd.Attributes.Last() != attributeSection) Space(policy.SpaceBetweenParameterAttributeSections); else Space(); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs index 828d220af..907fb807f 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs @@ -172,13 +172,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms "Microsoft.CodeAnalysis.EmbeddedAttribute", }; + internal static readonly HashSet<string> nonEmbeddedAttributeNames = new HashSet<string>() { + // non-embedded attributes, but we still want to remove them + "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", + "System.Runtime.CompilerServices.RequiredMemberAttribute", + "System.Runtime.CompilerServices.IsExternalInit", + }; + public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) { var typeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition; - if (typeDefinition == null || !attributeNames.Contains(typeDefinition.FullName)) + if (typeDefinition == null) return; - if (!typeDefinition.HasAttribute(KnownAttribute.Embedded)) + + if (attributeNames.Contains(typeDefinition.FullName)) + { + if (!typeDefinition.HasAttribute(KnownAttribute.Embedded)) + return; + } + else if (!nonEmbeddedAttributeNames.Contains(typeDefinition.FullName)) return; + if (typeDeclaration.Parent is NamespaceDeclaration ns && ns.Members.Count == 1) ns.Remove(); else From 906d248403a454843e8bb397b612d5e8f0b26b0e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Wed, 12 Feb 2025 22:54:35 +0100 Subject: [PATCH 04/49] Fix #3392: uses of init-setters must use object-initializer syntax. --- .../TestCases/Pretty/InitializerTests.cs | 24 +++++++++++++++++++ ...ransformCollectionAndObjectInitializers.cs | 8 ++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index 9accce88a..874abd89b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -235,6 +235,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests { int Property { get; set; } } + +#if CS90 + public class Issue3392Type + { + public bool Flag { get; init; } + public List<int> List { get; } = new List<int>(); + + public Issue3392Type(object x) + { + + } + } +#endif #endregion private S s1; @@ -1010,6 +1023,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests otherItem.Data2.Nullable = 3m; return otherItem; } + +#if CS90 + public Issue3392Type Issue3392(Issue3392Type x) + { + x = new Issue3392Type(null) { + Flag = false + }; + x.List.AddRange(Enumerable.Range(0, 10)); + return x; + } +#endif #if CS60 public OtherItem2 Issue1345c() { diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs index 781a83c01..764cbd64b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -103,6 +103,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; } int initializerItemsCount = 0; + bool initializerContainsInitOnlyItems = false; possibleIndexVariables.Clear(); currentPath.Clear(); isCollection = false; @@ -113,13 +114,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms // if the method is a setter we're dealing with an object initializer // if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer while (pos + initializerItemsCount + 1 < block.Instructions.Count - && IsPartOfInitializer(block.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind, context)) + && IsPartOfInitializer(block.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind, ref initializerContainsInitOnlyItems, context)) { initializerItemsCount++; } // Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable // directly after the possible initializer. - if (IsMethodCallOnVariable(block.Instructions[pos + initializerItemsCount + 1], v)) + if (!initializerContainsInitOnlyItems && IsMethodCallOnVariable(block.Instructions[pos + initializerItemsCount + 1], v)) return; // Calculate the correct number of statements inside the initializer: // All index variables that were used in the initializer have Index set to -1. @@ -200,7 +201,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms bool isCollection; readonly Stack<HashSet<AccessPathElement>> pathStack = new Stack<HashSet<AccessPathElement>>(); - bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, StatementTransformContext context) + bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, ref bool initializerContainsInitOnlyItems, StatementTransformContext context) { // Include any stores to local variables that are single-assigned and do not reference the initializer-variable // in the list of possible index variables. @@ -255,6 +256,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer) blockKind = BlockKind.ObjectInitializer; + initializerContainsInitOnlyItems |= lastElement.Member is IProperty { Setter.IsInitOnly: true }; return true; default: return false; From d7d0f8241833baab1b5417ef2462ef4dd5cdcb43 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Wed, 12 Feb 2025 22:56:49 +0100 Subject: [PATCH 05/49] Fix #3384: add special case for null-literal conversions in optional argument handling. --- .../TestCases/Pretty/OptionalArguments.cs | 22 +++++++++++++++++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 5 ++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs index 82cda462b..d69c8f35a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs @@ -32,6 +32,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty B } + internal class OptionalArgumentTest + { + private static void Test() + { + Test2(); + Test3(); + Test4(); + } + + private static void Test2(int a = 0) + { + } + + private static void Test3(int a = 0, int? b = null) + { + } + + private static void Test4(int? b = null, int a = 0) + { + } + } + public OptionalArguments(string name, int a = 5) { diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index fb46b12ea..5a5fe7802 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1043,7 +1043,10 @@ namespace ICSharpCode.Decompiler.CSharp bool IsOptionalArgument(IParameter parameter, TranslatedExpression arg) { - if (!parameter.IsOptional || !arg.ResolveResult.IsCompileTimeConstant) + if (!parameter.IsOptional) + return false; + + if (!arg.ResolveResult.IsCompileTimeConstant && arg.ResolveResult is not ConversionResolveResult { Conversion.IsNullLiteralConversion: true }) return false; if (parameter.GetAttributes().Any(a => a.AttributeType.IsKnownType(KnownAttribute.CallerMemberName) || a.AttributeType.IsKnownType(KnownAttribute.CallerFilePath) From 2c419f68cf92bebdf2c656571881efbd959de57e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Mon, 13 Jan 2025 20:04:58 +0100 Subject: [PATCH 06/49] Fix #3322: Add missing checks for equality comparison --- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../PrettyTestRunner.cs | 6 ++++++ .../TestCases/Pretty/Comparisons.cs | 18 ++++++++++++++++++ .../CSharp/Resolver/CSharpResolver.cs | 5 ++++- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 0ec42ecbe..3ff44e2bb 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -130,6 +130,7 @@ <Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" /> <Compile Include="TestAssemblyResolver.cs" /> <Compile Include="TestCases\ILPretty\MonoFixed.cs" /> + <Compile Include="TestCases\Pretty\Comparisons.cs" /> <None Include="TestCases\VBPretty\VBAutomaticEvents.vb" /> <Compile Include="TestCases\VBPretty\VBAutomaticEvents.cs" /> <Compile Include="TestCases\VBPretty\VBNonGenericForEach.cs" /> diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 284b51401..f4ff7c914 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -586,6 +586,12 @@ namespace ICSharpCode.Decompiler.Tests await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task Comparisons([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task ConstantsTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs new file mode 100644 index 000000000..5a1df3c9b --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs @@ -0,0 +1,18 @@ +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public class Comparisons + { + private class A + { + } + + private class B + { + } + + private bool CompareUnrelatedNeedsCast(A a, B b) + { + return (object)a == b; + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index 14ab787ba..9e556e6cd 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -805,7 +805,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality) { - if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true) + if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true + && (conversions.IdentityConversion(lhsType, rhsType) + || conversions.ExplicitConversion(lhsType, rhsType).IsReferenceConversion + || conversions.ExplicitConversion(rhsType, lhsType).IsReferenceConversion)) { // If it's a reference comparison if (op == BinaryOperatorType.Equality) From fb2561ac83f20060e7c374ef760562b2c3dcec7e Mon Sep 17 00:00:00 2001 From: tom-englert <mail@tom-englert.de> Date: Wed, 19 Feb 2025 18:29:49 +0100 Subject: [PATCH 07/49] Fix #3402: System.NullReferenceException on right click on .NET 9 (#3403) * Fix #3402: System.NullReferenceException on right click on .NET 9 * Add comments to clarify about used design pattern --- Directory.Packages.props | 10 +++++----- ILSpy/AssemblyTree/AssemblyTreeModel.cs | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a84164d03..f9cbde7a7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -27,7 +27,7 @@ <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> <PackageVersion Include="Microsoft.NETCore.ILAsm" Version="9.0.0" /> <PackageVersion Include="Microsoft.NETCore.ILDAsm" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Sbom.Targets" Version="3.0.1" /> + <PackageVersion Include="Microsoft.Sbom.Targets" Version="3.0.1" /> <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" /> <PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" /> <PackageVersion Include="Mono.Cecil" Version="0.11.6" /> @@ -45,10 +45,10 @@ <PackageVersion Include="System.Reflection.Metadata" Version="9.0.1" /> <PackageVersion Include="System.Resources.Extensions" Version="9.0.1" /> <PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0" /> - <PackageVersion Include="TomsToolbox.Composition.MicrosoftExtensions" Version="2.21.0" /> - <PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.20.0" /> - <PackageVersion Include="TomsToolbox.Wpf.Composition.AttributedModel" Version="2.21.0" /> - <PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.21.0" /> + <PackageVersion Include="TomsToolbox.Composition.MicrosoftExtensions" Version="2.22.0" /> + <PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.22.0" /> + <PackageVersion Include="TomsToolbox.Wpf.Composition.AttributedModel" Version="2.22.0" /> + <PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.22.0" /> <PackageVersion Include="coverlet.collector" Version="6.0.3" /> <PackageVersion Include="System.Net.Http" Version="4.3.4" /> <PackageVersion Include="System.Private.Uri" Version="4.3.2" /> diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index 11df01390..dd5ba6fba 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -767,6 +767,8 @@ namespace ICSharpCode.ILSpy.AssemblyTree } else { + // ensure that we are only connected once to the event, else we might get multiple notifications + ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed; ContextMenuProvider.ContextMenuClosed += ContextMenuClosed; } } From 202c5e22e314be52dcf90894b387dad7aa4c0b89 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sun, 16 Feb 2025 09:12:00 +0100 Subject: [PATCH 08/49] Fix #3383: more aggressively transform object initializers on structs --- .../TestCases/Correctness/InitializerTests.cs | 16 ----- .../TestCases/Pretty/InitializerTests.cs | 61 ++++++++++++++----- ...ransformCollectionAndObjectInitializers.cs | 8 --- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs index 1e0bd901b..7d4653f29 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs @@ -318,14 +318,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness }; } - public static void NotAStructInitializer_DefaultConstructor() - { - InitializerTests.StructData data = new InitializerTests.StructData(); - data.Field = 1; - data.Property = 2; - InitializerTests.X(InitializerTests.Y(), data); - } - public static void StructInitializer_DefaultConstructor() { InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData { @@ -334,14 +326,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness }); } - public static void NotAStructInitializer_ExplicitConstructor() - { - InitializerTests.StructData data = new InitializerTests.StructData(0); - data.Field = 1; - data.Property = 2; - InitializerTests.X(InitializerTests.Y(), data); - } - public static void StructInitializer_ExplicitConstructor() { InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData(0) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index 874abd89b..e02f23ff9 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -247,6 +247,51 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests } } + + private class StructInitPropertiesTest + { + private class TypeA + { + public int A { get; set; } + public int B { get; set; } + } + + private struct TypeB + { + public int A { get; set; } + public int B { get; set; } + } + + private struct TypeC + { + public int A { get; init; } + public int B { get; init; } + } + + private static TypeA TestA() + { + return new TypeA { + A = 1, + B = 2 + }; + } + + private static TypeB TestB() + { + return new TypeB { + A = 1, + B = 2 + }; + } + + private static TypeC TestC() + { + return new TypeC { + A = 1, + B = 2 + }; + } + } #endif #endregion @@ -928,14 +973,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests }); } - public static void NotAStructInitializer_DefaultConstructor() - { - StructData structData = default(StructData); - structData.Field = 1; - structData.Property = 2; - X(Y(), structData); - } - public static void StructInitializer_DefaultConstructor() { X(Y(), new StructData { @@ -956,14 +993,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests }; } - public static void NotAStructInitializer_ExplicitConstructor() - { - StructData structData = new StructData(0); - structData.Field = 1; - structData.Property = 2; - X(Y(), structData); - } - public static void StructInitializer_ExplicitConstructor() { X(Y(), new StructData(0) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs index 764cbd64b..c632e22a2 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -73,14 +73,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms instType = newObjInst.Method.DeclaringType; break; case DefaultValue defaultVal: - if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !currentMethod.IsConstructor) - { - // on statement level (no other expressions on IL stack), - // prefer to keep local variables (but not stack slots), - // unless we are in a constructor (where inlining object initializers might be - // critical for the base ctor call) - return; - } instType = defaultVal.Type; break; case Call c when c.Method.FullNameIs("System.Activator", "CreateInstance") && c.Method.TypeArguments.Count == 1: From 00969946795b5d36caebe75dc56ee25672e1f749 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Thu, 20 Feb 2025 10:00:59 +0100 Subject: [PATCH 09/49] Fix #3401: normalize directory separators to use the current platform separator --- .../CSharp/ProjectDecompiler/WholeProjectDecompiler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index f8b687b73..da5671559 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -692,10 +692,10 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (separateAtDots) currentSegmentLength = 0; } - else if (treatAsFileName && (c == '/' || c == '\\') && currentSegmentLength > 1) + else if (treatAsFileName && (c is '/' or '\\') && currentSegmentLength > 1) { // if we treat this as a file name, we've started a new segment - b.Append(c); + b.Append(Path.DirectorySeparatorChar); currentSegmentLength = 0; } else From 807ac32aab8705d21cca80bea0b661b9cbccff08 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Thu, 20 Feb 2025 10:29:52 +0100 Subject: [PATCH 10/49] Hide ItemsControl of ComboBox width hack, if languageVersionComboBox is invisible. --- ILSpy/Controls/MainToolBar.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/ILSpy/Controls/MainToolBar.xaml b/ILSpy/Controls/MainToolBar.xaml index 867c8119d..927747f31 100644 --- a/ILSpy/Controls/MainToolBar.xaml +++ b/ILSpy/Controls/MainToolBar.xaml @@ -75,6 +75,7 @@ <Grid Margin="2,0"> <ItemsControl ItemsSource="{Binding SelectedItem.LanguageVersions, ElementName=languageComboBox, UpdateSourceTrigger=PropertyChanged}" + Visibility="{Binding SelectedItem.HasLanguageVersions, ElementName=languageComboBox, Converter={toms:BooleanToVisibilityConverter}}" DisplayMemberPath="DisplayName" Height="0" Margin="15,0" /> <ComboBox Name="languageVersionComboBox" DisplayMemberPath="DisplayName" MaxDropDownHeight="Auto" ToolTip="{x:Static properties:Resources.SelectVersionDropdownTooltip}" From 5b90dbdabf654d670ae5b0dd863df6214e485a61 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Thu, 20 Feb 2025 10:49:50 +0100 Subject: [PATCH 11/49] Fix #3385: Allow address uses of structs in using transform, if the reference is passed to an in parameter. --- .../TestCases/Pretty/Using.cs | 46 +++++++++++++++++++ .../IL/Transforms/ILInlining.cs | 2 +- .../IL/Transforms/UsingTransform.cs | 14 +++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs index 9674b8cf7..04e819c70 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs @@ -39,6 +39,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + private struct TypeA_Issue3385 : IDisposable + { + private int dummy; + + public void Dispose() + { + } + +#if !ROSLYN3 + public static implicit operator TypeB_Issue3385(TypeA_Issue3385 a) + { + return default(TypeB_Issue3385); + } +#else + public static implicit operator TypeB_Issue3385(in TypeA_Issue3385 a) + { + return default(TypeB_Issue3385); + } +#endif + } + + private struct TypeB_Issue3385 + { + private int dummy; + } + #if CS80 [StructLayout(LayoutKind.Sequential, Size = 1)] public ref struct UsingRefStruct @@ -161,5 +187,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } #endif + + public static void Issue3385() + { +#if ROSLYN3 + using (TypeA_Issue3385 a = default(TypeA_Issue3385)) +#else + using (TypeA_Issue3385 typeA_Issue = default(TypeA_Issue3385)) +#endif + { +#if ROSLYN3 + Empty(a); +#else + Empty(typeA_Issue); +#endif + } + } + + private static void Empty(TypeB_Issue3385 b) + { + } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 7b38b2e90..3e2c743c8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -448,7 +448,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return inst != ldloca && inst.Parent is LdObj; } - static bool IsPassedToInParameter(LdLoca ldloca) + internal static bool IsPassedToInParameter(LdLoca ldloca) { if (ldloca.Parent is not CallInstruction call) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index 96fe3d13d..020166ac0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -89,7 +89,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally))) return false; - if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la)))) + if (!storeInst.Variable.AddressInstructions.All(ValidateAddressUse)) return false; if (storeInst.Variable.StoreInstructions.Count > 1) return false; @@ -104,6 +104,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike }.WithILRange(storeInst); return true; + + bool ValidateAddressUse(LdLoca la) + { + if (!la.IsDescendantOf(tryFinally)) + return false; + if (la.IsDescendantOf(tryFinally.TryBlock)) + { + if (!(ILInlining.IsUsedAsThisPointerInCall(la) || ILInlining.IsPassedToInParameter(la))) + return false; + } + return true; + } } /// <summary> From c0c5559a7b87d7322ec5228bd3da2cf3136ef7c9 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Thu, 20 Feb 2025 11:53:59 +0100 Subject: [PATCH 12/49] Fix #3399: Add module filename as ToolTip of AnalyzerEntityTreeNode --- ILSpy/Analyzers/AnalyzerEntityTreeNode.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs index 4217e411c..630bde183 100644 --- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs @@ -22,8 +22,8 @@ using System.Windows; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpyX; -using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; using ICSharpCode.ILSpyX.TreeView; +using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; namespace ICSharpCode.ILSpy.Analyzers { @@ -46,6 +46,8 @@ namespace ICSharpCode.ILSpy.Analyzers MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(this.Member.ParentModule?.MetadataFile, this.Member.MetadataToken))); } + public override object ToolTip => Member.ParentModule?.MetadataFile?.FileName; + public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies) { foreach (LoadedAssembly asm in removedAssemblies) From 79ddc448b6139912613cfddce66908b07c919137 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Thu, 20 Feb 2025 14:12:03 +0100 Subject: [PATCH 13/49] Fix #3377: Extend error information if multiple assemblies with the same (short) name are selected when creating a solution file. --- ILSpy/SolutionWriter.cs | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs index 0fee4c18a..29e8db8b0 100644 --- a/ILSpy/SolutionWriter.cs +++ b/ILSpy/SolutionWriter.cs @@ -28,7 +28,6 @@ using System.Threading.Tasks; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.Util; -using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; @@ -99,13 +98,42 @@ namespace ICSharpCode.ILSpy { var result = new AvalonEditTextOutput(); - var duplicates = new HashSet<string>(); - if (assemblies.Any(asm => !duplicates.Add(asm.ShortName))) + var assembliesByShortName = assemblies.ToLookup(_ => _.ShortName); + bool first = true; + bool abort = false; + + foreach (var item in assembliesByShortName) { - result.WriteLine("Duplicate assembly names selected, cannot generate a solution."); - return result; + var enumerator = item.GetEnumerator(); + if (!enumerator.MoveNext()) + continue; + var firstAssembly = enumerator.Current; + if (!enumerator.MoveNext()) + continue; + if (first) + { + result.WriteLine("Duplicate assembly names selected, cannot generate a solution:"); + abort = true; + } + + result.Write("- " + firstAssembly.Text + " conflicts with "); + + first = true; + do + { + var asm = enumerator.Current; + if (!first) + result.Write(", "); + result.Write(asm.Text); + first = false; + } while (enumerator.MoveNext()); + result.WriteLine(); + first = false; } + if (abort) + return result; + Stopwatch stopwatch = Stopwatch.StartNew(); try From 8eadd907c9bcb92e89f23d31d2b8f4fab8dc21e4 Mon Sep 17 00:00:00 2001 From: tom-englert <mail@tom-englert.de> Date: Thu, 20 Feb 2025 16:19:26 +0100 Subject: [PATCH 14/49] Fix #3393: Option to turn off smooth scrolling --- ILSpy/App.xaml | 12 ++++++++++-- ILSpy/App.xaml.cs | 2 ++ ILSpy/Controls/ZoomScrollViewer.xaml | 12 ++++++++++-- ILSpy/Options/DisplaySettings.cs | 8 ++++++++ ILSpy/Options/DisplaySettingsPanel.xaml | 1 + ILSpy/Properties/Resources.Designer.cs | 9 +++++++++ ILSpy/Properties/Resources.resx | 3 +++ 7 files changed, 43 insertions(+), 4 deletions(-) diff --git a/ILSpy/App.xaml b/ILSpy/App.xaml index ce2e14086..aac1e7181 100644 --- a/ILSpy/App.xaml +++ b/ILSpy/App.xaml @@ -3,7 +3,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:styles="urn:TomsToolbox.Wpf.Styles" xmlns:toms="urn:TomsToolbox" - xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes"> + xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" + xmlns:composition="urn:TomsToolbox.Composition" + xmlns:util="clr-namespace:ICSharpCode.ILSpy.Util"> <Application.Resources> <Style x:Key="DialogWindow" TargetType="{x:Type Window}"> <Setter Property="ShowInTaskbar" Value="False" /> @@ -24,7 +26,13 @@ </Style> <Style TargetType="ScrollViewer"> - <Setter Property="toms:AdvancedScrollWheelBehavior.Attach" Value="WithAnimation" /> + <Setter Property="toms:StyleBindings.Behaviors"> + <Setter.Value> + <toms:BehaviorCollection> + <toms:AdvancedScrollWheelBehavior UseScrollingAnimation="{Binding Path=(util:SettingsService.DisplaySettings).EnableSmoothScrolling, Source={composition:Import util:SettingsService}}"/> + </toms:BehaviorCollection> + </Setter.Value> + </Setter> </Style> </Application.Resources> diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index c82af3125..432499935 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -69,6 +69,8 @@ namespace ICSharpCode.ILSpy var cmdArgs = Environment.GetCommandLineArgs().Skip(1); CommandLineArguments = CommandLineArguments.Create(cmdArgs); + // This is only a temporary, read only handle to the settings service to access the AllowMultipleInstances setting before DI is initialized. + // At runtime, you must use the service via DI! var settingsService = new SettingsService(); bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true) diff --git a/ILSpy/Controls/ZoomScrollViewer.xaml b/ILSpy/Controls/ZoomScrollViewer.xaml index 509b8d636..648e1fb42 100644 --- a/ILSpy/Controls/ZoomScrollViewer.xaml +++ b/ILSpy/Controls/ZoomScrollViewer.xaml @@ -1,10 +1,18 @@ <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Controls="clr-namespace:ICSharpCode.ILSpy.Controls" - xmlns:toms="urn:TomsToolbox"> + xmlns:toms="urn:TomsToolbox" + xmlns:composition="urn:TomsToolbox.Composition" + xmlns:util="clr-namespace:ICSharpCode.ILSpy.Util"> <Style TargetType="{x:Type Controls:ZoomScrollViewer}"> - <Setter Property="toms:AdvancedScrollWheelBehavior.Attach" Value="WithAnimation" /> + <Setter Property="toms:StyleBindings.Behaviors"> + <Setter.Value> + <toms:BehaviorCollection> + <toms:AdvancedScrollWheelBehavior UseScrollingAnimation="{Binding Path=(util:SettingsService.DisplaySettings).EnableSmoothScrolling, Source={composition:Import util:SettingsService}}"/> + </toms:BehaviorCollection> + </Setter.Value> + </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Controls:ZoomScrollViewer}"> diff --git a/ILSpy/Options/DisplaySettings.cs b/ILSpy/Options/DisplaySettings.cs index 9eb07b394..8b21987bb 100644 --- a/ILSpy/Options/DisplaySettings.cs +++ b/ILSpy/Options/DisplaySettings.cs @@ -148,6 +148,12 @@ namespace ICSharpCode.ILSpy.Options set => SetProperty(ref showRawOffsetsAndBytesBeforeInstruction, value); } + private bool enableSmoothScrolling; + public bool EnableSmoothScrolling { + get => enableSmoothScrolling; + set => SetProperty(ref enableSmoothScrolling, value); + } + public XName SectionName => "DisplaySettings"; public void LoadFromXml(XElement section) @@ -172,6 +178,7 @@ namespace ICSharpCode.ILSpy.Options UseNestedNamespaceNodes = (bool?)section.Attribute("UseNestedNamespaceNodes") ?? false; ShowRawOffsetsAndBytesBeforeInstruction = (bool?)section.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false; StyleWindowTitleBar = (bool?)section.Attribute("StyleWindowTitleBar") ?? false; + EnableSmoothScrolling = (bool?)section.Attribute("EnableSmoothScrolling") ?? true; } public XElement SaveToXml() @@ -198,6 +205,7 @@ namespace ICSharpCode.ILSpy.Options section.SetAttributeValue("UseNestedNamespaceNodes", UseNestedNamespaceNodes); section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", ShowRawOffsetsAndBytesBeforeInstruction); section.SetAttributeValue("StyleWindowTitleBar", StyleWindowTitleBar); + section.SetAttributeValue("EnableSmoothScrolling", EnableSmoothScrolling); return section; } diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml b/ILSpy/Options/DisplaySettingsPanel.xaml index 9fb50d7a9..7df9a6b60 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml +++ b/ILSpy/Options/DisplaySettingsPanel.xaml @@ -78,6 +78,7 @@ <StackPanel Margin="3"> <CheckBox IsChecked="{Binding Settings.SortResults}" Content="{x:Static properties:Resources.SortResultsFitness}"></CheckBox> <CheckBox IsChecked="{Binding Settings.StyleWindowTitleBar}" Content="{x:Static properties:Resources.StyleTheWindowTitleBar}"></CheckBox> + <CheckBox IsChecked="{Binding Settings.EnableSmoothScrolling}" Content="{x:Static properties:Resources.EnableSmoothScrolling}"></CheckBox> </StackPanel> </GroupBox> </StackPanel> diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 01628056b..7e9235cae 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1722,6 +1722,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// <summary> + /// Looks up a localized string similar to Enable smooth scrolling. + /// </summary> + public static string EnableSmoothScrolling { + get { + return ResourceManager.GetString("EnableSmoothScrolling", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Enable word wrap. /// </summary> diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index bcb79fae7..a6e65ca67 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -591,6 +591,9 @@ Are you sure you want to continue?</value> <data name="EnableFoldingBlocksBraces" xml:space="preserve"> <value>Enable folding on all blocks in braces</value> </data> + <data name="EnableSmoothScrolling" xml:space="preserve"> + <value>Enable smooth scrolling</value> + </data> <data name="EnableWordWrap" xml:space="preserve"> <value>Enable word wrap</value> </data> From 8b768794937b1ad250578384a3bed9f8bf7266d6 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Thu, 20 Feb 2025 22:23:06 +0100 Subject: [PATCH 15/49] Fix #2716: Add an option to allow sorting custom attributes --- .../CSharp/CSharpDecompiler.cs | 1 + .../CSharp/Syntax/TypeSystemAstBuilder.cs | 78 +++++++++++++++++-- ICSharpCode.Decompiler/DecompilerSettings.cs | 18 +++++ .../ICSharpCode.Decompiler.csproj | 1 + .../Util/DelegateComparer.cs | 39 ++++++++++ ILSpy/Properties/Resources.Designer.cs | 9 +++ ILSpy/Properties/Resources.resx | 3 + ILSpy/Properties/Resources.zh-Hans.resx | 3 + 8 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 ICSharpCode.Decompiler/Util/DelegateComparer.cs diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 7147785de..86f6ed66e 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -528,6 +528,7 @@ namespace ICSharpCode.Decompiler.CSharp { var typeSystemAstBuilder = new TypeSystemAstBuilder(); typeSystemAstBuilder.ShowAttributes = true; + typeSystemAstBuilder.SortAttributes = settings.SortCustomAttributes; typeSystemAstBuilder.AlwaysUseShortTypeNames = true; typeSystemAstBuilder.AddResolveResultAnnotations = true; typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index c26998303..f217717f1 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -147,6 +147,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax /// </summary> public bool ShowAttributes { get; set; } + /// <summary> + /// Controls whether to sort attributes, if set to <see langword="false" /> attributes are shown in metadata order. + /// The default value is <see langword="false" />. + /// </summary> + public bool SortAttributes { get; set; } + /// <summary> /// Controls whether to use fully-qualified type names or short type names. /// The default value is <see langword="false" />. @@ -793,16 +799,72 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return attr; } - private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes) - { - return attributes.Select(a => new AttributeSection(ConvertAttribute(a))); - } - - private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes, string target) + private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes, string target = null) { - return attributes.Select(a => new AttributeSection(ConvertAttribute(a)) { - AttributeTarget = target + if (SortAttributes) + attributes = attributes.OrderBy(a => a, new DelegateComparer<IAttribute>(CompareAttribute)); + return attributes.Select(a => { + var section = new AttributeSection(ConvertAttribute(a)); + if (target != null) + section.AttributeTarget = target; + return section; }); + + static int CompareAttribute(IAttribute a, IAttribute b) + { + int result = CompareType(a.AttributeType, b.AttributeType); + if (result != 0) + return result; + if (a.HasDecodeErrors && b.HasDecodeErrors) + return 0; + if (a.HasDecodeErrors) + return -1; + if (b.HasDecodeErrors) + return 1; + result = a.FixedArguments.Length - b.FixedArguments.Length; + if (result != 0) + return result; + for (int i = 0; i < a.FixedArguments.Length; i++) + { + var argA = a.FixedArguments[i]; + var argB = b.FixedArguments[i]; + result = CompareType(argA.Type, argB.Type); + if (result != 0) + return result; + if (argA.Value is IComparable compA && argB.Value is IComparable compB) + result = compA.CompareTo(compB); + else + result = 0; + if (result != 0) + return result; + } + result = a.NamedArguments.Length - b.NamedArguments.Length; + if (result != 0) + return result; + for (int i = 0; i < a.FixedArguments.Length; i++) + { + var argA = a.NamedArguments[i]; + var argB = b.NamedArguments[i]; + result = argA.Name.CompareTo(argB.Name); + if (result != 0) + return result; + result = CompareType(argA.Type, argB.Type); + if (result != 0) + return result; + if (argA.Value is IComparable compA && argB.Value is IComparable compB) + result = compA.CompareTo(compB); + else + result = 0; + if (result != 0) + return result; + } + return 0; + } + + static int CompareType(IType a, IType b) + { + return a.FullName.CompareTo(b.FullName); + } } #endregion diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 773128bc8..b2d7f75e2 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -2148,6 +2148,24 @@ namespace ICSharpCode.Decompiler } } + bool sortCustomAttributes = false; + + /// <summary> + /// Sort custom attributes. + /// </summary> + [Category("DecompilerSettings.Other")] + [Description("DecompilerSettings.SortCustomAttributes")] + public bool SortCustomAttributes { + get { return sortCustomAttributes; } + set { + if (sortCustomAttributes != value) + { + sortCustomAttributes = value; + OnPropertyChanged(); + } + } + } + CSharpFormattingOptions csharpFormattingOptions; [Browsable(false)] diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index af50e7947..2a9eb220b 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -154,6 +154,7 @@ <Compile Include="Properties\DecompilerVersionInfo.cs" /> <Compile Include="TypeSystem\ITypeDefinitionOrUnknown.cs" /> <Compile Include="Util\BitOperations.cs" /> + <Compile Include="Util\DelegateComparer.cs" /> <Compile Include="Util\Index.cs" /> <Compile Include="Metadata\WebCilFile.cs" /> <None Include="Properties\DecompilerVersionInfo.template.cs" /> diff --git a/ICSharpCode.Decompiler/Util/DelegateComparer.cs b/ICSharpCode.Decompiler/Util/DelegateComparer.cs new file mode 100644 index 000000000..192a9196c --- /dev/null +++ b/ICSharpCode.Decompiler/Util/DelegateComparer.cs @@ -0,0 +1,39 @@ +#nullable enable +// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.Util +{ + public class DelegateComparer<T> : IComparer<T> + { + private readonly Func<T?, T?, int> func; + + public DelegateComparer(Func<T?, T?, int> func) + { + this.func = func ?? throw new ArgumentNullException(nameof(func)); + } + + public int Compare(T? x, T? y) + { + return func(x, y); + } + } +} diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 7e9235cae..ce6597717 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1325,6 +1325,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// <summary> + /// Looks up a localized string similar to Sort custom attributes. + /// </summary> + public static string DecompilerSettings_SortCustomAttributes { + get { + return ResourceManager.GetString("DecompilerSettings.SortCustomAttributes", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Detect switch on integer even if IL code does not use a jump table. /// </summary> diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index a6e65ca67..dd575638a 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -462,6 +462,9 @@ Are you sure you want to continue?</value> <data name="DecompilerSettings.ShowInfoFromDebugSymbolsIfAvailable" xml:space="preserve"> <value>Show info from debug symbols, if available</value> </data> + <data name="DecompilerSettings.SortCustomAttributes" xml:space="preserve"> + <value>Sort custom attributes</value> + </data> <data name="DecompilerSettings.SparseIntegerSwitch" xml:space="preserve"> <value>Detect switch on integer even if IL code does not use a jump table</value> </data> diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx index 8e46de32e..472ac2503 100644 --- a/ILSpy/Properties/Resources.zh-Hans.resx +++ b/ILSpy/Properties/Resources.zh-Hans.resx @@ -435,6 +435,9 @@ <data name="DecompilerSettings.ShowInfoFromDebugSymbolsIfAvailable" xml:space="preserve"> <value>显示调试符号中的信息(如果可用)</value> </data> + <data name="DecompilerSettings.SortCustomAttributes" xml:space="preserve"> + <value /> + </data> <data name="DecompilerSettings.SparseIntegerSwitch" xml:space="preserve"> <value>检测整型 switch 即使 IL 代码不使用跳转表</value> </data> From 6312ab18aee0ff48301fe2f1578dbd67543bebf6 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Feb 2025 07:11:42 +0100 Subject: [PATCH 16/49] #2716: Fix typo --- ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index f217717f1..c9ae2a66d 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -841,7 +841,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax result = a.NamedArguments.Length - b.NamedArguments.Length; if (result != 0) return result; - for (int i = 0; i < a.FixedArguments.Length; i++) + for (int i = 0; i < a.NamedArguments.Length; i++) { var argA = a.NamedArguments[i]; var argB = b.NamedArguments[i]; From 292c21d68efd776ff658de4a941f36df14d2bc19 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Feb 2025 07:32:32 +0100 Subject: [PATCH 17/49] #3401: Treat class name as single name and not as path. --- .../CSharp/ProjectDecompiler/WholeProjectDecompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index da5671559..283e2cb94 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -238,7 +238,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler string GetFileFileNameForHandle(TypeDefinitionHandle h) { var type = metadata.GetTypeDefinition(h); - string file = SanitizeFileName(metadata.GetString(type.Name) + ".cs"); + string file = CleanUpFileName(metadata.GetString(type.Name) + ".cs"); string ns = metadata.GetString(type.Namespace); if (string.IsNullOrEmpty(ns)) { From 31bbcf41bcb0dcd0b3e019bc56fa158d8ac37f70 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Feb 2025 09:14:23 +0100 Subject: [PATCH 18/49] Fix handling of file extensions in project export. --- .../WholeProjectDecompiler.cs | 28 +++++++++++-------- .../DebugInfo/PortablePdbWriter.cs | 2 +- .../BamlResourceNodeFactory.cs | 2 +- ILSpy/Commands/GeneratePdbContextMenuEntry.cs | 2 +- ILSpy/Commands/SelectPdbContextMenuEntry.cs | 2 +- ILSpy/TextView/DecompilerTextView.cs | 2 +- ILSpy/TreeNodes/AssemblyTreeNode.cs | 2 +- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 283e2cb94..ec3e955b2 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection.Metadata; @@ -135,7 +136,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler public void DecompileProject(MetadataFile file, string targetDirectory, CancellationToken cancellationToken = default(CancellationToken)) { - string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(file.Name) + ".csproj"); + string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(file.Name, ".csproj")); using (var writer = new StreamWriter(projectFileName)) { DecompileProject(file, targetDirectory, writer, cancellationToken); @@ -238,7 +239,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler string GetFileFileNameForHandle(TypeDefinitionHandle h) { var type = metadata.GetTypeDefinition(h); - string file = CleanUpFileName(metadata.GetString(type.Name) + ".cs"); + string file = CleanUpFileName(metadata.GetString(type.Name), ".cs"); string ns = metadata.GetString(type.Namespace); if (string.IsNullOrEmpty(ns)) { @@ -339,8 +340,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { foreach (var (name, value) in resourcesFile) { - string fileName = SanitizeFileName(name) - .Replace('/', Path.DirectorySeparatorChar); + string fileName = SanitizeFileName(name); string dirName = Path.GetDirectoryName(fileName); if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) { @@ -609,9 +609,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// <summary> /// Cleans up a node name for use as a file name. /// </summary> - public static string CleanUpFileName(string text) + public static string CleanUpFileName(string text, string extension) { - return CleanUpName(text, separateAtDots: false, treatAsFileName: false); + Debug.Assert(!string.IsNullOrEmpty(extension)); + if (!extension.StartsWith(".")) + extension = "." + extension; + text = text + extension; + + return CleanUpName(text, separateAtDots: false, treatAsFileName: !string.IsNullOrEmpty(extension), treatAsPath: false); } /// <summary> @@ -620,7 +625,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// </summary> public static string SanitizeFileName(string fileName) { - return CleanUpName(fileName, separateAtDots: false, treatAsFileName: true); + return CleanUpName(fileName, separateAtDots: false, treatAsFileName: true, treatAsPath: true); } /// <summary> @@ -629,7 +634,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// If <paramref name="treatAsFileName"/> is active, we check for file a extension and try to preserve it, /// if it's valid. /// </summary> - static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName) + static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName, bool treatAsPath) { // Remove anything that could be confused with a rooted path. int pos = text.IndexOf(':'); @@ -692,7 +697,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (separateAtDots) currentSegmentLength = 0; } - else if (treatAsFileName && (c is '/' or '\\') && currentSegmentLength > 1) + else if (treatAsPath && (c is '/' or '\\') && currentSegmentLength > 1) { // if we treat this as a file name, we've started a new segment b.Append(Path.DirectorySeparatorChar); @@ -732,13 +737,12 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// </summary> public static string CleanUpDirectoryName(string text) { - return CleanUpName(text, separateAtDots: false, treatAsFileName: false); + return CleanUpName(text, separateAtDots: false, treatAsFileName: false, treatAsPath: false); } public static string CleanUpPath(string text) { - return CleanUpName(text, separateAtDots: true, treatAsFileName: false) - .Replace('.', Path.DirectorySeparatorChar); + return CleanUpName(text, separateAtDots: true, treatAsFileName: true, treatAsPath: true); } static bool IsReservedFileSystemName(string name) diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs index 3779ace15..792c3e727 100644 --- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs +++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs @@ -92,7 +92,7 @@ namespace ICSharpCode.Decompiler.DebugInfo string ns = settings.UseNestedDirectoriesForNamespaces ? WholeProjectDecompiler.CleanUpPath(typeName.Namespace) : WholeProjectDecompiler.CleanUpDirectoryName(typeName.Namespace); - return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs"); + return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name, ".cs")); } var sourceFiles = reader.GetTopLevelTypeDefinitions().Where(t => IncludeTypeWhenGeneratingPdb(file, t, settings)).GroupBy(BuildFileNameFromTypeName).ToList(); diff --git a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs index 29c54c100..681a92d5a 100644 --- a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs +++ b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs @@ -62,7 +62,7 @@ namespace ILSpy.BamlDecompiler var typeDefinition = result.TypeName.HasValue ? typeSystem.MainModule.GetTypeDefinition(result.TypeName.Value.TopLevelTypeName) : null; if (typeDefinition != null) { - fileName = WholeProjectDecompiler.CleanUpPath(typeDefinition.ReflectionName) + ".xaml"; + fileName = WholeProjectDecompiler.CleanUpPath(typeDefinition.ReflectionName + ".xaml"); var partialTypeInfo = new PartialTypeInfo(typeDefinition); foreach (var member in result.GeneratedMembers) { diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index 7a0374895..33e10b24d 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -71,7 +71,7 @@ namespace ICSharpCode.ILSpy return; } SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb"; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName, ".pdb"); dlg.Filter = Resources.PortablePDBPdbAllFiles; dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) diff --git a/ILSpy/Commands/SelectPdbContextMenuEntry.cs b/ILSpy/Commands/SelectPdbContextMenuEntry.cs index 9c63a8b8c..5d3d95351 100644 --- a/ILSpy/Commands/SelectPdbContextMenuEntry.cs +++ b/ILSpy/Commands/SelectPdbContextMenuEntry.cs @@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy if (assembly == null) return; OpenFileDialog dlg = new OpenFileDialog(); - dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb"; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName, ".pdb"); dlg.Filter = Resources.PortablePDBPdbAllFiles; dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 3b9ea493c..dc0d5edae 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -1091,7 +1091,7 @@ namespace ICSharpCode.ILSpy.TextView SaveFileDialog dlg = new SaveFileDialog(); dlg.DefaultExt = language.FileExtension; dlg.Filter = language.Name + "|*" + language.FileExtension + Properties.Resources.AllFiles; - dlg.FileName = WholeProjectDecompiler.CleanUpFileName(treeNodes.First().ToString()) + language.FileExtension; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(treeNodes.First().ToString(), language.FileExtension); if (dlg.ShowDialog() == true) { SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), dlg.FileName); diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index a34882e4d..e2efa8d1a 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -475,7 +475,7 @@ namespace ICSharpCode.ILSpy.TreeNodes if (string.IsNullOrEmpty(language.ProjectFileExtension)) return false; SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = WholeProjectDecompiler.CleanUpFileName(LoadedAssembly.ShortName) + language.ProjectFileExtension; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(LoadedAssembly.ShortName, language.ProjectFileExtension); dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*"; if (dlg.ShowDialog() == true) { From 3c70224441aade1a8409c3de176b789761ceb7bf Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Feb 2025 11:59:21 +0100 Subject: [PATCH 19/49] Fix detection and display of explicitly implemented operators. --- .../Output/CSharpAmbienceTests.cs | 25 +++++++++- .../CSharp/OutputVisitor/CSharpAmbience.cs | 50 +++++++++++++++++-- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 4 -- .../Implementation/MetadataMethod.cs | 17 +++++++ 4 files changed, 86 insertions(+), 10 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs b/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs index 63eec4cd3..446dbde02 100644 --- a/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs +++ b/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs @@ -283,7 +283,17 @@ namespace ICSharpCode.Decompiler.Tests.Output [TestCase(ILSpyMainTreeViewMemberFlags, "this[int] : int")] public void Indexer(ConversionFlags flags, string expectedOutput) { - var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer).Single(); + var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer && !p.IsExplicitInterfaceImplementation).Single(); + ambience.ConversionFlags = flags; + + Assert.That(ambience.ConvertSymbol(prop), Is.EqualTo(expectedOutput)); + } + + [TestCase(StandardConversionFlags, "int Interface.this[int index] { get; }")] + [TestCase(ILSpyMainTreeViewMemberFlags, "Interface.this[int] : int")] + public void ExplicitIndexer(ConversionFlags flags, string expectedOutput) + { + var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer && p.IsExplicitInterfaceImplementation).Single(); ambience.ConversionFlags = flags; Assert.That(ambience.ConvertSymbol(prop), Is.EqualTo(expectedOutput)); @@ -323,7 +333,12 @@ namespace ICSharpCode.Decompiler.Tests.Output readonly struct ReadonlyStruct { } readonly ref struct ReadonlyRefStruct { } - class Program + interface Interface + { + int this[int x] { get; } + } + + class Program : Interface { int test; const int TEST2 = 2; @@ -336,6 +351,12 @@ namespace ICSharpCode.Decompiler.Tests.Output } } + int Interface.this[int index] { + get { + return index; + } + } + public event EventHandler ProgramChanged; public event EventHandler SomeEvent { diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index beac7989c..aa290cbd8 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -279,9 +279,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor ConvertType(member.DeclaringType, writer, formattingPolicy); writer.WriteToken(Roles.Dot, "."); } + IType explicitInterfaceType = GetExplicitInterfaceType(member); + string name = member.Name; + if (explicitInterfaceType != null) + { + name = name.Substring(name.LastIndexOf('.') + 1); + } switch (member.SymbolKind) { case SymbolKind.Indexer: + if (explicitInterfaceType != null) + { + ConvertType(explicitInterfaceType, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); + } writer.WriteKeyword(Roles.Identifier, "this"); break; case SymbolKind.Constructor: @@ -292,11 +303,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy); break; case SymbolKind.Operator: - switch (member.Name) + switch (name) { case "op_Implicit": writer.WriteKeyword(OperatorDeclaration.ImplicitRole, "implicit"); writer.Space(); + if (explicitInterfaceType != null) + { + ConvertType(explicitInterfaceType, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); + } writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); writer.Space(); ConvertType(member.ReturnType, writer, formattingPolicy); @@ -305,9 +321,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor case "op_CheckedExplicit": writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit"); writer.Space(); + if (explicitInterfaceType != null) + { + ConvertType(explicitInterfaceType, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); + } writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); writer.Space(); - if (member.Name == "op_CheckedExplicit") + if (name == "op_CheckedExplicit") { writer.WriteToken(OperatorDeclaration.CheckedKeywordRole, "checked"); writer.Space(); @@ -315,9 +336,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor ConvertType(member.ReturnType, writer, formattingPolicy); break; default: + if (explicitInterfaceType != null) + { + ConvertType(explicitInterfaceType, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); + } writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); writer.Space(); - var operatorType = OperatorDeclaration.GetOperatorType(member.Name); + var operatorType = OperatorDeclaration.GetOperatorType(name); if (operatorType.HasValue && !((ConversionFlags & ConversionFlags.SupportOperatorChecked) == 0 && OperatorDeclaration.IsChecked(operatorType.Value))) { if (OperatorDeclaration.IsChecked(operatorType.Value)) @@ -335,7 +361,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor } break; default: - writer.WriteIdentifier(Identifier.Create(member.Name)); + if (explicitInterfaceType != null) + { + ConvertType(explicitInterfaceType, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); + } + writer.WriteIdentifier(Identifier.Create(name)); break; } WriteTypeParameters(node, writer, formattingPolicy); @@ -407,6 +438,17 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor astType.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); } + IType GetExplicitInterfaceType(IMember member) + { + if (member.IsExplicitInterfaceImplementation) + { + var baseMember = member.ExplicitlyImplementedInterfaceMembers.FirstOrDefault(); + if (baseMember != null) + return baseMember.DeclaringType; + } + return null; + } + public string ConvertConstantValue(object constantValue) { return TextWriterTokenWriter.PrintPrimitiveValue(constantValue); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index c9ae2a66d..63b697d75 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1784,10 +1784,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax case SymbolKind.Event: return ConvertEvent((IEvent)entity); case SymbolKind.Method: - if (entity.Name.Contains(".op_")) - { - goto case SymbolKind.Operator; - } return ConvertMethod((IMethod)entity); case SymbolKind.Operator: return ConvertOperator((IMethod)entity); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index 83946c9fa..7173d1850 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -96,6 +96,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation this.symbolKind = SymbolKind.Destructor; } } + else if ((attributes & MethodAttributes.Static) != 0 && typeParameters.Length == 0) + { + // Operators that are explicit interface implementations are not marked + // with MethodAttributes.SpecialName or MethodAttributes.RTSpecialName + string name = this.Name; + int index = name.LastIndexOf('.'); + if (index > 0) + { + name = name.Substring(index + 1); + + if (name.StartsWith("op_", StringComparison.Ordinal) + && CSharp.Syntax.OperatorDeclaration.GetOperatorType(name) != null) + { + this.symbolKind = SymbolKind.Operator; + } + } + } this.IsExtensionMethod = (attributes & MethodAttributes.Static) == MethodAttributes.Static && (module.TypeSystemOptions & TypeSystemOptions.ExtensionMethods) == TypeSystemOptions.ExtensionMethods && def.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.Extension); From d31ed51569b4d6c2d2bbb0526881594b1a4b3b7d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Feb 2025 15:50:50 +0100 Subject: [PATCH 20/49] Fix a few bugs in AnalyzerEntityTreeNode --- ILSpy/Analyzers/AnalyzerEntityTreeNode.cs | 6 ++++- .../TreeNodes/AnalyzedModuleTreeNode.cs | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs index 630bde183..0e1895482 100644 --- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs @@ -46,10 +46,14 @@ namespace ICSharpCode.ILSpy.Analyzers MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(this.Member.ParentModule?.MetadataFile, this.Member.MetadataToken))); } - public override object ToolTip => Member.ParentModule?.MetadataFile?.FileName; + public override object ToolTip => Member?.ParentModule?.MetadataFile?.FileName; public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies) { + if (Member == null) + { + return true; + } foreach (LoadedAssembly asm in removedAssemblies) { if (this.Member.ParentModule.MetadataFile == asm.GetMetadataFileOrNull()) diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs index a85096147..157cb4ae4 100644 --- a/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs +++ b/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs @@ -17,9 +17,12 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Windows; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpyX; +using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; namespace ICSharpCode.ILSpy.Analyzers.TreeNodes @@ -38,6 +41,8 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes public override object Text => analyzedModule.AssemblyName; + public override object ToolTip => analyzedModule.MetadataFile?.FileName; + protected override void LoadChildren() { foreach (var lazy in Analyzers) @@ -62,5 +67,24 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes } public override IEntity Member => null; + + public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies) + { + if (analyzedModule == null) + { + return true; + } + foreach (LoadedAssembly asm in removedAssemblies) + { + if (this.analyzedModule.MetadataFile == asm.GetMetadataFileOrNull()) + return false; // remove this node + } + this.Children.RemoveAll( + delegate (SharpTreeNode n) { + AnalyzerTreeNode an = n as AnalyzerTreeNode; + return an == null || !an.HandleAssemblyListChanged(removedAssemblies, addedAssemblies); + }); + return true; + } } } From 53522c45f887c4dc8f490815f40a724e4e9e1559 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Feb 2025 15:52:11 +0100 Subject: [PATCH 21/49] #3401: fix missing file extension if name contains ':' --- .../ProjectDecompiler/WholeProjectDecompiler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index ec3e955b2..2299b6fec 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -636,13 +636,9 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// </summary> static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName, bool treatAsPath) { - // Remove anything that could be confused with a rooted path. - int pos = text.IndexOf(':'); - if (pos > 0) - text = text.Substring(0, pos); - text = text.Trim(); string extension = null; int currentSegmentLength = 0; + // Extract extension from the end of the name, if valid if (treatAsFileName) { // Check if input is a file name, i.e., has a valid extension @@ -668,6 +664,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler } } } + // Remove anything that could be confused with a rooted path. + int pos = text.IndexOf(':'); + if (pos > 0) + text = text.Substring(0, pos); + text = text.Trim(); // Remove generics pos = text.IndexOf('`'); if (pos > 0) From b0d6fa2276cb56597de4449b70b189c5f08087fe Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Thu, 23 Jan 2025 19:49:39 -0800 Subject: [PATCH 22/49] Add support for array initialization based on RuntimeHelpers.CreateSpan<T> --- .../TestCases/Pretty/InitializerTests.cs | 13 +++++ .../IL/Transforms/ExpressionTransforms.cs | 6 ++ .../Transforms/TransformArrayInitializers.cs | 55 +++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index e02f23ff9..c6ac577e3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -805,6 +805,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests array[0] = 1; Console.WriteLine(array.Length); } + +#if !NET40 && CS70 + public static ReadOnlySpan<byte> ReadOnlySpanInitializer_ByteArray() + { + return new byte[3] { 1, 2, 3 }; + } + + public static ReadOnlySpan<int> ReadOnlySpanInitializer_Int32Array() + { + return new int[3] { 1, 2, 3 }; + } +#endif + #endregion #region Object initializers diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index ed086d68f..1b8d09264 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -289,6 +289,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms replacement.AcceptVisitor(this); return; } + if (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization(inst, context, out var replacement2)) + { + context.Step("TransformRuntimeHelpersCreateSpanInitialization: single-dim", inst); + inst.ReplaceWith(replacement2); + return; + } base.VisitCall(inst); TransformAssignment.HandleCompoundAssign(inst, context); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 7ddec2e56..b9cb283e8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -143,6 +143,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } + internal static bool TransformRuntimeHelpersCreateSpanInitialization(Call inst, StatementTransformContext context, out ILInstruction replacement) + { + replacement = null; + if (!context.Settings.ArrayInitializers) + return false; + if (MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field)) + { + if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) + { + var valuesList = new List<ILInstruction>(); + var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); + var elementTypeSize = elementType.GetSize(); + if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0) + return false; + + var size = initialValue.Length / elementTypeSize; + if (context.Settings.Utf8StringLiterals && + elementType.IsKnownType(KnownTypeCode.Byte) && + DecodeUTF8String(initialValue, size, out string text)) + { + replacement = new LdStrUtf8(text); + return true; + } + if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) + { + var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); + replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); + return true; + } + } + } + return false; + } + private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text) { if (size > blob.RemainingBytes) @@ -187,6 +221,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } + static bool MatchRuntimeHelpersCreateSpan(Call inst, StatementTransformContext context, out IType elementType, out FieldDefinition field) + { + field = default; + elementType = null; + IType type = inst.Method.DeclaringType; + if (type.Namespace != "System.Runtime.CompilerServices" || type.Name != "RuntimeHelpers" || type.TypeParameterCount != 0) + return false; + if (inst.Arguments.Count != 1) + return false; + IMethod method = inst.Method; + if (method.Name != "CreateSpan" || method.TypeArguments.Count != 1) + return false; + elementType = method.TypeArguments[0]; + if (!inst.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdMemberToken(out var member)) + return false; + if (member.MetadataToken.IsNil) + return false; + field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken); + return true; + } + bool DoTransformMultiDim(ILFunction function, Block body, int pos) { if (pos >= body.Instructions.Count - 2) From bd0fd8db1f8d8d517e3f42ae9dd8f53a3b703c2d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sat, 22 Feb 2025 09:08:12 +0100 Subject: [PATCH 23/49] Small refactorings: - extract common code - reduce nesting --- .../Transforms/TransformArrayInitializers.cs | 90 ++++++++----------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index b9cb283e8..65d9d05e0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -119,28 +119,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms replacement = null; if (!context.Settings.ArrayInitializers) return false; - if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size)) - { - if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) - { - var valuesList = new List<ILInstruction>(); - var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); - if (context.Settings.Utf8StringLiterals && - elementType.IsKnownType(KnownTypeCode.Byte) && - DecodeUTF8String(initialValue, size, out string text)) - { - replacement = new LdStrUtf8(text); - return true; - } - if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) - { - var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); - replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); - return true; - } - } - } - return false; + if (!MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size)) + return false; + if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) + return false; + var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); + replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size); + return replacement != null; } internal static bool TransformRuntimeHelpersCreateSpanInitialization(Call inst, StatementTransformContext context, out ILInstruction replacement) @@ -148,33 +133,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms replacement = null; if (!context.Settings.ArrayInitializers) return false; - if (MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field)) - { - if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) - { - var valuesList = new List<ILInstruction>(); - var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); - var elementTypeSize = elementType.GetSize(); - if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0) - return false; + if (!MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field)) + return false; + if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) + return false; + var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); + var elementTypeSize = elementType.GetSize(); + if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0) + return false; + var size = initialValue.Length / elementTypeSize; + replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size); + return replacement != null; + } - var size = initialValue.Length / elementTypeSize; - if (context.Settings.Utf8StringLiterals && - elementType.IsKnownType(KnownTypeCode.Byte) && - DecodeUTF8String(initialValue, size, out string text)) - { - replacement = new LdStrUtf8(text); - return true; - } - if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) - { - var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); - replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); - return true; - } - } + private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, BlobReader initialValue, int size) + { + if (context.Settings.Utf8StringLiterals && elementType.IsKnownType(KnownTypeCode.Byte) + && DecodeUTF8String(initialValue, size, out string text)) + { + return new LdStrUtf8(text); } - return false; + var valuesList = new List<ILInstruction>(); + if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) + { + var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); + return BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); + } + + return null; } private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text) @@ -225,15 +211,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms { field = default; elementType = null; - IType type = inst.Method.DeclaringType; - if (type.Namespace != "System.Runtime.CompilerServices" || type.Name != "RuntimeHelpers" || type.TypeParameterCount != 0) + if (!IsRuntimeHelpers(inst.Method.DeclaringType)) return false; if (inst.Arguments.Count != 1) return false; - IMethod method = inst.Method; - if (method.Name != "CreateSpan" || method.TypeArguments.Count != 1) + if (inst.Method is not { Name: "CreateSpan", TypeArguments: [var type] }) return false; - elementType = method.TypeArguments[0]; + elementType = type; if (!inst.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdMemberToken(out var member)) return false; if (member.MetadataToken.IsNil) @@ -412,7 +396,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return field != null; } - static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices" }; + static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices", TypeParameterCount: 0 }; unsafe bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove) { From 38cdf6d50afda104fe87b4e34389a3ba9626a246 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sat, 22 Feb 2025 11:09:47 +0100 Subject: [PATCH 24/49] Avoid transforming sub pattern of cpblk stackalloc initializer --- .../IL/Transforms/TransformArrayInitializers.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 65d9d05e0..7a45440b0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -137,6 +137,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) return false; + if (IsSubPatternOfCpblkInitializer(inst)) + return false; var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); var elementTypeSize = elementType.GetSize(); if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0) @@ -146,6 +148,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms return replacement != null; } + private static bool IsSubPatternOfCpblkInitializer(Call inst) + { + if (inst.Parent is not AddressOf { Parent: Call { Parent: Cpblk cpblk } get_Item }) + return false; + return MatchGetStaticFieldAddress(get_Item, out _); + } + private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, BlobReader initialValue, int size) { if (context.Settings.Utf8StringLiterals && elementType.IsKnownType(KnownTypeCode.Byte) @@ -373,7 +382,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - bool MatchGetStaticFieldAddress(ILInstruction input, out IField field) + static bool MatchGetStaticFieldAddress(ILInstruction input, out IField field) { if (input.MatchLdsFlda(out field)) return true; From 3a13d5a69897b68c5a342e4cfb69fbe9819448b4 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Sat, 22 Feb 2025 02:00:01 -0800 Subject: [PATCH 25/49] Allow explicit null termination character --- .../TestCases/Pretty/InitializerTests.cs | 1 + .../IL/Transforms/TransformArrayInitializers.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index c6ac577e3..bad7cbccd 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -447,6 +447,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests #endif #if CS110 && !NET40 public static ReadOnlySpan<byte> UTF8Literal => "Hello, world!"u8; + public static ReadOnlySpan<byte> UTF8LiteralWithNullTerminator => "Hello, world!\0"u8; #endif #endregion diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 7a45440b0..45110c12d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -182,9 +182,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms for (int i = 0; i < size; i++) { byte val = blob.CurrentPointer[i]; - // If the string has control characters, it's probably binary data and not a string. - if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t')) + if (val == 0 && i == size - 1 && size > 1) { + // Allow explicit null-termination character. + } + else if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t')) + { + // If the string has control characters, it's probably binary data and not a string. text = null; return false; } From d8825bc1a6799ced72935ca5604ad0c5a8d66885 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 14:56:59 +0100 Subject: [PATCH 26/49] Bump ossf/scorecard-action from 2.4.0 to 2.4.1 (#3410) Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.0 to 2.4.1. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/v2.4.0...v2.4.1) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index f6f0279ad..74b6daa1e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@v2.4.0 # https://github.com/marketplace/actions/ossf-scorecard-action + uses: ossf/scorecard-action@v2.4.1 # https://github.com/marketplace/actions/ossf-scorecard-action with: results_file: results.sarif results_format: sarif From 5fab18f3a05400ba8ee52fed92d93d3897c01154 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sun, 2 Mar 2025 23:45:12 +0100 Subject: [PATCH 27/49] Fix #3414: Apply latest session settings before saving upon closing the main window --- ILSpy/MainWindow.xaml.cs | 3 +++ ILSpy/Search/SearchPaneModel.cs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index e78c4f7f8..298e5ca75 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -102,6 +102,9 @@ namespace ICSharpCode.ILSpy MessageBus.Send(this, new ApplySessionSettingsEventArgs(sessionSettings)); sessionSettings.WindowBounds = this.RestoreBounds; + // store window state in settings only if it's not minimized + if (this.WindowState != WindowState.Minimized) + sessionSettings.WindowState = this.WindowState; sessionSettings.DockLayout.Serialize(new(DockManager)); snapshot.Save(); diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs index a9f5444fc..f27acf105 100644 --- a/ILSpy/Search/SearchPaneModel.cs +++ b/ILSpy/Search/SearchPaneModel.cs @@ -54,6 +54,12 @@ namespace ICSharpCode.ILSpy.Search SearchTerm = e.SearchTerm; Show(); }; + MessageBus<ApplySessionSettingsEventArgs>.Subscribers += ApplySessionSettings; + } + + private void ApplySessionSettings(object sender, ApplySessionSettingsEventArgs e) + { + e.SessionSettings.SelectedSearchMode = SessionSettings.SelectedSearchMode; } public SearchModeModel[] SearchModes { get; } = [ From 3e5e81e16c34ef232d93f7083e66fa60ff1aa1cd Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Mon, 3 Mar 2025 19:20:47 +0100 Subject: [PATCH 28/49] Roslyn 4.13 --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f9cbde7a7..62dea4ad9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,8 +15,8 @@ <PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" /> <PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" /> <PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" /> - <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" /> - <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.12.0" /> + <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" /> + <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.13.0" /> <PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" /> <PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" /> <PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" /> From 5d1219950fb5bf9708ec898a79db042e3c4f40f9 Mon Sep 17 00:00:00 2001 From: jwfx <jwfx@bitmx.net> Date: Tue, 4 Mar 2025 21:46:29 +0100 Subject: [PATCH 29/49] Fix exception when writing resx files without adding any resources --- ICSharpCode.Decompiler/Util/ResXResourceWriter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs index 80d712353..09fa9f865 100644 --- a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs +++ b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs @@ -312,6 +312,9 @@ namespace ICSharpCode.Decompiler.Util public void Generate() { + if (writer == null) + InitWriter(); + if (written) throw new InvalidOperationException("The resource is already generated."); From f0f95efa050c844dcce2f573618f6e064742ddd7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sat, 8 Mar 2025 19:55:19 +0100 Subject: [PATCH 30/49] Fix StateRangeAnalysis to handle changes in yield return codegen in Roslyn 4.13. --- .../IL/ControlFlow/StateRangeAnalysis.cs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs index 4d67e9bb4..2314532da 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs @@ -21,11 +21,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; @@ -225,6 +222,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow goto default; } } + case StObj stobj when mode == StateRangeAnalysisMode.IteratorDispose: + { + if (stobj.MatchStFld(out var target, out var field, out var value) && target.MatchLdThis()) + { + if (field.MemberDefinition == stateField && value.MatchLdcI4(-2)) + { + // Roslyn 4.13 sets the state field in Dispose() to mark the iterator as disposed, + // don't consider this user code. + return stateRange; + } + else if (value.MatchDefaultOrNullOrZero()) + { + // Roslyn 4.13 clears any local hoisted local variables in Dispose(), + // don't consider this user code. + return stateRange; + } + else + { + goto default; + } + } + else + { + goto default; + } + } default: // User code - abort analysis if (mode == StateRangeAnalysisMode.IteratorDispose && !(inst is Leave l && l.IsLeavingFunction)) From cbe8dd43d75a6b17c1b377632f0586c96d37a8ef Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Sun, 9 Mar 2025 21:00:36 -0700 Subject: [PATCH 31/49] Fix null check in MatchLegacySwitchOnStringWithDict Updated the condition for `nullValueCaseBlock` to ensure it is not null and not equal to `defaultBlock`. --- ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index 8302038b8..9f7752951 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -668,7 +668,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!FixCasesWithoutValue(sections, stringValues)) return false; // switch contains case null: - if (nullValueCaseBlock != defaultBlock) + if (nullValueCaseBlock != null && nullValueCaseBlock != defaultBlock) { if (!AddNullSection(sections, stringValues, nullValueCaseBlock)) { From a1b3b14b0b5967bd4e9588790012829690a2eb79 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Sat, 15 Mar 2025 00:51:53 -0700 Subject: [PATCH 32/49] Add test --- .../ICSharpCode.Decompiler.Tests.csproj | 2 + .../ILPrettyTestRunner.cs | 6 + .../TestCases/ILPretty/Issue3421.cs | 28 +++++ .../TestCases/ILPretty/Issue3421.il | 116 ++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 3ff44e2bb..fc8734e1a 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -104,6 +104,7 @@ <None Include="TestCases\ILPretty\Issue1922.il" /> <None Include="TestCases\ILPretty\Issue1918.il" /> <None Include="TestCases\ILPretty\Issue2104.il" /> + <None Include="TestCases\ILPretty\Issue3421.il" /> <None Include="TestCases\ILPretty\WeirdEnums.il" /> <None Include="TestCases\ILPretty\ConstantBlobs.il" /> <None Include="TestCases\ILPretty\CS1xSwitch_Debug.il" /> @@ -129,6 +130,7 @@ <Compile Include="Output\InsertParenthesesVisitorTests.cs" /> <Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" /> <Compile Include="TestAssemblyResolver.cs" /> + <Compile Include="TestCases\ILPretty\Issue3421.cs" /> <Compile Include="TestCases\ILPretty\MonoFixed.cs" /> <Compile Include="TestCases\Pretty\Comparisons.cs" /> <None Include="TestCases\VBPretty\VBAutomaticEvents.vb" /> diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 04c94d089..5a86d4e81 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -207,6 +207,12 @@ namespace ICSharpCode.Decompiler.Tests await Run(); } + [Test] + public async Task Issue3421() + { + await Run(); + } + [Test] public async Task Issue2260SwitchString() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs new file mode 100644 index 000000000..d09c674fd --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs @@ -0,0 +1,28 @@ +internal class Issue3421 +{ + private string name; + private object value; + + public virtual void SetValue(object value) + { + if (name == null) + { + return; + } + switch (name) + { + case "##Name##": + return; + case "##Value##": + this.value = value; + return; + case "##InnerText##": + this.value = value.ToString(); + return; + } + if (this.value == null) + { + this.value = ""; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il new file mode 100644 index 000000000..541c8094b --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il @@ -0,0 +1,116 @@ +#define CORE_ASSEMBLY "System.Runtime" + +.assembly extern CORE_ASSEMBLY +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 4:0:0:0 +} + +.class private auto ansi beforefieldinit Issue3421 + extends [CORE_ASSEMBLY]System.Object +{ + // Fields + .field private string name + .field private object 'value' + .field private static class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> '<>f__switch$map1D' + .custom instance void [CORE_ASSEMBLY]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + + // Methods + .method public hidebysig virtual + instance void SetValue ( + object 'value' + ) cil managed + { + // Method begins at RVA 0x2050 + // Header size: 12 + // Code size: 180 (0xb4) + .maxstack 27 + .locals init ( + [0] string, + [1] class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>, + [2] int32 + ) + + IL_0000: ldarg.0 + IL_0001: ldfld string Issue3421::name + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: brfalse IL_0093 + + IL_000d: ldsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D' + IL_0012: brtrue IL_0048 + + IL_0017: ldc.i4.3 + IL_0018: newobj instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::.ctor(int32) + IL_001d: stloc.1 + IL_001e: ldloc.1 + IL_001f: ldstr "##Name##" + IL_0024: ldc.i4.0 + IL_0025: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1) + IL_002a: ldloc.1 + IL_002b: ldstr "##Value##" + IL_0030: ldc.i4.1 + IL_0031: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1) + IL_0036: ldloc.1 + IL_0037: ldstr "##InnerText##" + IL_003c: ldc.i4.2 + IL_003d: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1) + IL_0042: ldloc.1 + IL_0043: stsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D' + + IL_0048: ldsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D' + IL_004d: ldloc.0 + IL_004e: ldloca.s 2 + IL_0050: callvirt instance bool class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::TryGetValue(!0, !1&) + IL_0055: brfalse IL_0098 + + IL_005a: ldloc.2 + IL_005b: switch (IL_0071, IL_0076, IL_0082) + + IL_006c: br IL_0098 + + IL_0071: br IL_00b3 + + IL_0076: ldarg.0 + IL_0077: ldarg.1 + IL_0078: stfld object Issue3421::'value' + IL_007d: br IL_00b3 + + IL_0082: ldarg.0 + IL_0083: ldarg.1 + IL_0084: callvirt instance string [CORE_ASSEMBLY]System.Object::ToString() + IL_0089: stfld object Issue3421::'value' + IL_008e: br IL_00b3 + + IL_0093: br IL_00b3 + + IL_0098: ldarg.0 + IL_0099: ldfld object Issue3421::'value' + IL_009e: brtrue IL_00ae + + IL_00a3: ldarg.0 + IL_00a4: ldstr "" + IL_00a9: stfld object Issue3421::'value' + + IL_00ae: br IL_00b3 + + IL_00b3: ret + } // end of method Issue3421::SetValue + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2110 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [CORE_ASSEMBLY]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Issue3421::.ctor + +} // end of class Issue3421 From e4000c8a5c74fabe6f1cf5051a1cfdd9e99b466f Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Sat, 15 Mar 2025 01:29:58 -0700 Subject: [PATCH 33/49] Enhance null handling in switch transformations - Updated `Issue3421.cs`. - Updated `MatchLegacySwitchOnStringWithDict` to check for `leaveContainer` and handle null sections accordingly. - Introduced an overload for `AddNullSection` to accept `ILInstruction` as the body, improving flexibility. - Modified existing `AddNullSection` to utilize the new overload, allowing for varied body types in `SwitchSection`. --- .../TestCases/ILPretty/Issue3421.cs | 6 ++---- .../IL/Transforms/SwitchOnStringTransform.cs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs index d09c674fd..1acc63fac 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs @@ -5,10 +5,6 @@ internal class Issue3421 public virtual void SetValue(object value) { - if (name == null) - { - return; - } switch (name) { case "##Name##": @@ -19,6 +15,8 @@ internal class Issue3421 case "##InnerText##": this.value = value.ToString(); return; + case null: + return; } if (this.value == null) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index 9f7752951..f39ff3c86 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -675,6 +675,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } } + else if (leaveContainer != null && !defaultBlockJump.MatchLeave(leaveContainer)) + { + if (!AddNullSection(sections, stringValues, (Leave)exitBlockJump)) + { + return false; + } + } context.Step(nameof(MatchLegacySwitchOnStringWithDict), instructions[i]); bool keepAssignmentBefore = false; if (switchValueVar.LoadCount > 2 || switchValue == null) @@ -741,6 +748,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms } bool AddNullSection(List<SwitchSection> sections, List<(string Value, int Index)> stringValues, Block nullValueCaseBlock) + { + return AddNullSection(sections, stringValues, new Branch(nullValueCaseBlock)); + } + + bool AddNullSection(List<SwitchSection> sections, List<(string Value, int Index)> stringValues, ILInstruction body) { var label = new LongSet(stringValues.Max(item => item.Index) + 1); var possibleConflicts = sections.Where(sec => sec.Labels.Overlaps(label)).ToArray(); @@ -753,7 +765,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms possibleConflicts[0].Labels = possibleConflicts[0].Labels.ExceptWith(label); } stringValues.Add((null, (int)label.Values.First())); - sections.Add(new SwitchSection() { Labels = label, Body = new Branch(nullValueCaseBlock) }); + sections.Add(new SwitchSection() { Labels = label, Body = body }); return true; } From 012f5812e952d2c8f8ce1a14083934bd21a56cbe Mon Sep 17 00:00:00 2001 From: ElektroKill <elektrokilldev@protonmail.com> Date: Sat, 15 Mar 2025 20:28:07 +0100 Subject: [PATCH 34/49] Fix #3423 --- .../TestCases/Pretty/EnumTests.cs | 21 ++++++++++++++++++- .../CSharp/TranslatedExpression.cs | 7 +++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs index f66be670d..b7cd02c5c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -28,6 +28,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Item2 } + public enum NoZero + { + Item1 = 1, + Item2 + } + public enum OutOfOrderMembers { Item1 = 1, @@ -135,5 +141,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { return AttributeTargets.Class | AttributeTargets.Delegate; } + + public void EnumInNotZeroCheck(SimpleEnum value, NoZero value2) + { + if (value != SimpleEnum.Item1) + { + Console.WriteLine(); + } + + if (value2 != (NoZero)0) + { + Console.WriteLine(); + } + } } } diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index ad864b5ac..e40a975e0 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2014 Daniel Grunwald +// Copyright (c) 2014 Daniel Grunwald // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -733,9 +733,8 @@ namespace ICSharpCode.Decompiler.CSharp } else { - var zero = new PrimitiveExpression(0) - .WithoutILInstruction() - .WithRR(new ConstantResolveResult(expressionBuilder.compilation.FindType(KnownTypeCode.Int32), 0)); + var zero = expressionBuilder + .ConvertConstantValue(new ConstantResolveResult(Type, 0), allowImplicitConversion: true); var op = negate ? BinaryOperatorType.Equality : BinaryOperatorType.InEquality; return new BinaryOperatorExpression(Expression, op, zero.Expression) .WithoutILInstruction() From 49942382d17fdeb17cdbd42ea9222599fddf1e47 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Wed, 19 Mar 2025 22:52:27 +0100 Subject: [PATCH 35/49] Fix #3432: Do not include delegate construction use-sites in DetermineCaptureAndDeclarationScopes. --- .../IL/Transforms/LocalFunctionDecompiler.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index f00fc48b0..523d6e68c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -658,6 +658,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms switch (useSite) { case CallInstruction call: + if (DelegateConstruction.MatchDelegateConstruction(useSite, out _, out _, out _)) + { + // if this is a delegate construction, skip the use-site, because the capture scope + // was already determined when analyzing "this". + break; + } int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1; for (int i = call.Arguments.Count - 1; i >= firstArgumentIndex; i--) { From 16600fa6339db0f148a58f2b3f6dfbe0f465d380 Mon Sep 17 00:00:00 2001 From: ElektroKill <elektrokilldev@protonmail.com> Date: Thu, 20 Mar 2025 18:24:26 +0100 Subject: [PATCH 36/49] Addressed feedback --- .../TestCases/Pretty/EnumTests.cs | 2 +- .../CSharp/TranslatedExpression.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs index b7cd02c5c..2b26f7d3a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs @@ -149,7 +149,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine(); } - if (value2 != (NoZero)0) + if (value2 != 0) { Console.WriteLine(); } diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index e40a975e0..66d0eab36 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -731,11 +731,23 @@ namespace ICSharpCode.Decompiler.CSharp .WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual, this.ResolveResult, nullRef.ResolveResult)); } - else + else if (Type.Kind == TypeKind.Enum && Type.GetDefinition() is { } typeDef && + typeDef.Fields.Any(f => f.GetConstantValue() is { } val && (ulong)CSharpPrimitiveCast.Cast(TypeCode.UInt64, val, false) == 0L)) { var zero = expressionBuilder .ConvertConstantValue(new ConstantResolveResult(Type, 0), allowImplicitConversion: true); var op = negate ? BinaryOperatorType.Equality : BinaryOperatorType.InEquality; + return new BinaryOperatorExpression(Expression, op, zero.Expression) + .WithoutILInstruction() + .WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual, + this.ResolveResult, zero.ResolveResult)); + } + else + { + var zero = new PrimitiveExpression(0) + .WithoutILInstruction() + .WithRR(new ConstantResolveResult(expressionBuilder.compilation.FindType(KnownTypeCode.Int32), 0)); + var op = negate ? BinaryOperatorType.Equality : BinaryOperatorType.InEquality; return new BinaryOperatorExpression(Expression, op, zero.Expression) .WithoutILInstruction() .WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual, From 73e9771d3ca82048eca5b78f1f6743b66681a352 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Mar 2025 00:10:00 +0100 Subject: [PATCH 37/49] Fix #2269: LocalFunctionDecompiler misplaces nested local functions in ctors --- .../IL/Transforms/LocalFunctionDecompiler.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 523d6e68c..c377238c1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -142,9 +142,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms { DetermineCaptureAndDeclarationScope(info, useSite); - if (context.Function.Method.IsConstructor && localFunction.DeclarationScope == null) + if (context.Function.Method.IsConstructor) { - localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); + if (localFunction.DeclarationScope == null) + { + localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); + } + else + { + localFunction.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(useSite, localFunction.DeclarationScope); + if (localFunction.DeclarationScope == null) + { + localFunction.DeclarationScope = (BlockContainer)context.Function.Body; + } + } } } From a599aae54d19557e388971b4a56ff0444aeefed0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 4 Oct 2019 10:35:35 +0200 Subject: [PATCH 38/49] #1572: Do not generate variable names that match C# keywords. --- .../TestCases/Pretty/CompoundAssignmentTest.cs | 4 ++-- .../TestCases/Pretty/RefLocalsAndReturns.cs | 16 ++++++++-------- .../CSharp/OutputVisitor/CSharpOutputVisitor.cs | 9 ++++++--- .../IL/Transforms/AssignVariableNames.cs | 6 ++++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index b0cb624b6..b133ac078 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -4943,8 +4943,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void Issue1779(int value) { - CustomStruct2 @struct = GetStruct(); - @struct.IntProp += value; + CustomStruct2 customStruct = GetStruct(); + customStruct.IntProp += value; } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs index 7a65ff10a..192503f48 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs @@ -182,11 +182,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty GetRef<ReadOnlyStruct>().Method(); // call on a copy, not the original ref: - NormalStruct @ref = GetRef<NormalStruct>(); - @ref.Method(); + NormalStruct normalStruct = GetRef<NormalStruct>(); + normalStruct.Method(); - ReadOnlyStruct ref2 = GetRef<ReadOnlyStruct>(); - ref2.Method(); + ReadOnlyStruct readOnlyStruct = GetRef<ReadOnlyStruct>(); + readOnlyStruct.Method(); } public void CallOnReadOnlyRefReturn() @@ -293,13 +293,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void RefReassignment(ref NormalStruct s) { - ref NormalStruct @ref = ref GetRef<NormalStruct>(); - RefReassignment(ref @ref); + ref NormalStruct reference = ref GetRef<NormalStruct>(); + RefReassignment(ref reference); if (s.GetHashCode() == 0) { - @ref = ref GetRef<NormalStruct>(); + reference = ref GetRef<NormalStruct>(); } - RefReassignment(ref @ref.GetHashCode() == 4 ? ref @ref : ref s); + RefReassignment(ref reference.GetHashCode() == 4 ? ref reference : ref s); } public static void Main(string[] args) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index a462ccb9f..a4f448d2d 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -426,8 +426,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor /// <summary> /// Determines whether the specified identifier is a keyword in the given context. + /// If <paramref name="context"/> is <see langword="null" /> all keywords are treated as unconditional. /// </summary> - public static bool IsKeyword(string identifier, AstNode context) + public static bool IsKeyword(string identifier, AstNode context = null) { // only 2-10 char lower-case identifiers can be keywords if (identifier.Length > maxKeywordLength || identifier.Length < 2 || identifier[0] < 'a') @@ -440,10 +441,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor } if (queryKeywords.Contains(identifier)) { - return context.Ancestors.Any(ancestor => ancestor is QueryExpression); + return context == null || context.Ancestors.Any(ancestor => ancestor is QueryExpression); } if (identifier == "await") { + if (context == null) + return true; foreach (AstNode ancestor in context.Ancestors) { // with lambdas/anonymous methods, diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index bee806143..c3ce39109 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -657,8 +657,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (name.Length == 0) return "obj"; - else - return char.ToLower(name[0]) + name.Substring(1); + string lowerCaseName = char.ToLower(name[0]) + name.Substring(1); + if (CSharp.OutputVisitor.CSharpOutputVisitor.IsKeyword(lowerCaseName)) + return null; + return lowerCaseName; } internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context) From 03aecf047dcdb55ab7a8009ad37f0c1c77b6a8d0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sun, 2 Mar 2025 11:30:57 +0100 Subject: [PATCH 39/49] Add VariableScope and rework AssignVariableNames step to support renaming parameters of nested ILFunctions in the future. --- .../TestCases/Pretty/DelegateConstruction.cs | 6 +- .../CSharp/ExpressionBuilder.cs | 7 +- .../CSharp/StatementBuilder.cs | 10 + .../IL/ControlFlow/AsyncAwaitDecompiler.cs | 1 + .../IL/ControlFlow/YieldReturnDecompiler.cs | 1 + ICSharpCode.Decompiler/IL/ILReader.cs | 4 + .../IL/Instructions/ILFunction.cs | 5 + .../IL/Transforms/AssignVariableNames.cs | 684 +++++++++++------- .../Util/CollectionExtensions.cs | 2 +- 9 files changed, 438 insertions(+), 282 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index c87ca9a7d..fd76c9e23 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -377,10 +377,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public static void NameConflict2(int j) { List<Action<int>> list = new List<Action<int>>(); - for (int k = 0; k < 10; k++) + for (int i = 0; i < 10; i++) { - list.Add(delegate (int i) { - Console.WriteLine(i); + list.Add(delegate (int k) { + Console.WriteLine(k); }); } } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 63737a2d4..e95134ed4 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2547,6 +2547,11 @@ namespace ICSharpCode.Decompiler.CSharp foreach (var parameter in parameters) { var pd = astBuilder.ConvertParameter(parameter); + if (variables.TryGetValue(i, out var v)) + { + pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type)); + pd.Name = v.Name; + } if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList()) { // needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) @@ -2554,8 +2559,6 @@ namespace ICSharpCode.Decompiler.CSharp } if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) pd.Type = null; - if (variables.TryGetValue(i, out var v)) - pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type)); yield return pd; i++; } diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 094a51355..d3f2f5069 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -1389,6 +1389,16 @@ namespace ICSharpCode.Decompiler.CSharp var astBuilder = exprBuilder.astBuilder; var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod); + var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); + + foreach (var (i, p) in method.Parameters.WithIndex()) + { + if (variables.TryGetValue(i, out var v)) + { + p.Name = v.Name; + } + } + if (function.Method.HasBody) { var nestedBuilder = new StatementBuilder( diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index 259367b2d..ff37d9a1e 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -1165,6 +1165,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow function.MoveNextMethod = moveNextFunction.Method; function.SequencePointCandidates = moveNextFunction.SequencePointCandidates; function.CodeSize = moveNextFunction.CodeSize; + function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength; function.IsIterator = IsAsyncEnumerator; moveNextFunction.Variables.Clear(); moveNextFunction.ReleaseRef(); diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index dbea7d4cf..c5ef712bc 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -656,6 +656,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow function.MoveNextMethod = moveNextFunction.Method; function.SequencePointCandidates = moveNextFunction.SequencePointCandidates; function.CodeSize = moveNextFunction.CodeSize; + function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength; // Copy-propagate temporaries holding a copy of 'this'. // This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables. diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 2ec9da40a..226218e12 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -342,7 +342,10 @@ namespace ICSharpCode.Decompiler.IL if (index < 0) ilVar.Name = "this"; else if (string.IsNullOrWhiteSpace(name)) + { ilVar.Name = "P_" + index; + ilVar.HasGeneratedName = true; + } else ilVar.Name = name; return ilVar; @@ -706,6 +709,7 @@ namespace ICSharpCode.Decompiler.IL var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind); function.Variables.AddRange(parameterVariables); function.Variables.AddRange(localVariables); + function.LocalVariableSignatureLength = localVariables.Length; Debug.Assert(stackVariables != null); function.Variables.AddRange(stackVariables); function.Variables.AddRange(variableByExceptionHandler.Values); diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index bbf0e6441..eb81ccbce 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -67,6 +67,11 @@ namespace ICSharpCode.Decompiler.IL /// </summary> public readonly ILVariableCollection Variables; + /// <summary> + /// Gets + /// </summary> + public int LocalVariableSignatureLength; + /// <summary> /// Gets the scope in which the local function is declared. /// Returns null, if this is not a local function. diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index c3ce39109..0d1acd261 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -25,6 +26,7 @@ using System.Reflection.Metadata; using Humanizer.Inflections; +using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; @@ -32,7 +34,7 @@ using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { - public class AssignVariableNames : IILTransform + public class AssignVariableNames : ILVisitor<AssignVariableNames.VariableScope, Unit>, IILTransform { static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> { { "System.Boolean", "flag" }, @@ -53,52 +55,83 @@ namespace ICSharpCode.Decompiler.IL.Transforms }; ILTransformContext context; - List<string> currentLowerCaseTypeOrMemberNames; - Dictionary<string, int> reservedVariableNames; - HashSet<ILVariable> loopCounters; const char maxLoopVariableName = 'n'; - int numDisplayClassLocals; - public void Run(ILFunction function, ILTransformContext context) + public class VariableScope { - this.context = context; + readonly ILTransformContext context; + readonly VariableScope parentScope; + readonly ILFunction function; + readonly Dictionary<MethodDefinitionHandle, string> localFunctions = new(); + readonly Dictionary<ILVariable, string> variableMapping = new(ILVariableEqualityComparer.Instance); + readonly string[] assignedLocalSignatureIndices; + + IImmutableSet<string> currentLowerCaseTypeOrMemberNames; + Dictionary<string, int> reservedVariableNames; + HashSet<ILVariable> loopCounters; + int numDisplayClassLocals; + + public VariableScope(ILFunction function, ILTransformContext context, VariableScope parentScope = null) + { + this.function = function; + this.context = context; + this.parentScope = parentScope; + + numDisplayClassLocals = 0; + assignedLocalSignatureIndices = new string[function.LocalVariableSignatureLength]; + reservedVariableNames = new Dictionary<string, int>(); + + // find all loop counters in the current function + loopCounters = new HashSet<ILVariable>(); + foreach (var inst in TreeTraversal.PreOrder((ILInstruction)function, i => i.Children)) + { + if (inst is ILFunction && inst != function) + break; + if (inst is BlockContainer { Kind: ContainerKind.For, Blocks: [.., var incrementBlock] }) + { + foreach (var i in incrementBlock.Instructions) + { + if (HighLevelLoopTransform.MatchIncrement(i, out var variable)) + loopCounters.Add(variable); + } + } + } - reservedVariableNames = new Dictionary<string, int>(); - currentLowerCaseTypeOrMemberNames = new List<string>(); - var currentLowerCaseMemberNames = CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition); - foreach (var name in currentLowerCaseMemberNames) - currentLowerCaseTypeOrMemberNames.Add(name); - var currentLowerCaseTypeNames = CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition); - foreach (var name in currentLowerCaseTypeNames) - { - currentLowerCaseTypeOrMemberNames.Add(name); - AddExistingName(reservedVariableNames, name); - } - loopCounters = CollectLoopCounters(function); - foreach (var f in function.Descendants.OfType<ILFunction>()) - { - if (f.Method != null) + // if this is the root scope, we also collect all lower-case type and member names + // and fixed parameter names to avoid conflicts when naming local variables. + if (parentScope == null) { - if (IsSetOrEventAccessor(f.Method) && f.Method.Parameters.Count > 0) + var currentLowerCaseTypeOrMemberNames = new HashSet<string>(StringComparer.Ordinal); + foreach (var name in CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition)) + currentLowerCaseTypeOrMemberNames.Add(name); + foreach (var name in CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition)) { - for (int i = 0; i < f.Method.Parameters.Count - 1; i++) + currentLowerCaseTypeOrMemberNames.Add(name); + AddExistingName(reservedVariableNames, name); + } + this.currentLowerCaseTypeOrMemberNames = currentLowerCaseTypeOrMemberNames.ToImmutableHashSet(); + + // handle implicit parameters of set or event accessors + if (function.Method != null && IsSetOrEventAccessor(function.Method) && function.Parameters.Count > 0) + { + for (int i = 0; i < function.Method.Parameters.Count - 1; i++) { - AddExistingName(reservedVariableNames, f.Method.Parameters[i].Name); + AddExistingName(reservedVariableNames, function.Method.Parameters[i].Name); } - var lastParameter = f.Method.Parameters.Last(); - switch (f.Method.AccessorOwner) + var lastParameter = function.Method.Parameters.Last(); + switch (function.Method.AccessorOwner) { case IProperty prop: - if (f.Method.AccessorKind == MethodSemanticsAttributes.Setter) + if (function.Method.AccessorKind == MethodSemanticsAttributes.Setter) { if (prop.Parameters.Any(p => p.Name == "value")) { - f.Warnings.Add("Parameter named \"value\" already present in property signature!"); + function.Warnings.Add("Parameter named \"value\" already present in property signature!"); break; } - var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f + var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function && v.Kind == VariableKind.Parameter - && v.Index == f.Method.Parameters.Count - 1); + && v.Index == function.Method.Parameters.Count - 1); if (variableForLastParameter == null) { AddExistingName(reservedVariableNames, lastParameter.Name); @@ -114,11 +147,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms } break; case IEvent ev: - if (f.Method.AccessorKind != MethodSemanticsAttributes.Raiser) + if (function.Method.AccessorKind != MethodSemanticsAttributes.Raiser) { - var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f + var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function && v.Kind == VariableKind.Parameter - && v.Index == f.Method.Parameters.Count - 1); + && v.Index == function.Method.Parameters.Count - 1); if (variableForLastParameter == null) { AddExistingName(reservedVariableNames, lastParameter.Name); @@ -140,183 +173,394 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { - foreach (var p in f.Method.Parameters) - AddExistingName(reservedVariableNames, p.Name); + var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0).ToDictionary(v => v.Index); + foreach (var (i, p) in function.Parameters.WithIndex()) + { + string name = p.Name; + if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList) + { + // needs to be consistent with logic in ILReader.CreateILVarable + name = "P_" + i; + } + if (variables.TryGetValue(i, out var v)) + variableMapping[v] = name; + AddExistingName(reservedVariableNames, name); + } + } + + static bool IsSetOrEventAccessor(IMethod method) + { + switch (method.AccessorKind) + { + case MethodSemanticsAttributes.Setter: + case MethodSemanticsAttributes.Adder: + case MethodSemanticsAttributes.Remover: + return true; + default: + return false; + } } } else { - foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter)) - AddExistingName(reservedVariableNames, p.Name); + this.currentLowerCaseTypeOrMemberNames = parentScope.currentLowerCaseTypeOrMemberNames; + var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); + + foreach (var (i, p) in function.Parameters.WithIndex()) + { + if (function.Kind is ILFunctionKind.Delegate or ILFunctionKind.ExpressionTree + && CSharpDecompiler.IsTransparentIdentifier(p.Name)) + { + AddExistingName(reservedVariableNames, p.Name); + if (variables.TryGetValue(i, out var v)) + variableMapping[v] = p.Name; + } + if (!parentScope.IsReservedVariableName(p.Name, out _)) + { + AddExistingName(reservedVariableNames, p.Name); + if (variables.TryGetValue(i, out var v)) + variableMapping[v] = p.Name; + } + } } } - numDisplayClassLocals = 0; - PerformAssignment(function); - } - static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type) - { - foreach (var item in type.GetMembers(m => IsLowerCase(m.Name))) - yield return item.Name; - } + public void Add(MethodDefinitionHandle localFunction, string name) + { + this.localFunctions[localFunction] = name; + } - static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type) - { - var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace); - foreach (var item in ns.Types) + public string TryGetExistingName(MethodDefinitionHandle localFunction) { - if (IsLowerCase(item.Name)) - yield return item.Name; + if (localFunctions.TryGetValue(localFunction, out var name)) + return name; + return parentScope?.TryGetExistingName(localFunction); } - } - static bool IsLowerCase(string name) - { - return name.Length > 0 && char.ToLower(name[0]) == name[0]; - } + public string TryGetExistingName(ILVariable v) + { + if (variableMapping.TryGetValue(v, out var name)) + return name; + return parentScope?.TryGetExistingName(v); + } - bool IsSetOrEventAccessor(IMethod method) - { - switch (method.AccessorKind) + public string TryGetExistingName(ILFunction function, int index) { - case MethodSemanticsAttributes.Setter: - case MethodSemanticsAttributes.Adder: - case MethodSemanticsAttributes.Remover: + if (this.function == function) + { + return this.assignedLocalSignatureIndices[index]; + } + else + { + return parentScope?.TryGetExistingName(function, index); + } + } + + public void AssignNameToLocalSignatureIndex(ILFunction function, int index, string name) + { + var scope = this; + while (scope != null && scope.function != function) + scope = scope.parentScope; + Debug.Assert(scope != null); + scope.assignedLocalSignatureIndices[index] = name; + } + + public bool IsReservedVariableName(string name, out int index) + { + if (reservedVariableNames.TryGetValue(name, out index)) return true; - default: - return false; + return parentScope?.IsReservedVariableName(name, out index) ?? false; } - } - void PerformAssignment(ILFunction function) - { - var localFunctionMapping = new Dictionary<MethodDefinitionHandle, string>(); - var variableMapping = new Dictionary<ILVariable, string>(ILVariableEqualityComparer.Instance); - var assignedLocalSignatureIndices = new Dictionary<(ILFunction, int), string>(); + public void ReserveVariableName(string name, int index = 1) + { + reservedVariableNames[name] = index; + } - foreach (var inst in function.Descendants) + public string NextDisplayClassLocal() { - if (inst is ILFunction { Kind: ILFunctionKind.LocalFunction } localFunction) + return parentScope?.NextDisplayClassLocal() ?? "CS$<>8__locals" + (numDisplayClassLocals++); + } + + public bool IsLoopCounter(ILVariable v) + { + return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true); + } + + public string AssignName(ILVariable v) + { + // variable has no valid name + string newName = v.Name; + if (v.HasGeneratedName || !IsValidName(newName)) + { + // don't use the name from the debug symbols if it looks like a generated name + // generate a new one based on how the variable is used + newName = GenerateNameForVariable(v); + } + // use the existing name and update index appended to future conflicts + string nameWithoutNumber = SplitName(newName, out int newIndex); + if (IsReservedVariableName(nameWithoutNumber, out int lastUsedIndex)) { - // assign names to local functions - if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName)) - newName = null; - if (newName == null) + if (v.Type.IsKnownType(KnownTypeCode.Int32) && IsLoopCounter(v)) { - string nameWithoutNumber = "f"; - if (!reservedVariableNames.TryGetValue(nameWithoutNumber, out int currentIndex)) + // special case for loop counters, + // we don't want them to be named i, i2, ..., but i, j, ... + newName = GenerateNameForVariable(v); + nameWithoutNumber = newName; + newIndex = 1; + } + } + if (IsReservedVariableName(nameWithoutNumber, out lastUsedIndex)) + { + // name without number was already used + if (newIndex > lastUsedIndex) + { + // new index is larger than last, so we can use it + } + else + { + // new index is smaller or equal, so we use the next value + newIndex = lastUsedIndex + 1; + } + // resolve conflicts by appending the index to the new name: + newName = nameWithoutNumber + newIndex.ToString(); + } + // update the last used index + ReserveVariableName(nameWithoutNumber, newIndex); + variableMapping.Add(v, newName); + return newName; + } + + string GenerateNameForVariable(ILVariable variable) + { + string proposedName = null; + if (variable.Type.IsKnownType(KnownTypeCode.Int32)) + { + // test whether the variable might be a loop counter + if (loopCounters.Contains(variable)) + { + // For loop variables, use i,j,k,l,m,n + for (char c = 'i'; c <= maxLoopVariableName; c++) + { + if (!IsReservedVariableName(c.ToString(), out _)) + { + proposedName = c.ToString(); + break; + } + } + } + } + // The ComponentResourceManager inside InitializeComponent must be named "resources", + // otherwise the WinForms designer won't load the Form. + if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager") + { + proposedName = "resources"; + } + if (string.IsNullOrEmpty(proposedName)) + { + var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>() + .Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null) + .Where(arg => !string.IsNullOrWhiteSpace(arg)) + .Except(currentLowerCaseTypeOrMemberNames).ToList(); + if (proposedNameForAddress.Count > 0) + { + proposedName = proposedNameForAddress[0]; + } + } + if (string.IsNullOrEmpty(proposedName)) + { + var proposedNameForStores = new HashSet<string>(); + foreach (var store in variable.StoreInstructions) + { + if (store is StLoc stloc) { - currentIndex = 1; + var name = GetNameFromInstruction(stloc.Value); + if (!currentLowerCaseTypeOrMemberNames.Contains(name)) + proposedNameForStores.Add(name); } - int count = Math.Max(1, currentIndex) + 1; - reservedVariableNames[nameWithoutNumber] = count; - if (count > 1) + else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot) { - newName = nameWithoutNumber + count.ToString(); + var name = GetNameFromInstruction(match.TestedOperand); + if (!currentLowerCaseTypeOrMemberNames.Contains(name)) + proposedNameForStores.Add(name); } - else + else if (store is PinnedRegion pinnedRegion) { - newName = nameWithoutNumber; + var name = GetNameFromInstruction(pinnedRegion.Init); + if (!currentLowerCaseTypeOrMemberNames.Contains(name)) + proposedNameForStores.Add(name); } } - localFunction.Name = newName; - localFunction.ReducedMethod.Name = newName; - localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName; - } - else if (inst is IInstructionWithVariableOperand i) - { - var v = i.Variable; - // if there is already a valid name for the variable slot, just use it - if (variableMapping.TryGetValue(v, out string name)) + if (proposedNameForStores.Count == 1) { - v.Name = name; - continue; + proposedName = proposedNameForStores.Single(); } - switch (v.Kind) + } + if (string.IsNullOrEmpty(proposedName)) + { + var proposedNameForLoads = variable.LoadInstructions + .Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex)) + .Except(currentLowerCaseTypeOrMemberNames).ToList(); + if (proposedNameForLoads.Count == 1) { - case VariableKind.Parameter: - // Parameter names are handled in ILReader.CreateILVariable - // and CSharpDecompiler.FixParameterNames - break; - case VariableKind.InitializerTarget: // keep generated names - AddExistingName(reservedVariableNames, v.Name); - break; - case VariableKind.DisplayClassLocal: - v.Name = "CS$<>8__locals" + (numDisplayClassLocals++); - break; - case VariableKind.Local when v.Index != null: - if (assignedLocalSignatureIndices.TryGetValue((v.Function, v.Index.Value), out name)) - { - // make sure all local ILVariables that refer to the same slot in the locals signature - // are assigned the same name. - v.Name = name; - } - else - { - v.Name = AssignName(v, variableMapping); - // Remember the newly assigned name: - assignedLocalSignatureIndices.Add((v.Function, v.Index.Value), v.Name); - } - break; - default: - v.Name = AssignName(v, variableMapping); - break; + proposedName = proposedNameForLoads[0]; } } - else if (inst is (Call or LdFtn) and IInstructionWithMethodOperand m) + if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot) { - // update references to local functions - if (m.Method is LocalFunctionMethod lf - && localFunctionMapping.TryGetValue((MethodDefinitionHandle)lf.MetadataToken, out var name)) + var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>() + .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context))) + .Except(currentLowerCaseTypeOrMemberNames).ToList(); + if (proposedNameForStoresFromNewObj.Count == 1) { - lf.Name = name; + proposedName = proposedNameForStoresFromNewObj[0]; } } + if (string.IsNullOrEmpty(proposedName)) + { + proposedName = GetNameByType(variable.Type); + } + + // for generated names remove number-suffixes + return SplitName(proposedName, out _); } } - string AssignName(ILVariable v, Dictionary<ILVariable, string> variableMapping) + public void Run(ILFunction function, ILTransformContext context) + { + this.context = context; + function.AcceptVisitor(this, null); + } + + protected override Unit Default(ILInstruction inst, VariableScope context) { - // variable has no valid name - string newName = v.Name; - if (v.HasGeneratedName || !IsValidName(newName)) + foreach (var child in inst.Children) { - // don't use the name from the debug symbols if it looks like a generated name - // generate a new one based on how the variable is used - newName = GenerateNameForVariable(v); + child.AcceptVisitor(this, context); } - // use the existing name and update index appended to future conflicts - string nameWithoutNumber = SplitName(newName, out int newIndex); - if (reservedVariableNames.TryGetValue(nameWithoutNumber, out int lastUsedIndex)) + + if (inst is not IInstructionWithVariableOperand { Variable: var v }) + return default; + + // if there is already a valid name for the variable slot, just use it + string name = context.TryGetExistingName(v); + if (!string.IsNullOrEmpty(name)) { - if (v.Type.IsKnownType(KnownTypeCode.Int32) && loopCounters.Contains(v)) - { - // special case for loop counters, - // we don't want them to be named i, i2, ..., but i, j, ... - newName = GenerateNameForVariable(v); - nameWithoutNumber = newName; - newIndex = 1; - } + v.Name = name; + return default; } - if (reservedVariableNames.TryGetValue(nameWithoutNumber, out lastUsedIndex)) + + switch (v.Kind) { - // name without number was already used - if (newIndex > lastUsedIndex) - { - // new index is larger than last, so we can use it - } - else + case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction: + // Parameter names of top-level functions are handled in ILReader.CreateILVariable + // and CSharpDecompiler.FixParameterNames + break; + case VariableKind.InitializerTarget: // keep generated names + case VariableKind.NamedArgument: + context.ReserveVariableName(v.Name); + break; + case VariableKind.DisplayClassLocal: + v.Name = context.NextDisplayClassLocal(); + break; + case VariableKind.Local when v.Index != null: + name = context.TryGetExistingName(v.Function, v.Index.Value); + if (name != null) + { + // make sure all local ILVariables that refer to the same slot in the locals signature + // are assigned the same name. + v.Name = name; + } + else + { + v.Name = context.AssignName(v); + context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name); + } + break; + default: + v.Name = context.AssignName(v); + break; + } + + return default; + } + + protected internal override Unit VisitILFunction(ILFunction function, VariableScope context) + { + if (function.Kind == ILFunctionKind.LocalFunction) + { + // assign names to local functions + if (!LocalFunctionDecompiler.ParseLocalFunctionName(function.Name, out _, out var newName) || !IsValidName(newName)) + newName = null; + if (newName == null) { - // new index is smaller or equal, so we use the next value - newIndex = lastUsedIndex + 1; + string nameWithoutNumber = "f"; + if (!context.IsReservedVariableName(nameWithoutNumber, out int currentIndex)) + { + currentIndex = 1; + } + int count = Math.Max(1, currentIndex) + 1; + context.ReserveVariableName(nameWithoutNumber, count); + if (count > 1) + { + newName = nameWithoutNumber + count.ToString(); + } + else + { + newName = nameWithoutNumber; + } } - // resolve conflicts by appending the index to the new name: - newName = nameWithoutNumber + newIndex.ToString(); + function.Name = newName; + function.ReducedMethod.Name = newName; + context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName); + } + + return base.VisitILFunction(function, new VariableScope(function, this.context, context)); + } + + protected internal override Unit VisitCall(Call inst, VariableScope context) + { + if (inst.Method is LocalFunctionMethod m) + { + string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken); + if (!string.IsNullOrEmpty(name)) + m.Name = name; } - // update the last used index - reservedVariableNames[nameWithoutNumber] = newIndex; - variableMapping.Add(v, newName); - return newName; + + return base.VisitCall(inst, context); + } + + protected internal override Unit VisitLdFtn(LdFtn inst, VariableScope context) + { + if (inst.Method is LocalFunctionMethod m) + { + string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken); + if (!string.IsNullOrEmpty(name)) + m.Name = name; + } + + return base.VisitLdFtn(inst, context); + } + + static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type) + { + foreach (var item in type.GetMembers(m => IsLowerCase(m.Name))) + yield return item.Name; + } + + static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type) + { + var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace); + foreach (var item in ns.Types) + { + if (IsLowerCase(item.Name)) + yield return item.Name; + } + } + + static bool IsLowerCase(string name) + { + return name.Length > 0 && char.ToLower(name[0]) == name[0]; } /// <remarks> @@ -351,118 +595,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - HashSet<ILVariable> CollectLoopCounters(ILFunction function) - { - var loopCounters = new HashSet<ILVariable>(); - - foreach (BlockContainer possibleLoop in function.Descendants.OfType<BlockContainer>()) - { - if (possibleLoop.Kind != ContainerKind.For) - continue; - foreach (var inst in possibleLoop.Blocks.Last().Instructions) - { - if (HighLevelLoopTransform.MatchIncrement(inst, out var variable)) - loopCounters.Add(variable); - } - } - - return loopCounters; - } - - string GenerateNameForVariable(ILVariable variable) - { - string proposedName = null; - if (variable.Type.IsKnownType(KnownTypeCode.Int32)) - { - // test whether the variable might be a loop counter - if (loopCounters.Contains(variable)) - { - // For loop variables, use i,j,k,l,m,n - for (char c = 'i'; c <= maxLoopVariableName; c++) - { - if (!reservedVariableNames.ContainsKey(c.ToString())) - { - proposedName = c.ToString(); - break; - } - } - } - } - // The ComponentResourceManager inside InitializeComponent must be named "resources", - // otherwise the WinForms designer won't load the Form. - if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager") - { - proposedName = "resources"; - } - if (string.IsNullOrEmpty(proposedName)) - { - var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>() - .Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null) - .Where(arg => !string.IsNullOrWhiteSpace(arg)) - .Except(currentLowerCaseTypeOrMemberNames).ToList(); - if (proposedNameForAddress.Count > 0) - { - proposedName = proposedNameForAddress[0]; - } - } - if (string.IsNullOrEmpty(proposedName)) - { - var proposedNameForStores = new HashSet<string>(); - foreach (var store in variable.StoreInstructions) - { - if (store is StLoc stloc) - { - var name = GetNameFromInstruction(stloc.Value); - if (!currentLowerCaseTypeOrMemberNames.Contains(name)) - proposedNameForStores.Add(name); - } - else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot) - { - var name = GetNameFromInstruction(match.TestedOperand); - if (!currentLowerCaseTypeOrMemberNames.Contains(name)) - proposedNameForStores.Add(name); - } - else if (store is PinnedRegion pinnedRegion) - { - var name = GetNameFromInstruction(pinnedRegion.Init); - if (!currentLowerCaseTypeOrMemberNames.Contains(name)) - proposedNameForStores.Add(name); - } - } - if (proposedNameForStores.Count == 1) - { - proposedName = proposedNameForStores.Single(); - } - } - if (string.IsNullOrEmpty(proposedName)) - { - var proposedNameForLoads = variable.LoadInstructions - .Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex)) - .Except(currentLowerCaseTypeOrMemberNames).ToList(); - if (proposedNameForLoads.Count == 1) - { - proposedName = proposedNameForLoads[0]; - } - } - if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot) - { - var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>() - .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context))) - .Except(currentLowerCaseTypeOrMemberNames).ToList(); - if (proposedNameForStoresFromNewObj.Count == 1) - { - proposedName = proposedNameForStoresFromNewObj[0]; - } - } - if (string.IsNullOrEmpty(proposedName)) - { - proposedName = GetNameByType(variable.Type); - } - - // for generated names remove number-suffixes - return SplitName(proposedName, out _); - } - static string GetNameFromInstruction(ILInstruction inst) { switch (inst) @@ -663,7 +795,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return lowerCaseName; } - internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context) + static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context) { if (!variableType.IsKnownType(KnownTypeCode.Object)) return variableType; diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 0d5febe9c..a88ac4376 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -225,7 +225,7 @@ namespace ICSharpCode.Decompiler.Util yield return func(index++, element); } - public static IEnumerable<(int, T)> WithIndex<T>(this ICollection<T> source) + public static IEnumerable<(int, T)> WithIndex<T>(this IEnumerable<T> source) { int index = 0; foreach (var item in source) From 0481c7d1ee05f7b6008cc00564cc603c099ce3de Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sun, 2 Mar 2025 11:33:28 +0100 Subject: [PATCH 40/49] Improve LINQ decompiler to support combining lambda parameter names if they syntactically refer to the same range variable --- .../Transforms/CombineQueryExpressions.cs | 32 ++++++++----------- .../Transforms/IntroduceQueryExpressions.cs | 21 ++++++++++-- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs index ad92d1a27..55d6dec35 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs @@ -16,12 +16,12 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Collections.Generic; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.CSharp.Transforms { @@ -54,12 +54,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms next = child.NextSibling; CombineQueries(child, fromOrLetIdentifiers); } - QueryExpression query = node as QueryExpression; - if (query != null) + if (node is QueryExpression query) { QueryFromClause fromClause = (QueryFromClause)query.Clauses.First(); - QueryExpression innerQuery = fromClause.Expression as QueryExpression; - if (innerQuery != null) + if (fromClause.Expression is QueryExpression innerQuery) { if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery, fromOrLetIdentifiers)) { @@ -165,21 +163,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { RemoveTransparentIdentifierReferences(child, fromOrLetIdentifiers); } - MemberReferenceExpression mre = node as MemberReferenceExpression; - if (mre != null) + if (node is MemberReferenceExpression mre && mre.Target is IdentifierExpression ident + && CSharpDecompiler.IsTransparentIdentifier(ident.Identifier)) { - IdentifierExpression ident = mre.Target as IdentifierExpression; - if (ident != null && CSharpDecompiler.IsTransparentIdentifier(ident.Identifier)) - { - IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName); - mre.TypeArguments.MoveTo(newIdent.TypeArguments); - newIdent.CopyAnnotationsFrom(mre); - newIdent.RemoveAnnotations<Semantics.MemberResolveResult>(); // remove the reference to the property of the anonymous type - if (fromOrLetIdentifiers.TryGetValue(mre.MemberName, out var annotation)) - newIdent.AddAnnotation(annotation); - mre.ReplaceWith(newIdent); - return; - } + IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName); + mre.TypeArguments.MoveTo(newIdent.TypeArguments); + newIdent.CopyAnnotationsFrom(mre); + newIdent.RemoveAnnotations<Semantics.MemberResolveResult>(); // remove the reference to the property of the anonymous type + if (fromOrLetIdentifiers.TryGetValue(mre.MemberName, out var annotation)) + newIdent.AddAnnotation(annotation); + mre.ReplaceWith(newIdent); + return; } } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs index 8e5e55368..1590b2ab3 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs @@ -17,10 +17,10 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.IL; namespace ICSharpCode.Decompiler.CSharp.Transforms { @@ -54,13 +54,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms while (IsDegenerateQuery(innerQuery)) { QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First(); - if (fromClause.Identifier != innerFromClause.Identifier) - break; + ILVariable innerVariable = innerFromClause.Annotation<ILVariableResolveResult>()?.Variable; + ILVariable rangeVariable = fromClause.Annotation<ILVariableResolveResult>()?.Variable; // Replace the fromClause with all clauses from the inner query fromClause.Remove(); QueryClause insertionPos = null; foreach (var clause in innerQuery.Clauses) { + CombineRangeVariables(clause, innerVariable, rangeVariable); query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); } fromClause = innerFromClause; @@ -69,6 +70,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } + private void CombineRangeVariables(QueryClause clause, ILVariable oldVariable, ILVariable newVariable) + { + foreach (var identifier in clause.DescendantNodes().OfType<Identifier>()) + { + var variable = identifier.Parent.Annotation<ILVariableResolveResult>()?.Variable; + if (variable == oldVariable) + { + identifier.Parent.RemoveAnnotations<ILVariableResolveResult>(); + identifier.Parent.AddAnnotation(new ILVariableResolveResult(newVariable)); + identifier.ReplaceWith(Identifier.Create(newVariable.Name)); + } + } + } + bool IsDegenerateQuery(QueryExpression query) { if (query == null) From ffcd468d22f492bbdcc4c4045a2a74df4a7bf308 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sun, 2 Mar 2025 21:41:20 +0100 Subject: [PATCH 41/49] Fix #1572: parameters of lambdas and local functions are renamed, if there are with names from outer scopes collisions. --- .../PrettyTestRunner.cs | 56 ++++----- .../TestCases/ILPretty/GuessAccessors.cs | 4 +- .../TestCases/Pretty/DelegateConstruction.cs | 33 ++++++ .../TestCases/VBPretty/Async.cs | 12 +- .../CSharp/CSharpDecompiler.cs | 2 +- .../CSharp/ExpressionBuilder.cs | 2 +- .../IL/Transforms/AssignVariableNames.cs | 112 +++++++++++------- 7 files changed, 143 insertions(+), 78 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index f4ff7c914..cfe0c420b 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -233,22 +233,21 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { - NullPropagation = false, + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + settings.NullPropagation = false; // legacy csc generates a dead store in debug builds - RemoveDeadStores = (cscOptions == CompilerOptions.None), - FileScopedNamespaces = false, + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.FileScopedNamespaces = false; }); } [Test] public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { // legacy csc generates a dead store in debug builds - RemoveDeadStores = (cscOptions == CompilerOptions.None), - SwitchExpressions = false, - FileScopedNamespaces = false, + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.SwitchExpressions = false; }); } @@ -267,7 +266,10 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + settings.QueryExpressions = false; + settings.NullableReferenceTypes = false; + }); } [Test] @@ -293,9 +295,9 @@ namespace ICSharpCode.Decompiler.Tests { await RunForLibrary( cscOptions: cscOptions, - decompilerSettings: new DecompilerSettings { - UseEnhancedUsing = false, - FileScopedNamespaces = false, + configureDecompiler: settings => { + settings.UseEnhancedUsing = false; + settings.FileScopedNamespaces = false; } ); } @@ -327,11 +329,11 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - DecompilerSettings settings = Tester.GetSettings(cscOptions); - // legacy csc generates a dead store in debug builds - settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); - settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false; - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + // legacy csc generates a dead store in debug builds + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false; + }); } [Test] @@ -440,9 +442,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - var settings = Tester.GetSettings(cscOptions); - settings.UseDebugSymbols = false; - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseDebugSymbols = false); } [Test] @@ -474,7 +474,7 @@ namespace ICSharpCode.Decompiler.Tests { await RunForLibrary( cscOptions: cscOptions, - decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false, FileScopedNamespaces = false } + configureDecompiler: settings => { settings.UseEnhancedUsing = false; } ); } @@ -499,7 +499,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings()); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.FileScopedNamespaces = true); } [Test] @@ -601,7 +601,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6)); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.SetLanguageVersion(CSharp.LanguageVersion.CSharp6)); } [Test] @@ -712,12 +712,12 @@ namespace ICSharpCode.Decompiler.Tests await RunForLibrary(cscOptions: cscOptions); } - async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) + async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null) { - await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings); + await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler); } - async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) + async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null) { var csFile = Path.Combine(TestCasePath, testName + ".cs"); var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe"); @@ -739,7 +739,9 @@ namespace ICSharpCode.Decompiler.Tests } // 2. Decompile - var decompiled = await Tester.DecompileCSharp(exeFile, decompilerSettings ?? Tester.GetSettings(cscOptions)).ConfigureAwait(false); + var settings = Tester.GetSettings(cscOptions); + configureDecompiler?.Invoke(settings); + var decompiled = await Tester.DecompileCSharp(exeFile, settings).ConfigureAwait(false); // 3. Compile CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(cscOptions).Append("EXPECTED_OUTPUT").ToArray()); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs index c9e0f56e0..423ad4119 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs @@ -15,8 +15,8 @@ namespace ClassLibrary1 //IL_0007: Expected O, but got Unknown UnknownClass val = new UnknownClass(); int? unknownProperty = val.UnknownProperty; - int? num2 = (val.UnknownProperty = unknownProperty.GetValueOrDefault()); - int? num3 = num2; + int? num = (val.UnknownProperty = unknownProperty.GetValueOrDefault()); + int? num3 = num; List<object> list = new List<object> { val[unknownProperty.Value] ?? "", val.NotProperty, diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index fd76c9e23..d7566dced 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -19,15 +19,33 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; #if CS100 using System.Threading.Tasks; + #endif namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction { public static class DelegateConstruction { + internal class Dummy + { + public int baz; + + public List<Dummy> more; + } + + [CompilerGenerated] + internal class Helper + { + internal bool HelpMe(Dummy dum) + { + return true; + } + } + private class InstanceTests { public struct SomeData @@ -643,6 +661,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction { del(x); } + + public void Issue1572(DelegateConstruction.Dummy dum) + { +#if EXPECTED_OUTPUT + DelegateConstruction.Helper CS_0024_003C_003E8__locals0 = new DelegateConstruction.Helper(); + DelegateConstruction.Dummy dummy = dum.more.Where((DelegateConstruction.Dummy dummy2) => true).Where((DelegateConstruction.Dummy dummy2) => true).FirstOrDefault(); + Console.WriteLine(); + dummy.baz++; +#else + DelegateConstruction.Helper h = new DelegateConstruction.Helper(); + DelegateConstruction.Dummy localDummy = dum.more.Where(h.HelpMe).Where(h.HelpMe).FirstOrDefault(); + Console.WriteLine(); + localDummy.baz++; +#endif + } } [AttributeUsage(AttributeTargets.All)] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs index 6cb502dd0..7c2c76025 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs @@ -40,9 +40,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty public async void AwaitDefaultYieldAwaitable() { #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) - YieldAwaitable yieldAwaitable = default(YieldAwaitable); - YieldAwaitable yieldAwaitable2 = yieldAwaitable; - await yieldAwaitable2; + YieldAwaitable yieldAwaitable2 = default(YieldAwaitable); + YieldAwaitable yieldAwaitable = yieldAwaitable2; + await yieldAwaitable; #else await default(YieldAwaitable); #endif @@ -51,9 +51,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty public async void AwaitDefaultHopToThreadPool() { #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) - HopToThreadPoolAwaitable hopToThreadPoolAwaitable = default(HopToThreadPoolAwaitable); - HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = hopToThreadPoolAwaitable; - await hopToThreadPoolAwaitable2; + HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = default(HopToThreadPoolAwaitable); + HopToThreadPoolAwaitable hopToThreadPoolAwaitable = hopToThreadPoolAwaitable2; + await hopToThreadPoolAwaitable; #else await default(HopToThreadPoolAwaitable); #endif diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 86f6ed66e..b9559dba1 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1282,7 +1282,7 @@ namespace ICSharpCode.Decompiler.CSharp { if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList()) { - // needs to be consistent with logic in ILReader.CreateILVarable + // needs to be consistent with logic in ILReader.CreateILVariable parameter.Name = "P_" + i; } i++; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index e95134ed4..90df1890a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2554,7 +2554,7 @@ namespace ICSharpCode.Decompiler.CSharp } if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList()) { - // needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) + // needs to be consistent with logic in ILReader.CreateILVariable pd.Name = "P_" + i; } if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 0d1acd261..24f7079fc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -179,7 +179,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms string name = p.Name; if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList) { - // needs to be consistent with logic in ILReader.CreateILVarable + // needs to be consistent with logic in ILReader.CreateILVariable name = "P_" + i; } if (variables.TryGetValue(i, out var v)) @@ -221,6 +221,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (variables.TryGetValue(i, out var v)) variableMapping[v] = p.Name; } + else if (variables.TryGetValue(i, out var v)) + { + v.HasGeneratedName = true; + } } } } @@ -287,6 +291,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true); } + public string AssignNameIfUnassigned(ILVariable v) + { + if (variableMapping.TryGetValue(v, out var name)) + return name; + return AssignName(v); + } + public string AssignName(ILVariable v) { // variable has no valid name @@ -432,57 +443,65 @@ namespace ICSharpCode.Decompiler.IL.Transforms function.AcceptVisitor(this, null); } - protected override Unit Default(ILInstruction inst, VariableScope context) + Unit VisitChildren(ILInstruction inst, VariableScope context) { foreach (var child in inst.Children) { child.AcceptVisitor(this, context); } - if (inst is not IInstructionWithVariableOperand { Variable: var v }) - return default; + return default; + } - // if there is already a valid name for the variable slot, just use it - string name = context.TryGetExistingName(v); - if (!string.IsNullOrEmpty(name)) + protected override Unit Default(ILInstruction inst, VariableScope context) + { + if (inst is IInstructionWithVariableOperand { Variable: var v }) { - v.Name = name; - return default; - } + // if there is already a valid name for the variable slot, just use it + string name = context.TryGetExistingName(v); + if (!string.IsNullOrEmpty(name)) + { + v.Name = name; + return VisitChildren(inst, context); + } - switch (v.Kind) - { - case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction: - // Parameter names of top-level functions are handled in ILReader.CreateILVariable - // and CSharpDecompiler.FixParameterNames - break; - case VariableKind.InitializerTarget: // keep generated names - case VariableKind.NamedArgument: - context.ReserveVariableName(v.Name); - break; - case VariableKind.DisplayClassLocal: - v.Name = context.NextDisplayClassLocal(); - break; - case VariableKind.Local when v.Index != null: - name = context.TryGetExistingName(v.Function, v.Index.Value); - if (name != null) - { - // make sure all local ILVariables that refer to the same slot in the locals signature - // are assigned the same name. - v.Name = name; - } - else - { + switch (v.Kind) + { + case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction: + // Parameter names of top-level functions are handled in ILReader.CreateILVariable + // and CSharpDecompiler.FixParameterNames + break; + case VariableKind.InitializerTarget: // keep generated names + case VariableKind.NamedArgument: + context.ReserveVariableName(v.Name); + break; + case VariableKind.UsingLocal when v.AddressCount == 0 && v.LoadCount == 0: + // using variables that are not read, will not be declared in source source + break; + case VariableKind.DisplayClassLocal: + v.Name = context.NextDisplayClassLocal(); + break; + case VariableKind.Local when v.Index != null: + name = context.TryGetExistingName(v.Function, v.Index.Value); + if (name != null) + { + // make sure all local ILVariables that refer to the same slot in the locals signature + // are assigned the same name. + v.Name = name; + } + else + { + v.Name = context.AssignName(v); + context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name); + } + break; + default: v.Name = context.AssignName(v); - context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name); - } - break; - default: - v.Name = context.AssignName(v); - break; + break; + } } - return default; + return VisitChildren(inst, context); } protected internal override Unit VisitILFunction(ILFunction function, VariableScope context) @@ -515,7 +534,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName); } - return base.VisitILFunction(function, new VariableScope(function, this.context, context)); + var nestedContext = new VariableScope(function, this.context, context); + base.VisitILFunction(function, nestedContext); + + if (function.Kind != ILFunctionKind.TopLevelFunction) + { + foreach (var p in function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0)) + { + p.Name = nestedContext.AssignNameIfUnassigned(p); + } + } + + return default; } protected internal override Unit VisitCall(Call inst, VariableScope context) From 8a67f48e4e8d7320ea5b71e5c52f2b154a57ae14 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sun, 2 Mar 2025 22:18:24 +0100 Subject: [PATCH 42/49] Fix #1956: Adapt previous fix for variable names that have a number as suffix. --- ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 24f7079fc..e0029d00e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -215,7 +215,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (variables.TryGetValue(i, out var v)) variableMapping[v] = p.Name; } - if (!parentScope.IsReservedVariableName(p.Name, out _)) + string nameWithoutNumber = SplitName(p.Name, out int newIndex); + if (!parentScope.IsReservedVariableName(nameWithoutNumber, out _)) { AddExistingName(reservedVariableNames, p.Name); if (variables.TryGetValue(i, out var v)) From 355a039b59f137abf1bb7ca1fcf2a068c3a5de08 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sun, 2 Mar 2025 22:47:30 +0100 Subject: [PATCH 43/49] Sightly improve variable naming of known types such as EventArgs and Exceptions --- .../TestCases/ILPretty/GuessAccessors.cs | 12 +++---- .../TestCases/Pretty/ExceptionHandling.cs | 12 +++---- .../IL/Transforms/AssignVariableNames.cs | 34 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs index 423ad4119..69ebf0db9 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs @@ -50,9 +50,9 @@ namespace ClassLibrary1 //IL_00e1: Expected O, but got Unknown //IL_00e1: Expected O, but got Unknown UnknownGenericClass<UnknownEventArgs> val = new UnknownGenericClass<UnknownEventArgs>(); - UnknownEventArgs val2 = (val.UnknownProperty = val.UnknownProperty); + UnknownEventArgs e = (val.UnknownProperty = val.UnknownProperty); List<object> list = new List<object> { - val[((object)val2).GetHashCode()] ?? "", + val[((object)e).GetHashCode()] ?? "", val.NotProperty, val.get_NotPropertyWithGeneric<string>(42), val[42], @@ -61,10 +61,10 @@ namespace ClassLibrary1 }; val.OnEvent += Instance_OnEvent; val.OnEvent -= Instance_OnEvent; - UnknownEventArgs val3 = val[(UnknownEventArgs)null]; - val[new UnknownEventArgs()] = val3; - UnknownEventArgs val4 = val[new UnknownEventArgs(), new UnknownEventArgs()]; - val[new UnknownEventArgs(), new UnknownEventArgs()] = val4; + UnknownEventArgs e2 = val[(UnknownEventArgs)null]; + val[new UnknownEventArgs()] = e2; + UnknownEventArgs e3 = val[new UnknownEventArgs(), new UnknownEventArgs()]; + val[new UnknownEventArgs(), new UnknownEventArgs()] = e3; } public void MethodUnknownStatic() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs index 82aba9ab6..2e47643f0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs @@ -418,9 +418,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Console.WriteLine(input); } - catch (TException val) + catch (TException ex) { - Console.WriteLine(val.Message); + Console.WriteLine(ex.Message); throw; } } @@ -452,9 +452,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Console.WriteLine(input); } - catch (TException val) when (val.Message.Contains("Test")) + catch (TException ex) when (ex.Message.Contains("Test")) { - Console.WriteLine(val.Message); + Console.WriteLine(ex.Message); throw; } } @@ -465,9 +465,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Console.WriteLine(input); } - catch (TException val) when (val.Message.Contains("Test")) + catch (TException ex) when (ex.Message.Contains("Test")) { - Console.WriteLine("{0} {1}", val, val.ToString()); + Console.WriteLine("{0} {1}", ex, ex.ToString()); } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index e0029d00e..dbe8c5dd4 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -729,22 +729,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType); } - string name = type.Kind switch { - TypeKind.Array => "array", - TypeKind.Pointer => "ptr", - TypeKind.TypeParameter => "val", - TypeKind.Unknown => "val", - TypeKind.Dynamic => "val", - TypeKind.ByReference => "reference", - TypeKind.Tuple => "tuple", - TypeKind.NInt => "num", - TypeKind.NUInt => "num", - _ => null - }; - if (name != null) - { - return name; - } + string name; if (type.IsAnonymousType()) { name = "anon"; @@ -753,13 +738,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms { name = "ex"; } + else if (type.Name.EndsWith("EventArgs", StringComparison.Ordinal)) + { + name = "e"; + } else if (type.IsCSharpNativeIntegerType()) { name = "num"; } else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) { - name = type.Name; + name = type.Kind switch { + TypeKind.Array => "array", + TypeKind.Pointer => "ptr", + TypeKind.TypeParameter => "val", + TypeKind.Unknown => "val", + TypeKind.Dynamic => "val", + TypeKind.ByReference => "reference", + TypeKind.Tuple => "tuple", + TypeKind.NInt => "num", + TypeKind.NUInt => "num", + _ => type.Name + }; // remove the 'I' for interfaces if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2])) name = name.Substring(1); From 356d7a1b436f70dfbfa5080b75492f8623d2ceed Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Mar 2025 10:05:08 +0100 Subject: [PATCH 44/49] Fix #3408: Wrong exported assembly type --- .../CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs index a536d72c7..dba1f0f4c 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs @@ -81,13 +81,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler w.WriteEndElement(); // </Platform> string outputType; + PEHeaders headers = (module as PEFile)?.Reader.PEHeaders; - switch ((module as PEFile)?.Reader.PEHeaders.PEHeader.Subsystem) + switch (headers?.PEHeader.Subsystem) { - case Subsystem.WindowsGui: + case Subsystem.WindowsGui when !headers.IsDll: outputType = "WinExe"; break; - case Subsystem.WindowsCui: + case Subsystem.WindowsCui when !headers.IsDll: outputType = "Exe"; break; default: From 29861d7903cf9aceb9b074991d2d7f5e5c2f2ecc Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 21 Mar 2025 11:00:11 +0100 Subject: [PATCH 45/49] Fix #1647: Add a cast to enum constants where the enum type is not known. --- ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 63b697d75..2e21d93c6 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1134,7 +1134,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax expr = new PrimitiveExpression(constantValue, format); if (AddResolveResultAnnotations && literalType != null) expr.AddAnnotation(new ConstantResolveResult(literalType, constantValue)); - if (integerTypeMismatch && !type.Equals(expectedType)) + if (integerTypeMismatch && !type.Equals(expectedType) || underlyingType.Kind == TypeKind.Unknown) { expr = new CastExpression(ConvertType(type), expr); } From 51522c448784d31b4ce0058261436f0de573ef14 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sat, 22 Mar 2025 10:14:11 +0100 Subject: [PATCH 46/49] Fix #3190: NRE in YieldReturnDecompiler --- ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index c5ef712bc..5a8565a8d 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -1014,7 +1014,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } // We can't use MatchStLoc like above since the doFinallyBodies variable is split by SplitVariables. // This occurs for the Legacy VBC compiler. - if (oldBlock.Instructions[pos].MatchStLoc(out var var, out value) && var.Kind == VariableKind.Local && var.Index == doFinallyBodies.Index) + if (oldBlock.Instructions[pos].MatchStLoc(out var var, out value) && var.Kind == VariableKind.Local && var.Index == doFinallyBodies?.Index) { if (!value.MatchLdcI4(0)) { From 128f83d74cdef3cf6065977b2df7414ba950e22d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sat, 22 Mar 2025 12:19:47 +0100 Subject: [PATCH 47/49] Fix: Pass current language version to all parts of the UI --- ILSpy/ExtensionMethods.cs | 8 ++++-- ILSpy/Languages/CSharpLanguage.cs | 25 ++++++++++++++++--- ILSpy/Languages/LanguageService.cs | 2 ++ .../Metadata/CorTables/EventTableTreeNode.cs | 2 +- .../Metadata/CorTables/FieldTableTreeNode.cs | 2 +- .../Metadata/CorTables/MethodTableTreeNode.cs | 2 +- .../CorTables/PropertyTableTreeNode.cs | 2 +- .../CorTables/TypeDefTableTreeNode.cs | 2 +- ILSpy/Search/SearchPane.xaml.cs | 10 ++++++-- ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs | 2 +- ILSpy/TreeNodes/EventTreeNode.cs | 2 +- ILSpy/TreeNodes/FieldTreeNode.cs | 2 +- ILSpy/TreeNodes/MethodTreeNode.cs | 2 +- ILSpy/TreeNodes/PropertyTreeNode.cs | 2 +- ILSpy/TreeNodes/ReferenceFolderTreeNode.cs | 2 +- ILSpy/TreeNodes/TypeTreeNode.cs | 2 +- 16 files changed, 50 insertions(+), 19 deletions(-) diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index bd460826c..60bb77d40 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -76,11 +76,15 @@ namespace ICSharpCode.ILSpy return result; } - public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService) + public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService, LanguageVersion languageVersion) { + var decompilerSettings = settingsService.DecompilerSettings.Clone(); + if (!Enum.TryParse(languageVersion.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion)) + csharpLanguageVersion = Decompiler.CSharp.LanguageVersion.Latest; + decompilerSettings.SetLanguageVersion(csharpLanguageVersion); return file .GetLoadedAssembly() - .GetTypeSystemOrNull(DecompilerTypeSystem.GetOptions(settingsService.DecompilerSettings)); + .GetTypeSystemOrNull(DecompilerTypeSystem.GetOptions(decompilerSettings)); } #region DPI independence diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index b1686836c..73336640e 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -578,10 +578,18 @@ namespace ICSharpCode.ILSpy CSharpAmbience ambience = new CSharpAmbience(); // Do not forget to update CSharpAmbienceTests.ILSpyMainTreeViewTypeFlags, if this ever changes. ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList | ConversionFlags.PlaceReturnTypeAfterParameterList; - if (SettingsService.DecompilerSettings.LiftNullables) + var decompilerSettings = SettingsService.DecompilerSettings.Clone(); + if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion)) + languageVersion = Decompiler.CSharp.LanguageVersion.Latest; + decompilerSettings.SetLanguageVersion(languageVersion); + if (decompilerSettings.LiftNullables) { ambience.ConversionFlags |= ConversionFlags.UseNullableSpecifierForValueTypes; } + if (decompilerSettings.IntroducePrivateProtectedAccessibility) + { + ambience.ConversionFlags |= ConversionFlags.UsePrivateProtectedAccessibility; + } return ambience; } @@ -781,7 +789,11 @@ namespace ICSharpCode.ILSpy public override bool ShowMember(IEntity member) { MetadataFile assembly = member.ParentModule.MetadataFile; - return showAllMembers || !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, SettingsService.DecompilerSettings); + var decompilerSettings = SettingsService.DecompilerSettings.Clone(); + if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion)) + languageVersion = Decompiler.CSharp.LanguageVersion.Latest; + decompilerSettings.SetLanguageVersion(languageVersion); + return showAllMembers || !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, decompilerSettings); } public override RichText GetRichTextTooltip(IEntity entity) @@ -790,7 +802,10 @@ namespace ICSharpCode.ILSpy var output = new StringWriter(); var decoratedWriter = new TextWriterTokenWriter(output); var writer = new CSharpHighlightingTokenWriter(TokenWriter.InsertRequiredSpaces(decoratedWriter), locatable: decoratedWriter); - var settings = SettingsService.DecompilerSettings; + var settings = SettingsService.DecompilerSettings.Clone(); + if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion)) + languageVersion = Decompiler.CSharp.LanguageVersion.Latest; + settings.SetLanguageVersion(languageVersion); if (!settings.LiftNullables) { flags &= ~ConversionFlags.UseNullableSpecifierForValueTypes; @@ -815,6 +830,10 @@ namespace ICSharpCode.ILSpy { flags |= ConversionFlags.SupportInitAccessors; } + if (settings.IntroducePrivateProtectedAccessibility) + { + flags |= ConversionFlags.UsePrivateProtectedAccessibility; + } if (entity is IMethod m && m.IsLocalFunction) { writer.WriteIdentifier(Identifier.Create("(local)")); diff --git a/ILSpy/Languages/LanguageService.cs b/ILSpy/Languages/LanguageService.cs index 6c116b1ad..7cd6e47e1 100644 --- a/ILSpy/Languages/LanguageService.cs +++ b/ILSpy/Languages/LanguageService.cs @@ -35,10 +35,12 @@ namespace ICSharpCode.ILSpy [Shared] public class LanguageService : ObservableObjectBase { + private readonly SettingsService settingsService; private readonly LanguageSettings languageSettings; public LanguageService(IEnumerable<Language> languages, SettingsService settingsService, DockWorkspace dockWorkspace) { + this.settingsService = settingsService; languageSettings = settingsService.SessionSettings.LanguageSettings; var sortedLanguages = languages.ToList(); diff --git a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs index 821a25476..d63ab0ef3 100644 --- a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs @@ -94,7 +94,7 @@ namespace ICSharpCode.ILSpy.Metadata IEntity IMemberTreeNode.Member { get { - return ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle); + return ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle); } } diff --git a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs index eb48eaec2..345600d7c 100644 --- a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs @@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.Metadata public string NameTooltip => $"{MetadataTokens.GetHeapOffset(fieldDef.Name):X} \"{Name}\""; - IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle); + IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle); [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(fieldDef.Signature); diff --git a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs index 775661855..4f2d7b712 100644 --- a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs @@ -131,7 +131,7 @@ namespace ICSharpCode.ILSpy.Metadata } } - IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle); + IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle); public MethodDefEntry(MetadataFile metadataFile, MethodDefinitionHandle handle) { diff --git a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs index 255892ea1..87d187d28 100644 --- a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs @@ -92,7 +92,7 @@ namespace ICSharpCode.ILSpy.Metadata public string NameTooltip => $"{MetadataTokens.GetHeapOffset(propertyDef.Name):X} \"{Name}\""; - IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle); + IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle); [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(propertyDef.Signature); diff --git a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs index e7aa7bbd4..d5f51939d 100644 --- a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs @@ -173,7 +173,7 @@ namespace ICSharpCode.ILSpy.Metadata } } - IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle); + IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle); public TypeDefEntry(MetadataFile metadataFile, TypeDefinitionHandle handle) { diff --git a/ILSpy/Search/SearchPane.xaml.cs b/ILSpy/Search/SearchPane.xaml.cs index 83ec4cc1e..190d65b81 100644 --- a/ILSpy/Search/SearchPane.xaml.cs +++ b/ILSpy/Search/SearchPane.xaml.cs @@ -266,6 +266,7 @@ namespace ICSharpCode.ILSpy.Search searchTerm, (SearchMode)searchModeComboBox.SelectedIndex, assemblyTreeModel.CurrentLanguage, + assemblyTreeModel.CurrentLanguageVersion, treeNodeFactory, settingsService); currentSearch = startedSearch; @@ -295,6 +296,7 @@ namespace ICSharpCode.ILSpy.Search readonly SearchRequest searchRequest; readonly SearchMode searchMode; readonly Language language; + readonly LanguageVersion languageVersion; readonly ApiVisibility apiVisibility; readonly ITreeNodeFactory treeNodeFactory; readonly SettingsService settingsService; @@ -302,7 +304,7 @@ namespace ICSharpCode.ILSpy.Search public IProducerConsumerCollection<SearchResult> ResultQueue { get; } = new ConcurrentQueue<SearchResult>(); public RunningSearch(IList<LoadedAssembly> assemblies, string searchTerm, SearchMode searchMode, - Language language, ITreeNodeFactory treeNodeFactory, SettingsService settingsService) + Language language, LanguageVersion languageVersion, ITreeNodeFactory treeNodeFactory, SettingsService settingsService) { this.assemblies = assemblies; this.language = language; @@ -471,7 +473,11 @@ namespace ICSharpCode.ILSpy.Search request.RegEx = regex; request.SearchResultFactory = new SearchResultFactory(language); request.TreeNodeFactory = this.treeNodeFactory; - request.DecompilerSettings = settingsService.DecompilerSettings; + var decompilerSettings = settingsService.DecompilerSettings.Clone(); + if (!Enum.TryParse(this.languageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion)) + languageVersion = Decompiler.CSharp.LanguageVersion.Latest; + decompilerSettings.SetLanguageVersion(languageVersion); + request.DecompilerSettings = settingsService.DecompilerSettings.Clone(); return request; } diff --git a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs index c62af32d8..b779350d6 100644 --- a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs @@ -89,7 +89,7 @@ namespace ICSharpCode.ILSpy.TreeNodes var referencedModule = resolver.Resolve(r); if (referencedModule != null) { - var module = (MetadataModule)referencedModule.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule; + var module = (MetadataModule)referencedModule.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule; foreach (var childRef in referencedModule.AssemblyReferences) this.Children.Add(new AssemblyReferenceTreeNode(module, childRef, parentAssembly)); } diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs index ad75b1b5b..1af0a9de3 100644 --- a/ILSpy/TreeNodes/EventTreeNode.cs +++ b/ILSpy/TreeNodes/EventTreeNode.cs @@ -52,7 +52,7 @@ namespace ICSharpCode.ILSpy.TreeNodes private IEvent GetEventDefinition() { return ((MetadataModule)EventDefinition.ParentModule?.MetadataFile - ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService) + ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion) ?.MainModule)?.GetDefinition((EventDefinitionHandle)EventDefinition.MetadataToken) ?? EventDefinition; } diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs index 26319a03d..4a086ceb7 100644 --- a/ILSpy/TreeNodes/FieldTreeNode.cs +++ b/ILSpy/TreeNodes/FieldTreeNode.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.ILSpy.TreeNodes private IField GetFieldDefinition() { return ((MetadataModule)FieldDefinition.ParentModule?.MetadataFile - ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService) + ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion) ?.MainModule)?.GetDefinition((FieldDefinitionHandle)FieldDefinition.MetadataToken) ?? FieldDefinition; } diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs index f3d2a6daa..1658ac714 100644 --- a/ILSpy/TreeNodes/MethodTreeNode.cs +++ b/ILSpy/TreeNodes/MethodTreeNode.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.ILSpy.TreeNodes private IMethod GetMethodDefinition() { return ((MetadataModule)MethodDefinition.ParentModule?.MetadataFile - ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService) + ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion) ?.MainModule)?.GetDefinition((MethodDefinitionHandle)MethodDefinition.MetadataToken) ?? MethodDefinition; } diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs index df1a9f473..fc0a0ccbc 100644 --- a/ILSpy/TreeNodes/PropertyTreeNode.cs +++ b/ILSpy/TreeNodes/PropertyTreeNode.cs @@ -54,7 +54,7 @@ namespace ICSharpCode.ILSpy.TreeNodes private IProperty GetPropertyDefinition() { return ((MetadataModule)PropertyDefinition.ParentModule?.MetadataFile - ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService) + ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion) ?.MainModule)?.GetDefinition((PropertyDefinitionHandle)PropertyDefinition.MetadataToken) ?? PropertyDefinition; } diff --git a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs index c8c4dd8d0..61fc007b7 100644 --- a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs +++ b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes protected override void LoadChildren() { var metadata = module.Metadata; - var metadataModule = (MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule; + var metadataModule = (MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule; foreach (var r in module.AssemblyReferences.OrderBy(r => r.Name)) this.Children.Add(new AssemblyReferenceTreeNode(metadataModule, r, parentAssembly)); foreach (var r in metadata.GetModuleReferences().OrderBy(r => metadata.GetString(metadata.GetModuleReference(r).Name))) diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs index a79b5c654..edb4d3567 100644 --- a/ILSpy/TreeNodes/TypeTreeNode.cs +++ b/ILSpy/TreeNodes/TypeTreeNode.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes { return ((MetadataModule)ParentAssemblyNode.LoadedAssembly .GetMetadataFileOrNull() - ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService) + ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion) ?.MainModule)?.GetDefinition((SRM.TypeDefinitionHandle)TypeDefinition.MetadataToken); } From 96caa4ecb793c2e3215f44b7c05f1bd4d9ccf08e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sat, 22 Mar 2025 12:27:22 +0100 Subject: [PATCH 48/49] Fix: #3407 Add "private protected" feature for 7.2 decompiler options --- .../CSharp/CSharpDecompiler.cs | 1 + .../CSharp/OutputVisitor/CSharpAmbience.cs | 1 + .../CSharp/Syntax/TypeSystemAstBuilder.cs | 21 ++++++++++++------- ICSharpCode.Decompiler/DecompilerSettings.cs | 21 ++++++++++++++++++- ICSharpCode.Decompiler/Output/IAmbience.cs | 5 +++++ ILSpy/Properties/Resources.Designer.cs | 19 +++++++++++++++++ ILSpy/Properties/Resources.resx | 5 ++++- ILSpy/Properties/Resources.zh-Hans.resx | 3 +++ 8 files changed, 67 insertions(+), 9 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index b9559dba1..6c881715f 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -528,6 +528,7 @@ namespace ICSharpCode.Decompiler.CSharp { var typeSystemAstBuilder = new TypeSystemAstBuilder(); typeSystemAstBuilder.ShowAttributes = true; + typeSystemAstBuilder.UsePrivateProtectedAccessibility = settings.IntroducePrivateProtectedAccessibility; typeSystemAstBuilder.SortAttributes = settings.SortCustomAttributes; typeSystemAstBuilder.AlwaysUseShortTypeNames = true; typeSystemAstBuilder.AddResolveResultAnnotations = true; diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index aa290cbd8..35036232e 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -236,6 +236,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor astBuilder.ShowTypeParametersForUnboundTypes = true; astBuilder.ShowModifiers = (ConversionFlags & ConversionFlags.ShowModifiers) == ConversionFlags.ShowModifiers; astBuilder.ShowAccessibility = (ConversionFlags & ConversionFlags.ShowAccessibility) == ConversionFlags.ShowAccessibility; + astBuilder.UsePrivateProtectedAccessibility = (ConversionFlags & ConversionFlags.UsePrivateProtectedAccessibility) == ConversionFlags.UsePrivateProtectedAccessibility; astBuilder.AlwaysUseShortTypeNames = (ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) != ConversionFlags.UseFullyQualifiedTypeNames; astBuilder.ShowParameterNames = (ConversionFlags & ConversionFlags.ShowParameterNames) == ConversionFlags.ShowParameterNames; astBuilder.UseNullableSpecifierForValueTypes = (ConversionFlags & ConversionFlags.UseNullableSpecifierForValueTypes) != 0; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 2e21d93c6..3583c6a44 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -70,6 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax this.UseKeywordsForBuiltinTypes = true; this.UseNullableSpecifierForValueTypes = true; this.ShowAccessibility = true; + this.UsePrivateProtectedAccessibility = true; this.ShowModifiers = true; this.ShowBaseTypes = true; this.ShowTypeParameters = true; @@ -93,13 +94,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public bool AddResolveResultAnnotations { get; set; } /// <summary> - /// Controls the accessibility modifiers are shown. + /// Controls whether accessibility modifiers are shown. /// The default value is <see langword="true" />. /// </summary> public bool ShowAccessibility { get; set; } /// <summary> - /// Controls the non-accessibility modifiers are shown. + /// Controls whether "private protected" accessibility modifiers are shown. + /// The default value is <see langword="true" />. + /// </summary> + public bool UsePrivateProtectedAccessibility { get; set; } + + /// <summary> + /// Controls whether non-accessibility modifiers are shown. /// The default value is <see langword="true" />. /// </summary> public bool ShowModifiers { get; set; } @@ -1805,7 +1812,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax Modifiers modifiers = Modifiers.None; if (this.ShowAccessibility) { - modifiers |= ModifierFromAccessibility(typeDefinition.Accessibility); + modifiers |= ModifierFromAccessibility(typeDefinition.Accessibility, UsePrivateProtectedAccessibility); } if (this.ShowModifiers) { @@ -2073,7 +2080,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility) - decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility); + decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility, UsePrivateProtectedAccessibility); if (this.ShowModifiers && accessor.HasReadonlyModifier()) decl.Modifiers |= Modifiers.Readonly; TokenRole keywordRole = kind switch { @@ -2342,7 +2349,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax #endregion #region Convert Modifiers - public static Modifiers ModifierFromAccessibility(Accessibility accessibility) + public static Modifiers ModifierFromAccessibility(Accessibility accessibility, bool usePrivateProtected) { switch (accessibility) { @@ -2357,7 +2364,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax case Accessibility.ProtectedOrInternal: return Modifiers.Protected | Modifiers.Internal; case Accessibility.ProtectedAndInternal: - return Modifiers.Private | Modifiers.Protected; + return usePrivateProtected ? Modifiers.Private | Modifiers.Protected : Modifiers.Protected; default: return Modifiers.None; } @@ -2388,7 +2395,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax Modifiers m = Modifiers.None; if (this.ShowAccessibility && NeedsAccessibility(member)) { - m |= ModifierFromAccessibility(member.Accessibility); + m |= ModifierFromAccessibility(member.Accessibility, UsePrivateProtectedAccessibility); } if (this.ShowModifiers) { diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index b2d7f75e2..f4fac4485 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -112,6 +112,7 @@ namespace ICSharpCode.Decompiler introduceRefModifiersOnStructs = false; nonTrailingNamedArguments = false; refExtensionMethods = false; + introducePrivateProtectedAccessibilty = false; } if (languageVersion < CSharp.LanguageVersion.CSharp7_3) { @@ -185,7 +186,7 @@ namespace ICSharpCode.Decompiler || patternBasedFixedStatement) return CSharp.LanguageVersion.CSharp7_3; if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers - || nonTrailingNamedArguments || refExtensionMethods) + || nonTrailingNamedArguments || refExtensionMethods || introducePrivateProtectedAccessibilty) return CSharp.LanguageVersion.CSharp7_2; // C# 7.1 missing if (outVariables || throwExpressions || tupleTypes || tupleConversions @@ -1418,6 +1419,24 @@ namespace ICSharpCode.Decompiler } } + bool introducePrivateProtectedAccessibilty = true; + + /// <summary> + /// Gets/Sets whether "private protected" should be used. + /// </summary> + [Category("C# 7.2 / VS 2017.4")] + [Description("DecompilerSettings.IntroducePrivateProtectedAccessibility")] + public bool IntroducePrivateProtectedAccessibility { + get { return introducePrivateProtectedAccessibilty; } + set { + if (introducePrivateProtectedAccessibilty != value) + { + introducePrivateProtectedAccessibilty = value; + OnPropertyChanged(); + } + } + } + bool readOnlyMethods = true; [Category("C# 8.0 / VS 2019")] diff --git a/ICSharpCode.Decompiler/Output/IAmbience.cs b/ICSharpCode.Decompiler/Output/IAmbience.cs index 6518313a4..6099678e0 100644 --- a/ICSharpCode.Decompiler/Output/IAmbience.cs +++ b/ICSharpCode.Decompiler/Output/IAmbience.cs @@ -117,9 +117,14 @@ namespace ICSharpCode.Decompiler.Output /// Support C# 11 <c>operator checked</c>. /// </summary> SupportOperatorChecked = 0x100000, + /// <summary> + /// Support C# 7.2 <c>private protected</c>. + /// </summary> + UsePrivateProtectedAccessibility = 0x200000, StandardConversionFlags = ShowParameterNames | ShowAccessibility | + UsePrivateProtectedAccessibility | ShowParameterList | ShowParameterModifiers | ShowParameterDefaultValues | diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index ce6597717..c2982ddda 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1080,6 +1080,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// <summary> + /// Looks up a localized string similar to Introduce 'private protected' accessibility. + /// </summary> + public static string DecompilerSettings_IntroducePrivateProtectedAccessibility { + get { + return ResourceManager.GetString("DecompilerSettings.IntroducePrivateProtectedAccessibility", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Introduce static local functions. /// </summary> @@ -1118,6 +1127,16 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// <summary> + /// Looks up a localized resource of type System.Object. + /// </summary> + public static object DecompilerSettings_LifetimeAnnotations { + get { + object obj = ResourceManager.GetObject("DecompilerSettings.LifetimeAnnotations", resourceCulture); + return ((object)(obj)); + } + } + /// <summary> /// Looks up a localized string similar to Use nint/nuint types. /// </summary> diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index dd575638a..8b6e4ce23 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -595,7 +595,7 @@ Are you sure you want to continue?</value> <value>Enable folding on all blocks in braces</value> </data> <data name="EnableSmoothScrolling" xml:space="preserve"> - <value>Enable smooth scrolling</value> + <value>Enable smooth scrolling</value> </data> <data name="EnableWordWrap" xml:space="preserve"> <value>Enable word wrap</value> @@ -1108,4 +1108,7 @@ Do you want to continue?</value> <data name="_Window" xml:space="preserve"> <value>_Window</value> </data> + <data name="DecompilerSettings.IntroducePrivateProtectedAccessibility" xml:space="preserve"> + <value>Introduce 'private protected' accessibility</value> + </data> </root> \ No newline at end of file diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx index 472ac2503..0c6decb07 100644 --- a/ILSpy/Properties/Resources.zh-Hans.resx +++ b/ILSpy/Properties/Resources.zh-Hans.resx @@ -369,6 +369,9 @@ <data name="DecompilerSettings.IntroduceLocalFunctions" xml:space="preserve"> <value>引入局部函数(local functions)</value> </data> + <data name="DecompilerSettings.IntroducePrivateProtectedAccessibility" xml:space="preserve"> + <value /> + </data> <data name="DecompilerSettings.IntroduceStaticLocalFunctions" xml:space="preserve"> <value>引入静态局部函数(static local functions)</value> </data> From f5e851240d5d0dfcc9a7ed02e108910353821c7d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Sat, 22 Mar 2025 14:59:05 +0100 Subject: [PATCH 49/49] Add missing null check --- ILSpy/ExtensionMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index 60bb77d40..f0d3e8f14 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService, LanguageVersion languageVersion) { var decompilerSettings = settingsService.DecompilerSettings.Clone(); - if (!Enum.TryParse(languageVersion.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion)) + if (!Enum.TryParse(languageVersion?.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion)) csharpLanguageVersion = Decompiler.CSharp.LanguageVersion.Latest; decompilerSettings.SetLanguageVersion(csharpLanguageVersion); return file