From 15b826fb7060a648088bc3e3cd3b20ee90521c38 Mon Sep 17 00:00:00 2001 From: nil4 Date: Sun, 9 Feb 2025 10:41:16 +0100 Subject: [PATCH 1/6] Enable `major` 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 @@ WinExe net8.0-windows + major win-x64;win-arm64 False false From fa50e8d8b3602a8b56bef2f06b8f7024be28440a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 12 Feb 2025 22:53:22 +0100 Subject: [PATCH 2/6] 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 nonEmbeddedAttributesSnippetFile = new Lazy(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 GetPreprocessorSymbols(CompilerOptions flags) { var preprocessorSymbols = new List(); @@ -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 @@ - + 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 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 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 nonEmbeddedAttributeNames = new HashSet() { + // 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 Date: Wed, 12 Feb 2025 22:54:35 +0100 Subject: [PATCH 3/6] 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 List { get; } = new List(); + + 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> pathStack = new Stack>(); - bool IsPartOfInitializer(InstructionCollection instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, StatementTransformContext context) + bool IsPartOfInitializer(InstructionCollection 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 Date: Wed, 12 Feb 2025 22:56:49 +0100 Subject: [PATCH 4/6] 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 Date: Mon, 13 Jan 2025 20:04:58 +0100 Subject: [PATCH 5/6] 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 @@ + 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 Date: Wed, 19 Feb 2025 18:29:49 +0100 Subject: [PATCH 6/6] 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 @@ - + @@ -45,10 +45,10 @@ - - - - + + + + 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; } }