From 8cc186d59958496a5a714ed5c8a2fc6dc3a9978c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 21 Dec 2022 13:47:44 +0100 Subject: [PATCH 1/6] Use .NET 7.0 as target framework for the tests. This will be necessary for testing newer language features such as `ref` fields. --- .../ICSharpCode.Decompiler.TestRunner.csproj | 2 +- ICSharpCode.Decompiler.Tests/Helpers/Tester.cs | 10 +++++----- README.md | 2 +- global.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj b/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj index c60a25481..8c8d40ddc 100644 --- a/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj +++ b/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj @@ -2,7 +2,7 @@ Exe - net6.0-windows + net7.0 enable diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 3d35779e1..d1a19a620 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -105,9 +105,9 @@ namespace ICSharpCode.Decompiler.Tests.Helpers TesterPath = Path.GetDirectoryName(typeof(Tester).Assembly.Location); TestCasePath = Path.Combine(TesterPath, "../../../../TestCases"); #if DEBUG - testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Debug/net6.0-windows"); + testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Debug/net7.0"); #else - testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net6.0-windows"); + testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net7.0"); #endif packagesPropsFile = Path.Combine(TesterPath, "../../../../../packages.props"); roslynLatestVersion = XDocument.Load(packagesPropsFile).XPathSelectElement("//RoslynVersion").Value; @@ -270,8 +270,8 @@ namespace ICSharpCode.Decompiler.Tests.Helpers } static readonly string coreRefAsmPath = new DotNetCorePathFinder(TargetFrameworkIdentifier.NET, - new Version(6, 0), "Microsoft.NETCore.App") - .GetReferenceAssemblyPath(".NETCoreApp,Version=v6.0"); + new Version(7, 0), "Microsoft.NETCore.App") + .GetReferenceAssemblyPath(".NETCoreApp,Version=v7.0"); public static readonly string RefAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2"); @@ -308,7 +308,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers const string targetFrameworkAttributeSnippet = @" -[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp,Version=v6.0"", FrameworkDisplayName = """")] +[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp,Version=v7.0"", FrameworkDisplayName = """")] "; diff --git a/README.md b/README.md index 19d4a4dc8..d17665bb0 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ How to build - Follow Microsoft's instructions for [importing a configuration](https://docs.microsoft.com/en-us/visualstudio/install/import-export-installation-configurations?view=vs-2022#import-a-configuration), and import the .vsconfig file located at the root of the solution. - Alternatively, you can open the ILSpy solution (ILSpy.sln) and Visual Studio will [prompt you to install the missing components](https://docs.microsoft.com/en-us/visualstudio/install/import-export-installation-configurations?view=vs-2022#automatically-install-missing-components). - Finally, you can manually install the necessary components via the Visual Studio Installer. The workloads/components are as follows: - - Workload ".NET Desktop Development". This workload includes the .NET Framework 4.8 SDK and the .NET Framework 4.7.2 targeting pack, as well as the [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) (ILSpy.csproj targets .NET 6.0, but we have net472 projects too). _Note: The optional components of this workload are not required for ILSpy_ + - Workload ".NET Desktop Development". This workload includes the .NET Framework 4.8 SDK and the .NET Framework 4.7.2 targeting pack, as well as the [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) and [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0) (ILSpy.csproj targets .NET 6.0, but we have net472+net70 projects too). _Note: The optional components of this workload are not required for ILSpy_ - Workload "Visual Studio extension development" (ILSpy.sln contains a VS extension project) _Note: The optional components of this workload are not required for ILSpy_ - Individual Component "MSVC v143 - VS 2022 C++ x64/x86 build tools" (or similar) - _The VC++ toolset is optional_; if present it is used for `editbin.exe` to modify the stack size used by ILSpy.exe from 1MB to 16MB, because the decompiler makes heavy use of recursion, where small stack sizes lead to problems in very complex methods. diff --git a/global.json b/global.json index 68d9ecef8..1fcfe4fee 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.200", + "version": "7.0.100", "rollForward": "major", "allowPrerelease": true } From b9f6c88ed066dc124a6ad5f90e79893f711a153c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 23 Dec 2022 08:39:23 +0100 Subject: [PATCH 2/6] Fix parameters for StackTests test case. --- ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs index d06c6768e..ed4a3f2bb 100644 --- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs @@ -317,7 +317,7 @@ namespace ICSharpCode.Decompiler.Tests public async Task StackTests() { // IL contains .corflags = 32BITREQUIRED - await RunIL("StackTests.il", asmOptions: AssemblerOptions.Force32Bit); + await RunIL("StackTests.il", CompilerOptions.Force32Bit, AssemblerOptions.Force32Bit); } [Test] @@ -483,6 +483,11 @@ namespace ICSharpCode.Decompiler.Tests string outputFile = null; CompilerResults decompiledOutputFile = null; + bool optionsForce32Bit = options.HasFlag(CompilerOptions.Force32Bit); + bool asmOptionsForce32Bit = asmOptions.HasFlag(AssemblerOptions.Force32Bit); + + Assert.AreEqual(optionsForce32Bit, asmOptionsForce32Bit, "Inconsistent architecture."); + try { options |= CompilerOptions.UseTestRunner; From 263360f3f3c4437cb400e3a61f249ba5dcf92b5e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 23 Dec 2022 08:58:50 +0100 Subject: [PATCH 3/6] Add RemoveCompilerFeatureRequiredAttribute --- .../CSharp/CSharpDecompiler.cs | 25 +++++++++++++++++++ .../Implementation/KnownAttributes.cs | 2 ++ 2 files changed, 27 insertions(+) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index d89d77648..86ae6bf22 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1406,6 +1406,7 @@ namespace ICSharpCode.Decompiler.CSharp attr.Remove(); } } + RemoveCompilerFeatureRequiredAttribute(typeDecl, "RefStructs"); } if (settings.RequiredMembers) { @@ -1851,6 +1852,30 @@ namespace ICSharpCode.Decompiler.CSharp return found; } + internal static bool RemoveCompilerFeatureRequiredAttribute(EntityDeclaration entityDecl, string feature) + { + bool found = false; + foreach (var section in entityDecl.Attributes) + { + foreach (var attr in section.Attributes) + { + var symbol = attr.Type.GetSymbol(); + if (symbol is ITypeDefinition td && td.FullTypeName == KnownAttribute.CompilerFeatureRequired.GetTypeName() + && attr.Arguments.Count == 1 && attr.Arguments.SingleOrDefault() is PrimitiveExpression pe + && pe.Value is string s && s == feature) + { + attr.Remove(); + found = true; + } + } + if (section.Attributes.Count == 0) + { + section.Remove(); + } + } + return found; + } + bool FindAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType, out Syntax.Attribute attribute) { attribute = null; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index d6d99977a..31fba0891 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -33,6 +33,7 @@ namespace ICSharpCode.Decompiler.TypeSystem None, CompilerGenerated, + CompilerFeatureRequired, /// /// Marks a method as extension method; or a class as containing extension methods. /// @@ -119,6 +120,7 @@ namespace ICSharpCode.Decompiler.TypeSystem static readonly TopLevelTypeName[] typeNames = new TopLevelTypeName[Count]{ default, new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CompilerGeneratedAttribute)), + new TopLevelTypeName("System.Runtime.CompilerServices", "CompilerFeatureRequiredAttribute"), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(ExtensionAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(DynamicAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(TupleElementNamesAttribute)), From efeaf1356f5afdda8506209510e31b97bbe97b5c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 29 May 2023 14:07:06 +0200 Subject: [PATCH 4/6] Add feature: C#11 nint without NativeIntegerAttribute Because it is no longer possible to distinguish IntPtr from nint, this required a lot of testcase adjustment. --- .../Helpers/Tester.cs | 1 + .../PrettyTestRunner.cs | 3 +- .../TestCases/Pretty/ConstantsTests.cs | 18 +++++++++- .../TestCases/Pretty/DynamicTests.cs | 7 ++++ .../TestCases/Pretty/FunctionPointers.cs | 14 +++++++- .../TestCases/Pretty/LocalFunctions.cs | 4 +-- .../TestCases/Pretty/NativeInts.cs | 36 +++++++++++++++++-- .../TestCases/Pretty/PInvoke.cs | 11 ++++++ .../TestCases/Pretty/UnsafeCode.cs | 17 +++++++++ ICSharpCode.Decompiler/DecompilerSettings.cs | 21 ++++++++++- .../TypeSystem/ApplyAttributeTypeVisitor.cs | 2 +- .../TypeSystem/DecompilerTypeSystem.cs | 14 +++++++- ILSpy/Properties/Resources.Designer.cs | 9 +++++ ILSpy/Properties/Resources.resx | 3 ++ README.md | 4 +-- 15 files changed, 152 insertions(+), 12 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index d1a19a620..6219c2a1a 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -343,6 +343,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers { preprocessorSymbols.Add("NETCORE"); preprocessorSymbols.Add("NET60"); + preprocessorSymbols.Add("NET70"); } preprocessorSymbols.Add("ROSLYN"); preprocessorSymbols.Add("CS60"); diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index d1dfd0774..1a9a911e2 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -326,6 +326,7 @@ namespace ICSharpCode.Decompiler.Tests RemoveDeadStores = (cscOptions == CompilerOptions.None), UseExpressionBodyForCalculatedGetterOnlyProperties = false, FileScopedNamespaces = false, + NumericIntPtr = false, }); } @@ -486,7 +487,7 @@ namespace ICSharpCode.Decompiler.Tests } [Test] - public async Task NativeInts([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + public async Task NativeInts([ValueSource(nameof(roslyn3OrNewerWithNet40Options))] CompilerOptions cscOptions) { await RunForLibrary(cscOptions: cscOptions); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstantsTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstantsTests.cs index 8032484ce..5df746cc0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstantsTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstantsTests.cs @@ -1,10 +1,25 @@ -using System; +#if !(CS110 && NET70) +using System; +#endif using System.Threading.Tasks; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { internal class ConstantsTests { +#if CS90 + public nint? NullableNInt() + { + return null; + } + + public nuint? NullableNUInt() + { + return null; + } +#endif + +#if !(CS110 && NET70) public IntPtr? NullableIntPtr() { return null; @@ -14,6 +29,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { return null; } +#endif public ulong Issue1308(ulong u = 8uL) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs index c56524281..01edd50d2 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs @@ -436,10 +436,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return true.Equals(a); } +#if CS110 && NET70 + private static nint NewIntPtr(dynamic a) + { + return new nint(a); + } +#else private static IntPtr NewIntPtr(dynamic a) { return new IntPtr(a); } +#endif private static dynamic GetDynamic(int i) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs index faa752bcf..9423c4254 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs @@ -1,4 +1,6 @@ -using System; +#if !(CS110 && NET70) +using System; +#endif using System.Text; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -17,10 +19,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return &Overloaded; } +#if !(CS110 && NET70) public unsafe IntPtr GetAddressAsIntPtr() { return (IntPtr)(delegate*)(&Overloaded); } +#endif + + public unsafe nint GetAddressAsNInt() + { + return (nint)(delegate*)(&Overloaded); + } public unsafe void* GetAddressAsVoidPtr() { @@ -93,6 +102,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty internal class FunctionPointersWithNativeIntegerTypes { public unsafe delegate* F1; + #if !(CS110 && NET70) public unsafe delegate* F2; public unsafe delegate* F3; public unsafe delegate* F4; @@ -100,6 +110,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public unsafe delegate*> F6; public unsafe delegate*, IntPtr> F7; public unsafe delegate*> F8; + public unsafe delegate*> F9; + #endif } internal class FunctionPointersWithRefParams diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index c4d784fd4..677f6ebaf 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -844,10 +844,10 @@ namespace LocalFunctions #if CS90 public void Issue2196() { - EnumWindows(IntPtr.Zero, IntPtr.Zero); + EnumWindows(0L, 0L); [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "EnumWindows")] - static extern int EnumWindows(IntPtr hWnd, IntPtr lParam); + static extern int EnumWindows(long hWnd, long lParam); } #endif } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs index 69d3a3e0c..4267944bb 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs @@ -26,23 +26,36 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private const nint nint_const = 42; private const nuint nuint_const = 99u; +#if CS110 && NET70 + // C#11 on .NET7 no longer uses NativeIntegerAttribute, + // instead nint+IntPtr are considered to be the same type. + private nint intptr; + private nuint uintptr; +#else private IntPtr intptr; private UIntPtr uintptr; +#endif private nint i; private nuint u; private int i32; private uint u32; private long i64; private ulong u64; +#if !(CS110 && NET70) private (IntPtr, nint, UIntPtr, nuint) tuple_field; private (object, int, IntPtr, nint, UIntPtr, nuint) tuple_field2; private Dictionary dict1; private Dictionary dict2; private Dictionary dict3; private Dictionary dict4; +#endif + private Dictionary dict5; public void Convert() { + i = (nint)u; + u = (nuint)i; +#if !(CS110 && NET70) intptr = i; intptr = (nint)u; intptr = (nint)(nuint)uintptr; @@ -58,15 +71,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty u = (nuint)i; u = uintptr; u = (nuint)(nint)intptr; +#endif } public void Convert2() { i32 = (int)i; i = i32; +#if !(CS110 && NET70) intptr = (IntPtr)i32; i64 = (long)intptr; +#endif i64 = i; i = (nint)i64; @@ -79,7 +95,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void Arithmetic() { +#if !(CS110 && NET70) Console.WriteLine((nint)intptr * 2); +#endif Console.WriteLine(i * 2); Console.WriteLine(i + (nint)u); @@ -155,6 +173,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { GetInstance(3).u *= 2u; } +#if !(CS110 && NET70) GetInstance(4).intptr += (nint)i32; checked { @@ -164,10 +183,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } // multiplication results in compiler-error without the cast GetInstance(6).intptr *= (nint)2; +#endif - GetInstance(7).i <<= i32; + GetInstance(7).i += i32; + GetInstance(8).i <<= i32; } +#if !(CS110 && NET70) public void LocalTypeFromStore() { nint num = 42; @@ -188,10 +210,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty intptr = num3; intptr = intPtr; } - +#endif public void LocalTypeFromUse() { +#if CS110 && NET70 + nint num = intptr; + nint num2 = intptr; + + Console.WriteLine(); + + intptr = num; + i = num2 + 1; +#else IntPtr intPtr = intptr; nint num = intptr; @@ -199,6 +230,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty intptr = intPtr; i = num + 1; +#endif } public nint NegateUnsigned(nuint x) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs index b8483ecc1..6e7ca436a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs @@ -89,6 +89,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { } +#if CS110 && NET70 + [DllImport("ws2_32.dll", SetLastError = true)] + internal static extern nint ioctlsocket([In] nint socketHandle, [In] int cmd, [In][Out] ref int argp); + + public void CallMethodWithInOutParameter() + { + int argp = 0; + ioctlsocket(nint.Zero, 0, ref argp); + } +#else [DllImport("ws2_32.dll", SetLastError = true)] internal static extern IntPtr ioctlsocket([In] IntPtr socketHandle, [In] int cmd, [In][Out] ref int argp); @@ -97,5 +107,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty int argp = 0; ioctlsocket(IntPtr.Zero, 0, ref argp); } +#endif } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs index 2bb122eb3..9901fd69e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs @@ -30,7 +30,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { public static object StaticObj; +#if CS110 && NET70 + public nint A; +#else public IntPtr A; +#endif } private struct UnmanagedStruct @@ -605,6 +609,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return value.Integers[(int)s]; } +#if CS90 + private unsafe static void* CastNIntToVoidPtr(nint intptr) + { + return (void*)intptr; + } + + private unsafe static void* CastNIntToVoidPtr(nuint intptr) + { + return (void*)intptr; + } +#endif +#if !(CS110 && NET70) private unsafe static void* CastToVoidPtr(IntPtr intptr) { return (void*)intptr; @@ -614,6 +630,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { return (void*)intptr; } +#endif private unsafe static void* CastToVoidPtr(int* intptr) { diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 4ce0c44c0..fe4789422 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -152,12 +152,13 @@ namespace ICSharpCode.Decompiler parameterNullCheck = false; lifetimeAnnotations = false; requiredMembers = false; + numericIntPtr = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (parameterNullCheck || lifetimeAnnotations || requiredMembers) + if (parameterNullCheck || lifetimeAnnotations || requiredMembers || numericIntPtr) return CSharp.LanguageVersion.CSharp11_0; if (fileScopedNamespaces || recordStructs) return CSharp.LanguageVersion.CSharp10_0; @@ -211,6 +212,24 @@ namespace ICSharpCode.Decompiler } } + bool numericIntPtr = true; + + /// + /// Treat IntPtr/UIntPtr as nint/nuint. + /// + [Category("C# 11.0 / VS 2022.4")] + [Description("DecompilerSettings.NumericIntPtr")] + public bool NumericIntPtr { + get { return numericIntPtr; } + set { + if (numericIntPtr != value) + { + numericIntPtr = value; + OnPropertyChanged(); + } + } + } + bool covariantReturns = true; /// diff --git a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs index 5235da470..a6ba7ef15 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.TypeSystem { bool hasDynamicAttribute = false; bool[] dynamicAttributeData = null; - bool hasNativeIntegersAttribute = false; + bool hasNativeIntegersAttribute = (options & TypeSystemOptions.NativeIntegersWithoutAttribute) != 0; bool[] nativeIntegersAttributeData = null; string[] tupleElementNames = null; Nullability nullability; diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index b20d1bb0e..5898fa32e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -125,11 +125,17 @@ namespace ICSharpCode.Decompiler.TypeSystem /// LifetimeAnnotations = 0x4000, /// + /// Replace 'IntPtr' types with the 'nint' type even in absence of [NativeIntegerAttribute]. + /// Note: DecompilerTypeSystem constructor removes this setting from the options if + /// not targeting .NET 7 or later. + /// + NativeIntegersWithoutAttribute = 0x8000, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods - | NativeIntegers | FunctionPointers | LifetimeAnnotations + | NativeIntegers | FunctionPointers | LifetimeAnnotations | NativeIntegersWithoutAttribute } /// @@ -167,6 +173,8 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.FunctionPointers; if (settings.LifetimeAnnotations) typeSystemOptions |= TypeSystemOptions.LifetimeAnnotations; + if (settings.NumericIntPtr) + typeSystemOptions |= TypeSystemOptions.NativeIntegersWithoutAttribute; return typeSystemOptions; } @@ -304,6 +312,10 @@ namespace ICSharpCode.Decompiler.TypeSystem } } + if (!(identifier == TargetFrameworkIdentifier.NET && version >= new Version(7, 0))) + { + typeSystemOptions &= ~TypeSystemOptions.NativeIntegersWithoutAttribute; + } var mainModuleWithOptions = mainModule.WithOptions(typeSystemOptions); var referencedAssembliesWithOptions = referencedAssemblies.Select(file => file.WithOptions(typeSystemOptions)); // Primitive types are necessary to avoid assertions in ILReader. diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 94c50345b..60123a44f 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1108,6 +1108,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Treat (U)IntPtr as n(u)int. + /// + public static string DecompilerSettings_NumericIntPtr { + get { + return ResourceManager.GetString("DecompilerSettings.NumericIntPtr", resourceCulture); + } + } + /// /// Looks up a localized string similar to Object/collection initializer expressions. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 82d434c12..186665d73 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -393,6 +393,9 @@ Are you sure you want to continue? Nullable reference types + + Treat (U)IntPtr as n(u)int + Object/collection initializer expressions diff --git a/README.md b/README.md index d17665bb0..92b843aaf 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Features ------- * Decompilation to C# (check out the [language support status](https://github.com/icsharpcode/ILSpy/issues/829)) - * Whole-project decompilation (csproj, not sln!) + * Whole-project decompilation * Search for types/methods/properties (learn about the [options](https://github.com/icsharpcode/ILSpy/wiki/Search-Options)) * Hyperlink-based type/method/property navigation * Base/Derived types navigation, history @@ -65,7 +65,7 @@ How to build - ILSpy.XPlat.slnf: for the cross-platform CLI or PowerShell cmdlets - ILSpy.AddIn.slnf: for the Visual Studio plugin -**Note:** Visual Studio 16.3 and later include a version of the .NET (Core) SDK that is managed by the Visual Studio installer - once you update, it may get upgraded too. +**Note:** Visual Studio includes a version of the .NET SDK that is managed by the Visual Studio installer - once you update, it may get upgraded too. Please note that ILSpy is only compatible with the .NET 6.0 SDK and Visual Studio will refuse to load some projects in the solution (and unit tests will fail). If this problem occurs, please manually install the .NET 6.0 SDK from [here](https://dotnet.microsoft.com/download/dotnet/6.0). From 9abc2b90da2ce7e1980879eb776ea7e2f0034ebd Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 29 May 2023 15:01:25 +0200 Subject: [PATCH 5/6] Fix interaction of C# 11 nint==IntPtr with overload resolution. In C# 11+.NET 7 mode, we now always use type nint, never IntPtr, so that overload resolution works as expected. --- .../PrettyTestRunner.cs | 12 +++---- .../Correctness/OverloadResolution.cs | 31 +++++++++++++++++++ .../TestCases/Pretty/PInvoke.cs | 8 +---- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 15 ++++++++- .../TypeSystem/MetadataModule.cs | 5 +-- .../TypeSystem/TypeSystemExtensions.cs | 2 +- 6 files changed, 55 insertions(+), 18 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 1a9a911e2..c4027fc39 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -321,13 +321,11 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { - // legacy csc generates a dead store in debug builds - RemoveDeadStores = (cscOptions == CompilerOptions.None), - UseExpressionBodyForCalculatedGetterOnlyProperties = false, - FileScopedNamespaces = false, - NumericIntPtr = false, - }); + 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); } [Test] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs index 6d829ce95..e206872a5 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs @@ -36,6 +36,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness Issue1747(); CallAmbiguousOutParam(); CallWithInParam(); +#if CS90 + NativeIntTests(new IntPtr(1), 2); +#endif Issue2444.M2(); Issue2741.B.Test(new Issue2741.C()); } @@ -337,6 +340,34 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness #endif #endregion +#if CS90 + static void NativeIntTests(IntPtr i1, nint i2) + { + Console.WriteLine("NativeIntTests(i1):"); + ObjectOrLong((object)i1); + ObjectOrLong((long)i1); + Console.WriteLine("NativeIntTests(i2):"); + ObjectOrLong((object)i2); + ObjectOrLong((long)i2); + Console.WriteLine("NativeIntTests(new IntPtr):"); + ObjectOrLong((object)new IntPtr(3)); + ObjectOrLong((long)new IntPtr(3)); + Console.WriteLine("NativeIntTests(IntPtr.Zero):"); + ObjectOrLong((object)IntPtr.Zero); + ObjectOrLong((long)IntPtr.Zero); + } + + static void ObjectOrLong(object o) + { + Console.WriteLine("object " + o); + } + + static void ObjectOrLong(long l) + { + Console.WriteLine("long " + l); + } +#endif + #region #2444 public struct Issue2444 { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs index 6e7ca436a..877c8e393 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs @@ -93,20 +93,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty [DllImport("ws2_32.dll", SetLastError = true)] internal static extern nint ioctlsocket([In] nint socketHandle, [In] int cmd, [In][Out] ref int argp); - public void CallMethodWithInOutParameter() - { - int argp = 0; - ioctlsocket(nint.Zero, 0, ref argp); - } #else [DllImport("ws2_32.dll", SetLastError = true)] internal static extern IntPtr ioctlsocket([In] IntPtr socketHandle, [In] int cmd, [In][Out] ref int argp); - +#endif public void CallMethodWithInOutParameter() { int argp = 0; ioctlsocket(IntPtr.Zero, 0, ref argp); } -#endif } } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 3d53263cd..d256cf625 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1553,12 +1553,25 @@ namespace ICSharpCode.Decompiler.CSharp CastArguments(argumentList.Arguments, argumentList.ExpectedParameters); break; // make sure that we don't not end up in an infinite loop } + IType returnTypeOverride = null; + if (typeSystem.MainModule.TypeSystemOptions.HasFlag(TypeSystemOptions.NativeIntegersWithoutAttribute)) + { + // For DeclaringType, we don't use nint/nuint (so that DeclaringType.GetConstructors etc. works), + // but in NativeIntegersWithoutAttribute mode we must use nint/nuint for expression types, + // so that the appropriate set of conversions is used for further overload resolution. + if (method.DeclaringType.IsKnownType(KnownTypeCode.IntPtr)) + returnTypeOverride = SpecialType.NInt; + else if (method.DeclaringType.IsKnownType(KnownTypeCode.UIntPtr)) + returnTypeOverride = SpecialType.NUInt; + } return new ObjectCreateExpression( expressionBuilder.ConvertType(method.DeclaringType), argumentList.GetArgumentExpressions() ).WithRR(new CSharpInvocationResolveResult( target, method, argumentList.GetArgumentResolveResults().ToArray(), - isExpandedForm: argumentList.IsExpandedForm, argumentToParameterMap: argumentList.ArgumentToParameterMap + isExpandedForm: argumentList.IsExpandedForm, + argumentToParameterMap: argumentList.ArgumentToParameterMap, + returnTypeOverride: returnTypeOverride )); } } diff --git a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs index cf6ae1be5..3481ac1c8 100644 --- a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs +++ b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs @@ -402,8 +402,9 @@ namespace ICSharpCode.Decompiler.TypeSystem IType ResolveDeclaringType(EntityHandle declaringTypeReference, GenericContext context) { // resolve without substituting dynamic/tuple types - var ty = ResolveType(declaringTypeReference, context, - options & ~(TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple | TypeSystemOptions.NullabilityAnnotations)); + const TypeSystemOptions removedOptions = TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple + | TypeSystemOptions.NullabilityAnnotations | TypeSystemOptions.NativeIntegers | TypeSystemOptions.NativeIntegersWithoutAttribute; + var ty = ResolveType(declaringTypeReference, context, options & ~removedOptions); // but substitute tuple types in type arguments: ty = ApplyAttributeTypeVisitor.ApplyAttributesToType(ty, Compilation, null, metadata, options, Nullability.Oblivious, typeChildrenOnly: true); return ty; diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index 9da26dbe7..3ce5d0180 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -239,7 +239,7 @@ namespace ICSharpCode.Decompiler.TypeSystem bool IsUnmanagedTypeInternal(IType type) { - if (type.Kind is TypeKind.Enum or TypeKind.Pointer or TypeKind.FunctionPointer) + if (type.Kind is TypeKind.Enum or TypeKind.Pointer or TypeKind.FunctionPointer or TypeKind.NInt or TypeKind.NUInt) { return true; } From b823955ad6ee97715a66d070fd8fe0720a7731fa Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 29 May 2023 16:52:50 +0200 Subject: [PATCH 6/6] Fix mcs-5 foreach/using pattern when C# 7 (pattern matching) is not enabled. --- .../IL/Transforms/UsingTransform.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index c2a84f46c..ee3b705ea 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -388,7 +388,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms isInlinedIsInst = false; if (condition.MatchCompNotEquals(out var left, out var right)) { - if (left.MatchIsInst(out var arg, out var type) && type.IsKnownType(disposeType)) + if (left.MatchStLoc(out var inlineAssignVar, out var inlineAssignVal)) + { + if (!inlineAssignVal.MatchIsInst(out var arg, out var type) && type.IsKnownType(disposeType)) + return false; + if (!inlineAssignVar.IsSingleDefinition || inlineAssignVar.LoadCount != 1) + return false; + if (!inlineAssignVar.Type.IsKnownType(disposeType)) + return false; + isInlinedIsInst = true; + left = arg; + if (!left.MatchLdLoc(objVar) || !right.MatchLdNull()) + return false; + objVar = inlineAssignVar; + return true; + } + else if (left.MatchIsInst(out var arg, out var type) && type.IsKnownType(disposeType)) { isInlinedIsInst = true; left = arg;