diff --git a/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs b/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs index d9d89af5e..049533a7f 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs @@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers "System.Runtime.CompilerServices.IsUnmanagedAttribute", "System.Runtime.CompilerServices.NullableAttribute", "System.Runtime.CompilerServices.NullableContextAttribute", + "System.Runtime.CompilerServices.NativeIntegerAttribute", "Microsoft.CodeAnalysis.EmbeddedAttribute", }; diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 0dc01b9b4..1dc72a59f 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -57,6 +57,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers ReferenceVisualBasic = 0x40, ReferenceCore = 0x80, GeneratePdb = 0x100, + Preview = 0x200 } [Flags] @@ -271,7 +272,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers if (flags.HasFlag(CompilerOptions.UseRoslyn)) { var parseOptions = new CSharpParseOptions( preprocessorSymbols: preprocessorSymbols.ToArray(), - languageVersion: Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8 + languageVersion: flags.HasFlag(CompilerOptions.Preview) ? Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview : Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8 ); var syntaxTrees = sourceFileNames.Select(f => SyntaxFactory.ParseSyntaxTree(File.ReadAllText(f), parseOptions, path: f, encoding: Encoding.UTF8)); if (flags.HasFlag(CompilerOptions.ReferenceCore)) { @@ -399,7 +400,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers internal static DecompilerSettings GetSettings(CompilerOptions cscOptions) { if (cscOptions.HasFlag(CompilerOptions.UseRoslyn)) { - return new DecompilerSettings(CSharp.LanguageVersion.Latest); + if (cscOptions.HasFlag(CompilerOptions.Preview)) { + return new DecompilerSettings(CSharp.LanguageVersion.Latest); + } else { + return new DecompilerSettings(CSharp.LanguageVersion.CSharp8_0); + } } else { return new DecompilerSettings(CSharp.LanguageVersion.CSharp5); } diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 6df95aff2..c4b541875 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -42,8 +42,8 @@ - - + + @@ -92,6 +92,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 7a6cc9aa0..c5687aa85 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -337,6 +337,12 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions); } + [Test] + public void NativeInts([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + } + [Test] public void NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs index 3b369e3fc..2a1b99130 100644 --- a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs +++ b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs @@ -88,39 +88,65 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void ExplicitConversions() { - RunWithOutput("Random Tests\\TestCases", "ExplicitConversions.exe"); + RunWithOutput("Random Tests\\TestCases", "ExplicitConversions.exe", LanguageVersion.CSharp8_0); } [Test] public void ExplicitConversions_32() { - RunWithOutput("Random Tests\\TestCases", "ExplicitConversions_32.exe"); + RunWithOutput("Random Tests\\TestCases", "ExplicitConversions_32.exe", LanguageVersion.CSharp8_0); + } + + [Test] + [Ignore("Waiting for https://github.com/dotnet/roslyn/issues/45929")] + public void ExplicitConversions_With_NativeInts() + { + RunWithOutput("Random Tests\\TestCases", "ExplicitConversions.exe", LanguageVersion.Preview); + } + + [Test] + [Ignore("Waiting for https://github.com/dotnet/roslyn/issues/45929")] + public void ExplicitConversions_32_With_NativeInts() + { + RunWithOutput("Random Tests\\TestCases", "ExplicitConversions_32.exe", LanguageVersion.Preview); } [Test] public void Random_TestCase_1() { - RunWithOutput("Random Tests\\TestCases", "TestCase-1.exe"); + RunWithOutput("Random Tests\\TestCases", "TestCase-1.exe", LanguageVersion.CSharp8_0); + } + + [Test] + [Ignore("Waiting for https://github.com/dotnet/roslyn/issues/45929")] + public void Random_TestCase_1_With_NativeInts() + { + RunWithOutput("Random Tests\\TestCases", "TestCase-1.exe", LanguageVersion.Preview); } - void RunWithTest(string dir, string fileToRoundtrip, string fileToTest, string keyFile = null, bool useOldProjectFormat = false) + // Let's limit the roundtrip tests to C# 8.0 for now; because 9.0 is still in preview + // and the generated project doesn't build as-is. + const LanguageVersion defaultLanguageVersion = LanguageVersion.CSharp8_0; + + void RunWithTest(string dir, string fileToRoundtrip, string fileToTest, LanguageVersion languageVersion = defaultLanguageVersion, string keyFile = null, bool useOldProjectFormat = false) { - RunInternal(dir, fileToRoundtrip, outputDir => RunTest(outputDir, fileToTest), keyFile, useOldProjectFormat); + RunInternal(dir, fileToRoundtrip, outputDir => RunTest(outputDir, fileToTest), languageVersion, snkFilePath: keyFile, useOldProjectFormat: useOldProjectFormat); } - void RunWithOutput(string dir, string fileToRoundtrip) + void RunWithOutput(string dir, string fileToRoundtrip, LanguageVersion languageVersion = defaultLanguageVersion) { string inputDir = Path.Combine(TestDir, dir); RunInternal(dir, fileToRoundtrip, - outputDir => Tester.RunAndCompareOutput(fileToRoundtrip, Path.Combine(inputDir, fileToRoundtrip), Path.Combine(outputDir, fileToRoundtrip))); + outputDir => Tester.RunAndCompareOutput(fileToRoundtrip, Path.Combine(inputDir, fileToRoundtrip), Path.Combine(outputDir, fileToRoundtrip)), + languageVersion); } - void RunOnly(string dir, string fileToRoundtrip) + void RunOnly(string dir, string fileToRoundtrip, LanguageVersion languageVersion = defaultLanguageVersion) { - RunInternal(dir, fileToRoundtrip, outputDir => { }); + RunInternal(dir, fileToRoundtrip, outputDir => { }, languageVersion); } - void RunInternal(string dir, string fileToRoundtrip, Action testAction, string snkFilePath = null, bool useOldProjectFormat = false) + void RunInternal(string dir, string fileToRoundtrip, Action testAction, LanguageVersion languageVersion, string snkFilePath = null, bool useOldProjectFormat = false) { if (!Directory.Exists(TestDir)) { Assert.Ignore($"Assembly-roundtrip test ignored: test directory '{TestDir}' needs to be checked out separately." + Environment.NewLine + @@ -131,8 +157,8 @@ namespace ICSharpCode.Decompiler.Tests string outputDir = inputDir + "-output"; if (inputDir.EndsWith("TestCases")) { // make sure output dir names are unique so that we don't get trouble due to parallel test execution - decompiledDir += Path.GetFileNameWithoutExtension(fileToRoundtrip); - outputDir += Path.GetFileNameWithoutExtension(fileToRoundtrip); + decompiledDir += Path.GetFileNameWithoutExtension(fileToRoundtrip) + "_" + languageVersion.ToString(); + outputDir += Path.GetFileNameWithoutExtension(fileToRoundtrip) + "_" + languageVersion.ToString(); } ClearDirectory(decompiledDir); ClearDirectory(outputDir); @@ -155,9 +181,7 @@ namespace ICSharpCode.Decompiler.Tests // use a fixed GUID so that we can diff the output between different ILSpy runs without spurious changes var projectGuid = Guid.Parse("{127C83E4-4587-4CF9-ADCA-799875F3DFE6}"); - // Let's limit the roundtrip tests to C# 7.3 for now; because 8.0 is still in preview - // and the generated project doesn't build as-is. - var settings = new DecompilerSettings(LanguageVersion.CSharp7_3); + var settings = new DecompilerSettings(languageVersion); if (useOldProjectFormat) { settings.UseSdkStyleProjectFormat = false; } diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs index e278c01d3..9c7a43a4b 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs @@ -32,6 +32,8 @@ namespace ICSharpCode.Decompiler.Tests.Semantics // assign short names to the fake reflection types using Null = ICSharpCode.Decompiler.TypeSystem.ReflectionHelper.Null; using dynamic = ICSharpCode.Decompiler.TypeSystem.ReflectionHelper.Dynamic; + using nuint = ICSharpCode.Decompiler.TypeSystem.ReflectionHelper.NUInt; + using nint = ICSharpCode.Decompiler.TypeSystem.ReflectionHelper.NInt; using C = Conversion; [TestFixture, Parallelizable(ParallelScope.All)] @@ -56,6 +58,13 @@ namespace ICSharpCode.Decompiler.Tests.Semantics return conversions.ImplicitConversion(from2, to2); } + Conversion ExplicitConversion(Type from, Type to) + { + IType from2 = compilation.FindType(from); + IType to2 = compilation.FindType(to); + return conversions.ExplicitConversion(from2, to2); + } + [Test] public void IdentityConversions() { @@ -285,6 +294,126 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Assert.AreEqual(C.None, ImplicitConversion(typeof(int*), typeof(dynamic))); } + [Test] + public void ConversionToNInt() + { + // Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md + Assert.AreEqual(C.UnboxingConversion, ExplicitConversion(typeof(object), typeof(nint))); + Assert.AreEqual(C.ExplicitPointerConversion, ExplicitConversion(typeof(void*), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(sbyte), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(byte), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(short), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(ushort), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(int), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(uint), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(long), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(ulong), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(char), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(float), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(double), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(decimal), typeof(nint))); + Assert.AreEqual(C.IdentityConversion, ExplicitConversion(typeof(IntPtr), typeof(nint))); + Assert.AreEqual(C.None, ExplicitConversion(typeof(UIntPtr), typeof(nint))); + } + + [Test] + public void ConversionToNUInt() + { + // Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md + Assert.AreEqual(C.UnboxingConversion, ExplicitConversion(typeof(object), typeof(nuint))); + Assert.AreEqual(C.ExplicitPointerConversion, ExplicitConversion(typeof(void*), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(sbyte), typeof(nuint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(byte), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(short), typeof(nuint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(ushort), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(int), typeof(nuint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(uint), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(long), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(ulong), typeof(nuint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(char), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(float), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(double), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(decimal), typeof(nuint))); + Assert.AreEqual(C.None, ExplicitConversion(typeof(IntPtr), typeof(nuint))); + Assert.AreEqual(C.IdentityConversion, ExplicitConversion(typeof(UIntPtr), typeof(nuint))); + } + + [Test] + public void ConversionFromNInt() + { + // Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md + Assert.AreEqual(C.BoxingConversion, ExplicitConversion(typeof(nint), typeof(object))); + Assert.AreEqual(C.ExplicitPointerConversion, ExplicitConversion(typeof(nint), typeof(void*))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(sbyte))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(byte))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(short))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(ushort))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(int))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(uint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(long))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(ulong))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(char))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(float))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(double))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(decimal))); + Assert.AreEqual(C.IdentityConversion, ExplicitConversion(typeof(nint), typeof(IntPtr))); + Assert.AreEqual(C.None, ExplicitConversion(typeof(nint), typeof(UIntPtr))); + } + + [Test] + public void ConversionFromNUInt() + { + // Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md + Assert.AreEqual(C.BoxingConversion, ExplicitConversion(typeof(nuint), typeof(object))); + Assert.AreEqual(C.ExplicitPointerConversion, ExplicitConversion(typeof(nuint), typeof(void*))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(sbyte))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(byte))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(short))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(ushort))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(int))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(uint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(long))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(ulong))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(char))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(float))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(double))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(decimal))); + Assert.AreEqual(C.None, ExplicitConversion(typeof(nuint), typeof(IntPtr))); + Assert.AreEqual(C.IdentityConversion, ExplicitConversion(typeof(nuint), typeof(UIntPtr))); + } + + + [Test] + public void NIntEnumConversion() + { + var explicitEnumConversion = C.EnumerationConversion(isImplicit: false, isLifted: false); + Assert.AreEqual(explicitEnumConversion, ExplicitConversion(typeof(nint), typeof(StringComparison))); + Assert.AreEqual(explicitEnumConversion, ExplicitConversion(typeof(nuint), typeof(StringComparison))); + Assert.AreEqual(explicitEnumConversion, ExplicitConversion(typeof(StringComparison), typeof(nint))); + Assert.AreEqual(explicitEnumConversion, ExplicitConversion(typeof(StringComparison), typeof(nuint))); + } + + [Test] + public void IntegerLiteralToNIntConversions() + { + Assert.IsTrue(IntegerLiteralConversion(0, typeof(nint))); + Assert.IsTrue(IntegerLiteralConversion(-1, typeof(nint))); + Assert.IsFalse(IntegerLiteralConversion(uint.MaxValue, typeof(nint))); + Assert.IsFalse(IntegerLiteralConversion(long.MaxValue, typeof(nint))); + } + + + [Test] + public void IntegerLiteralToNUIntConversions() + { + Assert.IsTrue(IntegerLiteralConversion(0, typeof(nuint))); + Assert.IsFalse(IntegerLiteralConversion(-1, typeof(nuint))); + Assert.IsTrue(IntegerLiteralConversion(uint.MaxValue, typeof(nuint))); + Assert.IsFalse(IntegerLiteralConversion(long.MaxValue, typeof(nuint))); + } + [Test] public void UnconstrainedTypeParameter() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/CompoundAssignment.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/CompoundAssignment.cs index 4c60b7afd..ee9bbb0af 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/CompoundAssignment.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/CompoundAssignment.cs @@ -32,6 +32,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness UnsignedShiftRightStaticProperty(); DivideByBigValue(); Overflow(); + IntPtr_CompoundAssign(); } static void Test(int a, int b) @@ -104,6 +105,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness } } + IntPtr intPtrField = new IntPtr(IntPtr.Size == 8 ? long.MaxValue : int.MaxValue); + + public IntPtr IntPtrProperty { + get { + Console.WriteLine("In {0}.get_IntPtrProperty", instanceNumber); + return intPtrField; + } + set { + Console.WriteLine("In {0}.set_IntPtrProperty, value={1}", instanceNumber, value); + intPtrField = value; + } + } + public static Dictionary GetDict() { Console.WriteLine("In GetDict()"); @@ -112,8 +126,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness static CompoundAssignment GetObject() { - Console.WriteLine("In GetObject() (instance #)"); - return new CompoundAssignment(); + var obj = new CompoundAssignment(); + Console.WriteLine("In GetObject() (instance #{0})", obj.instanceNumber); + return obj; } static string GetString() @@ -208,5 +223,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness { return val; } + + static void IntPtr_CompoundAssign() + { + Console.WriteLine("IntPtr_CompoundAssign:"); +#if !MCS + GetObject().IntPtrProperty -= 2; + GetObject().IntPtrProperty += 2; +#endif + } } } \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs index 4bbc8dd9a..777dcedd1 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs @@ -186,7 +186,7 @@ namespace System.Runtime.CompilerServices [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe static void* Add(void* source, int elementOffset) { - return (byte*)source + (long)elementOffset * (long)sizeof(T); + return (byte*)source + (nint)elementOffset * (nint)sizeof(T); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -210,7 +210,7 @@ namespace System.Runtime.CompilerServices [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe static void* Subtract(void* source, int elementOffset) { - return (byte*)source - (long)elementOffset * (long)sizeof(T); + return (byte*)source - (nint)elementOffset * (nint)sizeof(T); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs new file mode 100644 index 000000000..7574f4757 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs @@ -0,0 +1,198 @@ +// Copyright (c) 2020 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 +// 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.Tests.TestCases.Pretty +{ + internal class NativeInts + { + private const nint nint_const = 42; + private const nuint nuint_const = 99u; + + private IntPtr intptr; + private UIntPtr uintptr; + private nint i; + private nuint u; + private int i32; + private uint u32; + private long i64; + private ulong u64; + private (IntPtr, nint, UIntPtr, nuint) tuple_field; + private Dictionary dict1; + private Dictionary dict2; + + public void Convert() + { + intptr = i; + intptr = (nint)u; + intptr = (nint)(nuint)uintptr; + + uintptr = (nuint)i; + uintptr = u; + uintptr = (nuint)(nint)intptr; + + i = intptr; + i = (nint)u; + i = (nint)(nuint)uintptr; + + u = (nuint)i; + u = uintptr; + u = (nuint)(nint)intptr; + } + + public void Convert2() + { + i32 = (int)i; + i = i32; + intptr = (IntPtr)i32; + + i64 = (long)intptr; + i64 = i; + i = (nint)i64; + + u32 = (uint)i; + i = (nint)u32; + + u64 = (uint)i; + i = (nint)u64; + } + + public void Arithmetic() + { + Console.WriteLine((nint)intptr * 2); + Console.WriteLine(i * 2); + + Console.WriteLine(i + (nint)u); + Console.WriteLine((nuint)i + u); + } + + public void Shifts() + { + Console.WriteLine(i << i32); + Console.WriteLine(i >> i32); + Console.WriteLine(u >> i32); + Console.WriteLine(u << i32); + } + + public void Comparisons() + { + Console.WriteLine(i < i32); + Console.WriteLine(i <= i32); + Console.WriteLine(i > i32); + Console.WriteLine(i >= i32); + Console.WriteLine(i == (nint)u); + Console.WriteLine(i < (nint)u); + Console.WriteLine((nuint)i < u); + } + + public void Unary() + { + Console.WriteLine(~i); + Console.WriteLine(~u); + Console.WriteLine(-i); + } + + public unsafe int* PtrArithmetic(int* ptr) + { + return ptr + i; + } + + public unsafe nint* PtrArithmetic(nint* ptr) + { + return ptr + u; + } + + public object[] Boxing() + { + return new object[10] { + 1, + (nint)2, + 3L, + 4u, + (nuint)5u, + 6uL, + int.MaxValue, + (nint)int.MaxValue, + i64, + (nint)i64 + }; + } + + public NativeInts GetInstance(int i) + { + return this; + } + + public void CompoundAssign() + { + GetInstance(0).i += i32; + checked { + GetInstance(1).i += i32; + } + GetInstance(2).u *= 2u; + checked { + GetInstance(3).u *= 2u; + } + GetInstance(4).intptr += (nint)i32; + checked { + // Note: the cast is necessary here, without it we'd call IntPtr.op_Addition + // but that is always unchecked. + GetInstance(5).intptr += (nint)i32; + } + // multiplication results in compiler-error without the cast + GetInstance(6).intptr *= (nint)2; + + GetInstance(7).i <<= i32; + } + + public void LocalTypeFromStore() + { + nint num = 42; + IntPtr zero = IntPtr.Zero; + nint zero2 = IntPtr.Zero; + nuint num2 = 43u; + nint num3 = i; + IntPtr intPtr = intptr; + + Console.WriteLine(); + zero2 = 1; + Console.WriteLine(); + + intptr = num; + intptr = zero; + intptr = zero2; + uintptr = num2; + intptr = num3; + intptr = intPtr; + } + + + public void LocalTypeFromUse() + { + IntPtr intPtr = intptr; + nint num = intptr; + + Console.WriteLine(); + + intptr = intPtr; + i = num + 1; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs index de17663e9..697bcfd06 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs @@ -146,9 +146,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void LocalVariables((int, int) a) { - (int, int) valueTuple = (a.Item1 + a.Item2, a.Item1 * a.Item2); - Console.WriteLine(valueTuple.ToString()); - Console.WriteLine(valueTuple.GetType().FullName); + (int, int) tuple = (a.Item1 + a.Item2, a.Item1 * a.Item2); + Console.WriteLine(tuple.ToString()); + Console.WriteLine(tuple.GetType().FullName); } public void Foreach(IEnumerable<(int, string)> input) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 785f8a58b..5e5191066 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -165,6 +165,7 @@ namespace ICSharpCode.Decompiler.CSharp new HighLevelLoopTransform(), new ReduceNestingTransform(), new IntroduceDynamicTypeOnLocals(), + new IntroduceNativeIntTypeOnLocals(), new AssignVariableNames(), }; } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs b/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs index cc6fa8015..530f5aa48 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs @@ -17,6 +17,7 @@ namespace ICSharpCode.Decompiler.CSharp CSharp7_2 = 702, CSharp7_3 = 703, CSharp8_0 = 800, + Preview = 900, Latest = 0x7FFFFFFF } } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 5ff0113f8..c0c583b00 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -109,15 +109,10 @@ namespace ICSharpCode.Decompiler.CSharp if (!allowImplicitConversion) { if (expr is NullReferenceExpression && rr.Type.Kind != TypeKind.Null) { expr = new CastExpression(ConvertType(rr.Type), expr); - } else { - switch (rr.Type.GetDefinition()?.KnownTypeCode) { - case KnownTypeCode.SByte: - case KnownTypeCode.Byte: - case KnownTypeCode.Int16: - case KnownTypeCode.UInt16: - expr = new CastExpression(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)), expr); - break; - } + } else if (rr.Type.IsCSharpSmallIntegerType()) { + expr = new CastExpression(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)), expr); + } else if (rr.Type.IsCSharpNativeIntegerType()) { + expr = new CastExpression(new PrimitiveType(rr.Type.Name), expr); } } var exprRR = expr.Annotation(); @@ -572,7 +567,7 @@ namespace ICSharpCode.Decompiler.CSharp if (argUType.GetStackType().GetSize() < inst.UnderlyingResultType.GetSize() || argUType.Kind == TypeKind.Enum && argUType.IsSmallIntegerType() - || argUType.GetStackType() == StackType.I + || (argUType.GetStackType() == StackType.I && !argUType.IsCSharpNativeIntegerType()) || argUType.IsKnownType(KnownTypeCode.Boolean) || argUType.IsKnownType(KnownTypeCode.Char)) { @@ -581,13 +576,11 @@ namespace ICSharpCode.Decompiler.CSharp // Same if the argument is an enum based on a small integer type // (those don't undergo numeric promotion in C# the way non-enum small integer types do). // Same if the type is one that does not support ~ (IntPtr, bool and char). - StackType targetStackType = inst.UnderlyingResultType; - if (targetStackType == StackType.I) { - // IntPtr doesn't support operator ~. - // Note that it's OK to use a type that's larger than necessary. - targetStackType = StackType.I8; + Sign sign = context.TypeHint.GetSign(); + if (sign == Sign.None) { + sign = argUType.GetSign(); } - IType targetType = compilation.FindType(targetStackType.ToKnownTypeCode(argUType.GetSign())); + IType targetType = FindArithmeticType(inst.UnderlyingResultType, sign); if (inst.IsLifted) { targetType = NullableType.Create(compilation, targetType); } @@ -800,7 +793,7 @@ namespace ICSharpCode.Decompiler.CSharp } else if (rightUType.GetStackType() == inst.InputType && !rightUType.IsSmallIntegerType()) { targetType = rightUType; } else { - targetType = compilation.FindType(inst.InputType.ToKnownTypeCode(leftUType.GetSign())); + targetType = FindType(inst.InputType, leftUType.GetSign()); } } if (inst.IsLifted) { @@ -900,24 +893,14 @@ namespace ICSharpCode.Decompiler.CSharp } } - // Ensure the inputs have the correct sign: - KnownTypeCode inputType = KnownTypeCode.None; - switch (inst.InputType) { - case StackType.I: // In order to generate valid C# we need to treat (U)IntPtr as (U)Int64 in comparisons. - case StackType.I8: - inputType = inst.Sign == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64; - break; - case StackType.I4: - inputType = inst.Sign == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32; - break; - } - if (inputType != KnownTypeCode.None) { - IType targetType = compilation.FindType(inputType); + if (inst.InputType.IsIntegerType()) { + // Ensure the inputs have the correct sign: + IType inputType = FindArithmeticType(inst.InputType, inst.Sign); if (inst.IsLifted) { - targetType = NullableType.Create(compilation, targetType); + inputType = NullableType.Create(compilation, inputType); } - left = left.ConvertTo(targetType, this); - right = right.ConvertTo(targetType, this); + left = left.ConvertTo(inputType, this); + right = right.ConvertTo(inputType, this); } return new BinaryOperatorExpression(left.Expression, op, right.Expression) .WithILInstruction(inst) @@ -993,22 +976,22 @@ namespace ICSharpCode.Decompiler.CSharp { switch (inst.Operator) { case BinaryNumericOperator.Add: - return HandleBinaryNumeric(inst, BinaryOperatorType.Add); + return HandleBinaryNumeric(inst, BinaryOperatorType.Add, context); case BinaryNumericOperator.Sub: - return HandleBinaryNumeric(inst, BinaryOperatorType.Subtract); + return HandleBinaryNumeric(inst, BinaryOperatorType.Subtract, context); case BinaryNumericOperator.Mul: - return HandleBinaryNumeric(inst, BinaryOperatorType.Multiply); + return HandleBinaryNumeric(inst, BinaryOperatorType.Multiply, context); case BinaryNumericOperator.Div: return HandlePointerSubtraction(inst) - ?? HandleBinaryNumeric(inst, BinaryOperatorType.Divide); + ?? HandleBinaryNumeric(inst, BinaryOperatorType.Divide, context); case BinaryNumericOperator.Rem: - return HandleBinaryNumeric(inst, BinaryOperatorType.Modulus); + return HandleBinaryNumeric(inst, BinaryOperatorType.Modulus, context); case BinaryNumericOperator.BitAnd: - return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseAnd); + return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseAnd, context); case BinaryNumericOperator.BitOr: - return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseOr); + return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseOr, context); case BinaryNumericOperator.BitXor: - return HandleBinaryNumeric(inst, BinaryOperatorType.ExclusiveOr); + return HandleBinaryNumeric(inst, BinaryOperatorType.ExclusiveOr, context); case BinaryNumericOperator.ShiftLeft: return HandleShift(inst, BinaryOperatorType.ShiftLeft); case BinaryNumericOperator.ShiftRight: @@ -1158,12 +1141,9 @@ namespace ICSharpCode.Decompiler.CSharp TranslatedExpression EnsureIntegerType(TranslatedExpression expr) { - if (!expr.Type.IsCSharpPrimitiveIntegerType()) { + if (!expr.Type.IsCSharpPrimitiveIntegerType() && !expr.Type.IsCSharpNativeIntegerType()) { // pointer arithmetic accepts all primitive integer types, but no enums etc. - StackType targetType = expr.Type.GetStackType() == StackType.I4 ? StackType.I4 : StackType.I8; - expr = expr.ConvertTo( - compilation.FindType(targetType.ToKnownTypeCode(expr.Type.GetSign())), - this); + expr = expr.ConvertTo(FindArithmeticType(expr.Type.GetStackType(), expr.Type.GetSign()), this); } return expr; } @@ -1254,7 +1234,7 @@ namespace ICSharpCode.Decompiler.CSharp } } - TranslatedExpression HandleBinaryNumeric(BinaryNumericInstruction inst, BinaryOperatorType op) + TranslatedExpression HandleBinaryNumeric(BinaryNumericInstruction inst, BinaryOperatorType op, TranslationContext context) { var resolverWithOverflowCheck = resolver.WithCheckForOverflow(inst.CheckForOverflow); var left = Translate(inst.Left); @@ -1276,11 +1256,12 @@ namespace ICSharpCode.Decompiler.CSharp if (op == BinaryOperatorType.Subtract && inst.Left.MatchLdcI(0)) { IType rightUType = NullableType.GetUnderlyingType(right.Type); - if (rightUType.IsKnownType(KnownTypeCode.Int32) || rightUType.IsKnownType(KnownTypeCode.Int64) || rightUType.IsCSharpSmallIntegerType()) { + if (rightUType.IsKnownType(KnownTypeCode.Int32) || rightUType.IsKnownType(KnownTypeCode.Int64) + || rightUType.IsCSharpSmallIntegerType() || rightUType.IsCSharpNativeIntegerType()) { // unary minus is supported on signed int and long, and on the small integer types (since they promote to int) var uoe = new UnaryOperatorExpression(UnaryOperatorType.Minus, right.Expression); uoe.AddAnnotation(inst.CheckForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); - var resultType = rightUType.IsKnownType(KnownTypeCode.Int64) ? rightUType : compilation.FindType(KnownTypeCode.Int32); + var resultType = FindArithmeticType(inst.RightInputType, Sign.Signed); if (inst.IsLifted) resultType = NullableType.Create(compilation, resultType); return uoe.WithILInstruction(inst).WithRR(new OperatorResolveResult( @@ -1312,8 +1293,12 @@ namespace ICSharpCode.Decompiler.CSharp || !IsCompatibleWithSign(left.Type, inst.Sign) || !IsCompatibleWithSign(right.Type, inst.Sign)) { // Left and right operands are incompatible, so convert them to a common type - StackType targetStackType = inst.UnderlyingResultType == StackType.I ? StackType.I8 : inst.UnderlyingResultType; - IType targetType = compilation.FindType(targetStackType.ToKnownTypeCode(inst.Sign)); + Sign sign = inst.Sign; + if (sign == Sign.None) { + // If the sign doesn't matter, try to use the same sign as expected by the context + sign = context.TypeHint.GetSign(); + } + IType targetType = FindArithmeticType(inst.UnderlyingResultType, sign); left = left.ConvertTo(NullableType.IsNullable(left.Type) ? NullableType.Create(compilation, targetType) : targetType, this); right = right.ConvertTo(NullableType.IsNullable(right.Type) ? NullableType.Create(compilation, targetType) : targetType, this); rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult); @@ -1327,6 +1312,39 @@ namespace ICSharpCode.Decompiler.CSharp return resultExpr; } + /// + /// Gets a type matching the stack type and sign. + /// + IType FindType(StackType stackType, Sign sign) + { + if (stackType == StackType.I && settings.NativeIntegers) { + return sign == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt; + } else { + return compilation.FindType(stackType.ToKnownTypeCode(sign)); + } + } + + /// + /// Gets a type used for performing arithmetic with the stack type and sign. + /// + /// This may result in a larger type than requested when the selected C# version + /// doesn't support native integers. + /// Should only be used after a call to PrepareArithmeticArgument() + /// to ensure that we're not preserving extra bits from an oversized TranslatedExpression. + /// + IType FindArithmeticType(StackType stackType, Sign sign) + { + if (stackType == StackType.I) { + if (settings.NativeIntegers) { + return sign == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt; + } else { + // If native integers are not available, use 64-bit arithmetic instead + stackType = StackType.I8; + } + } + return compilation.FindType(stackType.ToKnownTypeCode(sign)); + } + /// /// Handle oversized arguments needing truncation; and avoid IntPtr/pointers in arguments. /// @@ -1339,17 +1357,17 @@ namespace ICSharpCode.Decompiler.CSharp if (argStackType.IsIntegerType() && argStackType.GetSize() < argUType.GetSize()) { // If the argument is oversized (needs truncation to match stack size of its ILInstruction), // perform the truncation now. - IType targetType = compilation.FindType(argStackType.ToKnownTypeCode(sign)); + IType targetType = FindType(argStackType, sign); argUType = targetType; if (isLifted) targetType = NullableType.Create(compilation, targetType); arg = arg.ConvertTo(targetType, this); } - if (argUType.GetStackType() == StackType.I) { + if (argUType.IsKnownType(KnownTypeCode.IntPtr) || argUType.IsKnownType(KnownTypeCode.UIntPtr)) { // None of the operators we might want to apply are supported by IntPtr/UIntPtr. // Also, pointer arithmetic has different semantics (works in number of elements, not bytes). // So any inputs of size StackType.I must be converted to long/ulong. - IType targetType = compilation.FindType(StackType.I8.ToKnownTypeCode(sign)); + IType targetType = FindArithmeticType(StackType.I, sign); if (isLifted) targetType = NullableType.Create(compilation, targetType); arg = arg.ConvertTo(targetType, this); @@ -1385,6 +1403,8 @@ namespace ICSharpCode.Decompiler.CSharp var left = Translate(inst.Left); var right = Translate(inst.Right); + left = PrepareArithmeticArgument(left, inst.LeftInputType, inst.Sign, inst.IsLifted); + Sign sign = inst.Sign; var leftUType = NullableType.GetUnderlyingType(left.Type); if (leftUType.IsCSharpSmallIntegerType() && sign != Sign.Unsigned && inst.UnderlyingResultType == StackType.I4) { @@ -1396,12 +1416,7 @@ namespace ICSharpCode.Decompiler.CSharp // if we don't need a specific sign, prefer keeping that of the input: sign = leftUType.GetSign(); } - IType targetType; - if (inst.UnderlyingResultType == StackType.I4) { - targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32); - } else { - targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64); - } + IType targetType = FindArithmeticType(inst.UnderlyingResultType, sign); if (NullableType.IsNullable(left.Type)) { targetType = NullableType.Create(compilation, targetType); } @@ -1562,10 +1577,7 @@ namespace ICSharpCode.Decompiler.CSharp } } else { IType targetType = NullableType.GetUnderlyingType(target.Type).GetEnumUnderlyingType(); - if (NullableType.IsNullable(value.Type)) { - targetType = NullableType.Create(compilation, targetType); - } - value = value.ConvertTo(targetType, this, inst.CheckForOverflow, allowImplicitConversion: true); + value = ConvertValue(value, targetType); } break; case AssignmentOperatorType.Multiply: @@ -1575,10 +1587,7 @@ namespace ICSharpCode.Decompiler.CSharp case AssignmentOperatorType.BitwiseOr: case AssignmentOperatorType.ExclusiveOr: { IType targetType = NullableType.GetUnderlyingType(target.Type); - if (NullableType.IsNullable(value.Type)) { - targetType = NullableType.Create(compilation, targetType); - } - value = value.ConvertTo(targetType, this, inst.CheckForOverflow, allowImplicitConversion: true); + value = ConvertValue(value, targetType); break; } } @@ -1590,6 +1599,20 @@ namespace ICSharpCode.Decompiler.CSharp resultExpr.Expression.AddAnnotation(inst.CheckForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); } return resultExpr; + + TranslatedExpression ConvertValue(TranslatedExpression value, IType targetType) + { + bool allowImplicitConversion = true; + if (targetType.GetStackType() == StackType.I) { + // Force explicit cast for (U)IntPtr, keep allowing implicit conversion only for n(u)int + allowImplicitConversion = targetType.IsCSharpNativeIntegerType(); + targetType = targetType.GetSign() == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt; + } + if (NullableType.IsNullable(value.Type)) { + targetType = NullableType.Create(compilation, targetType); + } + return value.ConvertTo(targetType, this, inst.CheckForOverflow, allowImplicitConversion); + } } TranslatedExpression HandleCompoundShift(NumericCompoundAssign inst, AssignmentOperatorType op) @@ -1631,7 +1654,11 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitConv(Conv inst, TranslationContext context) { - var arg = Translate(inst.Argument); + Sign hintSign = inst.InputSign; + if (hintSign == Sign.None) { + hintSign = context.TypeHint.GetSign(); + } + var arg = Translate(inst.Argument, typeHint: FindArithmeticType(inst.InputType, hintSign)); IType inputType = NullableType.GetUnderlyingType(arg.Type); StackType inputStackType = inst.InputType; // Note: we're dealing with two conversions here: @@ -1650,8 +1677,15 @@ namespace ICSharpCode.Decompiler.CSharp IType GetType(KnownTypeCode typeCode) { IType type = compilation.FindType(typeCode); - if (inst.IsLifted) + // Prefer n(u)int over (U)IntPtr + if (typeCode == KnownTypeCode.IntPtr && settings.NativeIntegers && !type.Equals(context.TypeHint)) { + type = SpecialType.NInt; + } else if (typeCode == KnownTypeCode.UIntPtr && settings.NativeIntegers && !type.Equals(context.TypeHint)) { + type = SpecialType.NUInt; + } + if (inst.IsLifted) { type = NullableType.Create(compilation, type); + } return type; } @@ -2272,19 +2306,14 @@ namespace ICSharpCode.Decompiler.CSharp .WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.Type, ReferenceKind.Ref)); } - TranslatedExpression TranslateArrayIndex(ILInstruction i, bool expectSystemIndex = false) + TranslatedExpression TranslateArrayIndex(ILInstruction i) { var input = Translate(i); - KnownTypeCode targetType; - if (i.ResultType == StackType.I4) { - if (input.Type.IsSmallIntegerType() && input.Type.Kind != TypeKind.Enum) { - return input; // we don't need a cast, just let small integers be promoted to int - } - targetType = input.Type.GetSign() == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32; - } else { - targetType = input.Type.GetSign() == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64; + if (i.ResultType == StackType.I4 && input.Type.IsSmallIntegerType() && input.Type.Kind != TypeKind.Enum) { + return input; // we don't need a cast, just let small integers be promoted to int } - return input.ConvertTo(compilation.FindType(targetType), this); + IType targetType = FindArithmeticType(i.ResultType, input.Type.GetSign()); + return input.ConvertTo(targetType, this); } internal static bool IsUnboxAnyWithIsInst(UnboxAny unboxAny, IsInst isInst) @@ -2337,8 +2366,17 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitBox(Box inst, TranslationContext context) { + IType targetType = inst.Type; + var arg = Translate(inst.Argument, typeHint: targetType); + if (settings.NativeIntegers && !arg.Type.Equals(targetType)) { + if (targetType.IsKnownType(KnownTypeCode.IntPtr)) { + targetType = SpecialType.NInt; + } else if (targetType.IsKnownType(KnownTypeCode.UIntPtr)) { + targetType = SpecialType.NUInt; + } + } + arg = arg.ConvertTo(targetType, this); var obj = compilation.FindType(KnownTypeCode.Object); - var arg = Translate(inst.Argument, typeHint: inst.Type).ConvertTo(inst.Type, this); return new CastExpression(ConvertType(obj), arg.Expression) .WithILInstruction(inst) .WithRR(new ConversionResolveResult(obj, arg.ResolveResult, Conversion.BoxingConversion)); diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 8701e1dd9..eae329895 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -326,10 +326,12 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /* char */ { false, true , true , true , true , true }, /* sbyte */ { true , false, true , false, true , false }, /* byte */ { true , true , true , true , true , true }, - /* short */ { false, false, true , false, true , false }, - /* ushort */ { false, false, true , true , true , true }, - /* int */ { false, false, false, false, true , false }, - /* uint */ { false, false, false, false, true , true }, + /* short */ { true , false, true , false, true , false }, + /* ushort */ { false, true , true , true , true , true }, + /* int */ { false, false, true , false, true , false }, + /* uint */ { false, false, false, true , true , true }, + /* long */ { false, false, false, false, true , false }, + /* ulong */ { false, false, false, false, false, true }, }; bool ImplicitNumericConversion(IType fromType, IType toType) @@ -337,7 +339,29 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver // C# 4.0 spec: §6.1.2 TypeCode from = ReflectionHelper.GetTypeCode(fromType); + if (from == TypeCode.Empty) { + // When converting from a native-sized integer, treat it as 64-bits + switch (fromType.Kind) { + case TypeKind.NInt: + from = TypeCode.Int64; + break; + case TypeKind.NUInt: + from = TypeCode.UInt64; + break; + } + } TypeCode to = ReflectionHelper.GetTypeCode(toType); + if (to == TypeCode.Empty) { + // When converting to a native-sized integer, only 32-bits can be stored safely + switch (toType.Kind) { + case TypeKind.NInt: + to = TypeCode.Int32; + break; + case TypeKind.NUInt: + to = TypeCode.UInt32; + break; + } + } if (to >= TypeCode.Single && to <= TypeCode.Decimal) { // Conversions to float/double/decimal exist from all integral types, // and there's a conversion from float to double. @@ -345,7 +369,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver || from == TypeCode.Single && to == TypeCode.Double; } else { // Conversions to integral types: look at the table - return from >= TypeCode.Char && from <= TypeCode.UInt32 + return from >= TypeCode.Char && from <= TypeCode.UInt64 && to >= TypeCode.Int16 && to <= TypeCode.UInt64 && implicitNumericConversionLookup[from - TypeCode.Char, to - TypeCode.Int16]; } @@ -353,6 +377,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool IsNumericType(IType type) { + switch (type.Kind) { + case TypeKind.NInt: + case TypeKind.NUInt: + return true; + } TypeCode c = ReflectionHelper.GetTypeCode(type); return c >= TypeCode.Char && c <= TypeCode.Decimal; } @@ -693,7 +722,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return false; // C# 4.0 spec: §6.1.9 TypeCode fromTypeCode = ReflectionHelper.GetTypeCode(rr.Type); - TypeCode toTypeCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(toType)); + toType = NullableType.GetUnderlyingType(toType); + TypeCode toTypeCode = ReflectionHelper.GetTypeCode(toType); + if (toType.Kind == TypeKind.NUInt) { + toTypeCode = TypeCode.UInt32; + } if (fromTypeCode == TypeCode.Int64) { long val = (long)rr.ConstantValue; return val >= 0 && toTypeCode == TypeCode.UInt64; @@ -772,6 +805,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool IsIntegerType(IType type) { + switch (type.Kind) { + case TypeKind.NInt: + case TypeKind.NUInt: + return true; + } TypeCode c = ReflectionHelper.GetTypeCode(type); return c >= TypeCode.SByte && c <= TypeCode.UInt64; } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index 3bc6f15f5..d87e53b67 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -449,14 +449,20 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver // C# 4.0 spec: §7.6.9 Postfix increment and decrement operators // C# 4.0 spec: §7.7.5 Prefix increment and decrement operators TypeCode code = ReflectionHelper.GetTypeCode(type); - if ((code >= TypeCode.Char && code <= TypeCode.Decimal) || type.Kind == TypeKind.Enum || type.Kind == TypeKind.Pointer) + if ((code >= TypeCode.Char && code <= TypeCode.Decimal) || type.Kind == TypeKind.Enum || type.Kind == TypeKind.Pointer || type.IsCSharpNativeIntegerType()) return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); else return new ErrorResolveResult(expression.Type); case UnaryOperatorType.Plus: + if (type.IsCSharpNativeIntegerType()) { + return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); + } methodGroup = operators.UnaryPlusOperators; break; case UnaryOperatorType.Minus: + if (type.IsCSharpNativeIntegerType()) { + return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); + } methodGroup = CheckForOverflow ? operators.CheckedUnaryMinusOperators : operators.UncheckedUnaryMinusOperators; break; case UnaryOperatorType.Not: @@ -472,7 +478,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver rr = WithCheckForOverflow(false).ResolveCast(type, rr); if (rr.IsCompileTimeConstant) return rr; - } + } + return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); + } else if (type.IsCSharpNativeIntegerType()) { return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); } else { methodGroup = operators.BitwiseComplementOperators; @@ -774,6 +782,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return HandleEnumComparison(op, rhsType, isNullable, lhs, rhs); } else if (lhsType is PointerType && rhsType is PointerType) { return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs); + } else if (lhsType.IsCSharpNativeIntegerType() || rhsType.IsCSharpNativeIntegerType()) { + if (lhsType.Equals(rhsType)) + return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs, isLifted: isNullable); + else + return new ErrorResolveResult(compilation.FindType(KnownTypeCode.Boolean)); } if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality) { if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true) { @@ -855,6 +868,15 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver default: throw new InvalidOperationException(); } + if (lhsType.IsCSharpNativeIntegerType() || rhsType.IsCSharpNativeIntegerType()) { + if (lhsType.Equals(rhsType)) { + return BinaryOperatorResolveResult( + isNullable ? NullableType.Create(compilation, lhsType) : lhsType, + lhs, op, rhs, isLifted: isNullable); + } + // mixing nint/nuint is not allowed + return new ErrorResolveResult(lhsType); + } OverloadResolution builtinOperatorOR = CreateOverloadResolution(new[] { lhs, rhs }); foreach (var candidate in methodGroup) { builtinOperatorOR.AddCandidate(candidate); @@ -1000,8 +1022,22 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool BinaryNumericPromotion(bool isNullable, ref ResolveResult lhs, ref ResolveResult rhs, bool allowNullableConstants) { // C# 4.0 spec: §7.3.6.2 - TypeCode lhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(lhs.Type)); - TypeCode rhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(rhs.Type)); + var lhsUType = NullableType.GetUnderlyingType(lhs.Type); + var rhsUType = NullableType.GetUnderlyingType(rhs.Type); + TypeCode lhsCode = ReflectionHelper.GetTypeCode(lhsUType); + TypeCode rhsCode = ReflectionHelper.GetTypeCode(rhsUType); + // Treat C# 9 native integers as falling between int and long. + // However they don't have a TypeCode, so we hack around that here: + if (lhsUType.Kind == TypeKind.NInt) { + lhsCode = TypeCode.Int32; + } else if (lhsUType.Kind == TypeKind.NUInt) { + lhsCode = TypeCode.UInt32; + } + if (rhsUType.Kind == TypeKind.NInt) { + rhsCode = TypeCode.Int32; + } else if (rhsUType.Kind == TypeKind.NUInt) { + rhsCode = TypeCode.UInt32; + } // if one of the inputs is the null literal, promote that to the type of the other operand if (isNullable && lhs.Type.Kind == TypeKind.Null && rhsCode >= TypeCode.Boolean && rhsCode <= TypeCode.Decimal) { lhs = CastTo(rhsCode, isNullable, lhs, allowNullableConstants); @@ -1018,7 +1054,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver if (lhsCode == TypeCode.Decimal || rhsCode == TypeCode.Decimal) { targetType = TypeCode.Decimal; bindingError = (lhsCode == TypeCode.Single || lhsCode == TypeCode.Double - || rhsCode == TypeCode.Single || rhsCode == TypeCode.Double); + || rhsCode == TypeCode.Single || rhsCode == TypeCode.Double); } else if (lhsCode == TypeCode.Double || rhsCode == TypeCode.Double) { targetType = TypeCode.Double; } else if (lhsCode == TypeCode.Single || rhsCode == TypeCode.Single) { @@ -1026,10 +1062,19 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } else if (lhsCode == TypeCode.UInt64 || rhsCode == TypeCode.UInt64) { targetType = TypeCode.UInt64; bindingError = IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs); - } else if (lhsCode == TypeCode.Int64 || rhsCode == TypeCode.Int64) { - targetType = TypeCode.Int64; + } else if (lhsUType.Kind == TypeKind.NUInt || rhsUType.Kind == TypeKind.NUInt) { + bindingError = IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs); + lhs = CastTo(SpecialType.NUInt, isNullable, lhs, allowNullableConstants); + rhs = CastTo(SpecialType.NUInt, isNullable, rhs, allowNullableConstants); + return !bindingError; } else if (lhsCode == TypeCode.UInt32 || rhsCode == TypeCode.UInt32) { targetType = (IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs)) ? TypeCode.Int64 : TypeCode.UInt32; + } else if (lhsCode == TypeCode.Int64 || rhsCode == TypeCode.Int64) { + targetType = TypeCode.Int64; + } else if (lhsUType.Kind == TypeKind.NInt || rhsUType.Kind == TypeKind.NInt) { + lhs = CastTo(SpecialType.NInt, isNullable, lhs, allowNullableConstants); + rhs = CastTo(SpecialType.NInt, isNullable, rhs, allowNullableConstants); + return !bindingError; } else { targetType = TypeCode.Int32; } @@ -1063,17 +1108,21 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return false; } } - + ResolveResult CastTo(TypeCode targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants) { - IType elementType = compilation.FindType(targetType); - IType nullableType = MakeNullable(elementType, isNullable); + return CastTo(compilation.FindType(targetType), isNullable, expression, allowNullableConstants); + } + + ResolveResult CastTo(IType targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants) + { + IType nullableType = MakeNullable(targetType, isNullable); if (nullableType.Equals(expression.Type)) return expression; if (allowNullableConstants && expression.IsCompileTimeConstant) { if (expression.ConstantValue == null) return new ConstantResolveResult(nullableType, null); - ResolveResult rr = ResolveCast(elementType, expression); + ResolveResult rr = ResolveCast(targetType, expression); if (rr.IsError) return rr; Debug.Assert(rr.IsCompileTimeConstant); @@ -1262,7 +1311,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver // C# 4.0 spec: §7.7.6 Cast expressions Conversion c = conversions.ExplicitConversion(expression, targetType); if (expression.IsCompileTimeConstant && !c.IsUserDefined) { - TypeCode code = ReflectionHelper.GetTypeCode(targetType); + IType underlyingType = targetType.GetEnumUnderlyingType(); + TypeCode code = ReflectionHelper.GetTypeCode(underlyingType); if (code >= TypeCode.Boolean && code <= TypeCode.Decimal && expression.ConstantValue != null) { try { return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue)); @@ -1276,16 +1326,15 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return new ConstantResolveResult(targetType, expression.ConstantValue); else return new ErrorResolveResult(targetType); - } else if (targetType.Kind == TypeKind.Enum) { - code = ReflectionHelper.GetTypeCode(GetEnumUnderlyingType(targetType)); - if (code >= TypeCode.SByte && code <= TypeCode.UInt64 && expression.ConstantValue != null) { - try { - return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue)); - } catch (OverflowException) { - return new ErrorResolveResult(targetType); - } catch (InvalidCastException) { - return new ErrorResolveResult(targetType); - } + } else if ((underlyingType.Kind == TypeKind.NInt || underlyingType.Kind == TypeKind.NUInt) && expression.ConstantValue != null) { + code = (underlyingType.Kind == TypeKind.NInt ? TypeCode.Int32 : TypeCode.UInt32); + try { + return new ConstantResolveResult(targetType, Util.CSharpPrimitiveCast.Cast(code, expression.ConstantValue, checkForOverflow: true)); + } catch (OverflowException) { + // If constant value doesn't fit into 32-bits, the conversion is not a compile-time constant + return new ConversionResolveResult(targetType, expression, c, checkForOverflow); + } catch (InvalidCastException) { + return new ErrorResolveResult(targetType); } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index c6b563bec..3fe6a5f02 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -303,7 +303,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax astType = ConvertTypeHelper(pt.GenericType, pt.TypeArguments); break; default: - astType = MakeSimpleType(type.Name); + switch (type.Kind) { + case TypeKind.Dynamic: + case TypeKind.NInt: + case TypeKind.NUInt: + astType = new PrimitiveType(type.Name); + break; + default: + astType = MakeSimpleType(type.Name); + break; + } break; } if (type.Nullability == Nullability.Nullable) { @@ -713,7 +722,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return ace; } else if (rr.IsCompileTimeConstant) { var expr = ConvertConstantValue(rr.Type, rr.ConstantValue); - if (isBoxing && rr.Type.IsCSharpSmallIntegerType()) { + if (isBoxing && (rr.Type.IsCSharpSmallIntegerType() || rr.Type.IsCSharpNativeIntegerType())) { // C# does not have small integer literal types. // We need to add a cast so that the integer literal gets boxed as the correct type. expr = new CastExpression(ConvertType(rr.Type), expr); @@ -784,21 +793,24 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax if (type.IsKnownType(KnownTypeCode.Double) || type.IsKnownType(KnownTypeCode.Single)) return ConvertFloatingPointLiteral(type, constantValue); IType literalType = type; - bool smallInteger = type.IsCSharpSmallIntegerType(); - if (smallInteger) { + bool integerTypeMismatch = type.IsCSharpSmallIntegerType() || type.IsCSharpNativeIntegerType(); + if (integerTypeMismatch) { // C# does not have integer literals of small integer types, // use `int` literal instead. - constantValue = CSharpPrimitiveCast.Cast(TypeCode.Int32, constantValue, false); - literalType = type.GetDefinition().Compilation.FindType(KnownTypeCode.Int32); + // It also doesn't have native integer literals, those also use `int` (or `uint` for `nuint`). + bool unsigned = type.Kind == TypeKind.NUInt; + constantValue = CSharpPrimitiveCast.Cast(unsigned ? TypeCode.UInt32 : TypeCode.Int32, constantValue, false); + var compilation = resolver?.Compilation ?? expectedType.GetDefinition()?.Compilation; + literalType = compilation?.FindType(unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32); } LiteralFormat format = LiteralFormat.None; if (PrintIntegralValuesAsHex) { format = LiteralFormat.HexadecimalNumber; } expr = new PrimitiveExpression(constantValue, format); - if (AddResolveResultAnnotations) + if (AddResolveResultAnnotations && literalType != null) expr.AddAnnotation(new ConstantResolveResult(literalType, constantValue)); - if (smallInteger && !type.Equals(expectedType)) { + if (integerTypeMismatch && !type.Equals(expectedType)) { expr = new CastExpression(ConvertType(type), expr); } return expr; @@ -813,7 +825,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax // find IType of constant in compilation. var constantType = expectedType; if (!expectedType.IsKnownType(info.Type)) { - var compilation = expectedType.GetDefinition().Compilation; + var compilation = resolver?.Compilation ?? expectedType.GetDefinition()?.Compilation; + if (compilation == null) + return false; constantType = compilation.FindType(info.Type); } // if the field definition cannot be found, do not generate a reference to the field. diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 4b04441a3..3365867da 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -305,22 +305,29 @@ namespace ICSharpCode.Decompiler.CSharp // Direct cast only works correctly for IntPtr -> long. // IntPtr -> int works correctly only in checked context. // Everything else can be worked around by casting via long. - if (!(targetType.IsKnownType(KnownTypeCode.Int64) || checkForOverflow && targetType.IsKnownType(KnownTypeCode.Int32))) { - return this.ConvertTo(compilation.FindType(KnownTypeCode.Int64), expressionBuilder, checkForOverflow) - .ConvertTo(targetType, expressionBuilder, checkForOverflow); + if (!(targetType.IsKnownType(KnownTypeCode.Int64) || targetType.Kind == TypeKind.NInt || checkForOverflow && targetType.IsKnownType(KnownTypeCode.Int32))) { + var convertVia = expressionBuilder.settings.NativeIntegers ? SpecialType.NInt : compilation.FindType(KnownTypeCode.Int64); + return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow) + .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); } } else if (type.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion from UIntPtr // Direct cast only works correctly for UIntPtr -> ulong. // UIntPtr -> uint works correctly only in checked context. // Everything else can be worked around by casting via ulong. - if (!(targetType.IsKnownType(KnownTypeCode.UInt64) || checkForOverflow && targetType.IsKnownType(KnownTypeCode.UInt32))) { - return this.ConvertTo(compilation.FindType(KnownTypeCode.UInt64), expressionBuilder, checkForOverflow) - .ConvertTo(targetType, expressionBuilder, checkForOverflow); + if (!(targetType.IsKnownType(KnownTypeCode.UInt64) || targetType.Kind == TypeKind.NUInt || checkForOverflow && targetType.IsKnownType(KnownTypeCode.UInt32))) { + var convertVia = expressionBuilder.settings.NativeIntegers ? SpecialType.NUInt : compilation.FindType(KnownTypeCode.UInt64); + return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow) + .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); } } if (targetUType.IsKnownType(KnownTypeCode.IntPtr)) { // Conversion to IntPtr - if (type.IsKnownType(KnownTypeCode.Int32)) { - // normal casts work for int (both in checked and unchecked context) + if (type.IsKnownType(KnownTypeCode.Int32) || type.Kind == TypeKind.NInt) { + // normal casts work for int/nint (both in checked and unchecked context) + // note that pointers only allow normal casts in unchecked contexts + } else if (expressionBuilder.settings.NativeIntegers) { + // if native integer types are available, prefer using those + return this.ConvertTo(SpecialType.NInt, expressionBuilder, checkForOverflow) + .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); } else if (checkForOverflow) { // if overflow-checking is enabled, we can simply cast via long: // (and long itself works directly in checked context) @@ -328,17 +335,19 @@ namespace ICSharpCode.Decompiler.CSharp return this.ConvertTo(compilation.FindType(KnownTypeCode.Int64), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow); } - } else { + } else if (type.Kind != TypeKind.Pointer) { // If overflow-checking is disabled, the only way to truncate to native size // without throwing an exception in 32-bit mode is to use a pointer type. - if (type.Kind != TypeKind.Pointer) { - return this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow) - .ConvertTo(targetType, expressionBuilder, checkForOverflow); - } + return this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow) + .ConvertTo(targetType, expressionBuilder, checkForOverflow); } } else if (targetUType.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion to UIntPtr - if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind == TypeKind.Pointer) { - // normal casts work for uint and pointers (both in checked and unchecked context) + if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind == TypeKind.Pointer || type.Kind == TypeKind.NUInt) { + // normal casts work for uint/nuint and pointers (both in checked and unchecked context) + } else if (expressionBuilder.settings.NativeIntegers) { + // if native integer types are available, prefer using those + return this.ConvertTo(SpecialType.NUInt, expressionBuilder, checkForOverflow) + .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); } else if (checkForOverflow) { // if overflow-checking is enabled, we can simply cast via ulong: // (and ulong itself works directly in checked context) @@ -471,7 +480,7 @@ namespace ICSharpCode.Decompiler.CSharp return this; } } else { - if (targetType.Kind != TypeKind.Dynamic && type.Kind != TypeKind.Dynamic && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(type, targetType)) { + if (NormalizeTypeVisitor.IgnoreNullabilityAndTuples.EquivalentTypes(type, targetType)) { // avoid an explicit cast when types differ only in nullability of reference types return this; } @@ -505,6 +514,11 @@ namespace ICSharpCode.Decompiler.CSharp // If the cast was required for the old conversion, avoid making it implicit. return false; } + if (oldTargetType.Kind == TypeKind.NInt || oldTargetType.Kind == TypeKind.NUInt + || newTargetType.Kind == TypeKind.NInt || newTargetType.Kind == TypeKind.NUInt) { + // nint has identity conversion with IntPtr, but the two have different implicit conversions + return false; + } if (conversion.IsBoxingConversion) { return conversions.IsBoxingConversionOrInvolvingTypeParameter(inputType, newTargetType); } diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 3e8349bd2..e1b158b0a 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -114,10 +114,15 @@ namespace ICSharpCode.Decompiler staticLocalFunctions = false; ranges = false; } + if (languageVersion < CSharp.LanguageVersion.Preview) { + nativeIntegers = false; + } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { + if (nativeIntegers) + return CSharp.LanguageVersion.Preview; if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges) return CSharp.LanguageVersion.CSharp8_0; if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement) @@ -141,6 +146,23 @@ namespace ICSharpCode.Decompiler return CSharp.LanguageVersion.CSharp1; } + bool nativeIntegers = true; + + /// + /// Use C# 9 nint/nuint types. + /// + [Category("C# 9.0 (experimental)")] + [Description("DecompilerSettings.NativeIntegers")] + public bool NativeIntegers { + get { return nativeIntegers; } + set { + if (nativeIntegers != value) { + nativeIntegers = value; + OnPropertyChanged(); + } + } + } + bool anonymousMethods = true; /// diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 5535d7d43..a00763439 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -78,6 +78,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs b/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs index 32c0230a3..1bca64d88 100644 --- a/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs +++ b/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs @@ -202,7 +202,7 @@ namespace ICSharpCode.Decompiler.IL case BinaryNumericOperator.BitXor: var left = bni.Left.InferType(compilation); var right = bni.Right.InferType(compilation); - if (left.Equals(right) && (left.IsCSharpPrimitiveIntegerType() || left.IsKnownType(KnownTypeCode.Boolean))) + if (left.Equals(right) && (left.IsCSharpPrimitiveIntegerType() || left.IsCSharpNativeIntegerType() || left.IsKnownType(KnownTypeCode.Boolean))) return left; else return SpecialType.UnknownType; diff --git a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs index 6e27ee85d..3f1b56725 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs @@ -128,7 +128,7 @@ namespace ICSharpCode.Decompiler.IL /// Gets whether the instruction checks for overflow. /// public readonly bool CheckForOverflow; - + /// /// For integer operations that depend on the sign, specifies whether the operation /// is signed or unsigned. @@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL CompoundTargetKind targetKind, ILInstruction value, IType type, CompoundEvalMode evalMode) : base(OpCode.NumericCompoundAssign, evalMode, target, targetKind, value) { - Debug.Assert(IsBinaryCompatibleWithType(binary, type)); + Debug.Assert(IsBinaryCompatibleWithType(binary, type, null)); this.CheckForOverflow = binary.CheckForOverflow; this.Sign = binary.Sign; this.LeftInputType = binary.LeftInputType; @@ -164,11 +164,11 @@ namespace ICSharpCode.Decompiler.IL Debug.Assert(evalMode == CompoundEvalMode.EvaluatesToNewValue || (Operator == BinaryNumericOperator.Add || Operator == BinaryNumericOperator.Sub)); Debug.Assert(this.ResultType == (IsLifted ? StackType.O : UnderlyingResultType)); } - + /// /// Gets whether the specific binary instruction is compatible with a compound operation on the specified type. /// - internal static bool IsBinaryCompatibleWithType(BinaryNumericInstruction binary, IType type) + internal static bool IsBinaryCompatibleWithType(BinaryNumericInstruction binary, IType type, DecompilerSettings settings) { if (binary.IsLifted) { if (!NullableType.IsNullable(type)) @@ -201,6 +201,19 @@ namespace ICSharpCode.Decompiler.IL default: return false; // operator not supported on pointer types } + } else if (type.IsKnownType(KnownTypeCode.IntPtr) || type.IsKnownType(KnownTypeCode.UIntPtr)) { + // "target.intptr *= 2;" is compiler error, but + // "target.intptr *= (nint)2;" works + if (settings != null && !settings.NativeIntegers) { + // But if native integers are not available, we cannot use compound assignment. + return false; + } + // The trick with casting the RHS to n(u)int doesn't work for shifts: + switch (binary.Operator) { + case BinaryNumericOperator.ShiftLeft: + case BinaryNumericOperator.ShiftRight: + return false; + } } if (binary.Sign != Sign.None) { if (type.IsCSharpSmallIntegerType()) { diff --git a/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs b/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs index f1e7032dc..3064a4520 100644 --- a/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs +++ b/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs @@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL if (mul.CheckForOverflow != checkForOverflow) return null; if (elementSize > 0 && mul.Right.MatchLdcI(elementSize.Value) - || mul.Right.UnwrapConv(ConversionKind.SignExtend) is SizeOf sizeOf && sizeOf.Type.Equals(pointerElementType)) { + || mul.Right.UnwrapConv(ConversionKind.SignExtend) is SizeOf sizeOf && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(sizeOf.Type, pointerElementType)) { var countOffsetInst = mul.Left; if (unwrapZeroExtension) { countOffsetInst = countOffsetInst.UnwrapConv(ConversionKind.ZeroExtend); diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 02b25933c..d95cc63da 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -417,19 +417,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType); } - string name; - if (type is ArrayType) { - name = "array"; - } else if (type is PointerType) { - name = "ptr"; - } else if (type.Kind == TypeKind.TypeParameter || type.Kind == TypeKind.Unknown || type.Kind == TypeKind.Dynamic) { - name = "val"; - } else if (type.Kind == TypeKind.ByReference) { - name = "reference"; - } else if (type.IsAnonymousType()) { + 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; + } + if (type.IsAnonymousType()) { name = "anon"; } else if (type.Name.EndsWith("Exception", StringComparison.Ordinal)) { name = "ex"; + } else if (type.IsCSharpNativeIntegerType()) { + name = "num"; } else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) { name = type.Name; // remove the 'I' for interfaces diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs new file mode 100644 index 000000000..27962a359 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs @@ -0,0 +1,78 @@ +// Copyright (c) 2020 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 +// 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; +using System.Linq; +using System.Text; +using ICSharpCode.Decompiler.IL.Transforms; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL +{ + class IntroduceNativeIntTypeOnLocals : IILTransform + { + public void Run(ILFunction function, ILTransformContext context) + { + if (!context.Settings.NativeIntegers) + return; + foreach (var variable in function.Variables) { + if (variable.Kind != VariableKind.Local && + variable.Kind != VariableKind.StackSlot && + variable.Kind != VariableKind.ForeachLocal && + variable.Kind != VariableKind.UsingLocal) { + continue; + } + if (!(variable.Type.IsKnownType(KnownTypeCode.IntPtr) || variable.Type.IsKnownType(KnownTypeCode.UIntPtr))) + continue; + bool isUsedAsNativeInt = variable.LoadInstructions.Any(IsUsedAsNativeInt); + bool isAssignedNativeInt = variable.StoreInstructions.Any(store => IsNativeIntStore(store, context.TypeSystem)); + if (isUsedAsNativeInt || isAssignedNativeInt) { + variable.Type = variable.Type.GetSign() == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt; + } + } + } + + static bool IsUsedAsNativeInt(LdLoc load) + { + return load.Parent switch + { + BinaryNumericInstruction { UnderlyingResultType: StackType.I } => true, + BitNot { UnderlyingResultType: StackType.I } => true, + CallInstruction call => call.GetParameter(load.ChildIndex)?.Type.IsCSharpNativeIntegerType() ?? false, + _ => false, + }; + } + + static bool IsNativeIntStore(IStoreInstruction store, ICompilation compilation) + { + if (store is StLoc stloc) { + switch (stloc.Value) { + case BinaryNumericInstruction { UnderlyingResultType: StackType.I }: + return true; + case Conv { ResultType: StackType.I }: + return true; + default: + var inferredType = stloc.Value.InferType(compilation); + return inferredType.IsCSharpNativeIntegerType(); + } + } + return false; + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs index c99b15ce6..50df2c861 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs @@ -198,9 +198,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType) + static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType, DecompilerSettings settings) { - if (!NumericCompoundAssign.IsBinaryCompatibleWithType(binary, targetType)) + if (!NumericCompoundAssign.IsBinaryCompatibleWithType(binary, targetType, settings)) return false; if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow)) return false; // conv does not match binary operation @@ -305,7 +305,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (!IsMatchingCompoundLoad(binary.Left, compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable)) return false; - if (!ValidateCompoundAssign(binary, smallIntConv, targetType)) + if (!ValidateCompoundAssign(binary, smallIntConv, targetType, context.Settings)) return false; context.Step($"Compound assignment (binary.numeric)", compoundStore); finalizeMatch?.Invoke(context); @@ -656,7 +656,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (binary != null && (binary.Right.MatchLdcI(1) || binary.Right.MatchLdcF4(1) || binary.Right.MatchLdcF8(1))) { if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub)) return false; - if (!ValidateCompoundAssign(binary, conv, targetType)) + if (!ValidateCompoundAssign(binary, conv, targetType, context.Settings)) return false; stloc = binary.Left as StLoc; } else if (value is Call operatorCall && operatorCall.Method.IsOperator && operatorCall.Arguments.Count == 1) { @@ -731,7 +731,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub)) return false; - if (!ValidateCompoundAssign(binary, conv, targetType)) + if (!ValidateCompoundAssign(binary, conv, targetType, context.Settings)) return false; context.Step("TransformPostIncDecOperator (builtin)", inst); finalizeMatch?.Invoke(context); diff --git a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs index daef269bc..b024d4620 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs @@ -42,6 +42,8 @@ namespace ICSharpCode.Decompiler.TypeSystem { bool hasDynamicAttribute = false; bool[] dynamicAttributeData = null; + bool hasNativeIntegersAttribute = false; + bool[] nativeIntegersAttributeData = null; string[] tupleElementNames = null; Nullability nullability; Nullability[] nullableAttributeData = null; @@ -50,7 +52,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } else { nullability = Nullability.Oblivious; } - const TypeSystemOptions relevantOptions = TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple | TypeSystemOptions.NullabilityAnnotations; + const TypeSystemOptions relevantOptions = TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple | TypeSystemOptions.NullabilityAnnotations | TypeSystemOptions.NativeIntegers; if (attributes != null && (options & relevantOptions) != 0) { foreach (var attrHandle in attributes.Value) { var attr = metadata.GetCustomAttribute(attrHandle); @@ -65,6 +67,16 @@ namespace ICSharpCode.Decompiler.TypeSystem dynamicAttributeData = values.SelectArray(v => (bool)v.Value); } } + } else if ((options & TypeSystemOptions.NativeIntegers) != 0 && attrType.IsKnownType(metadata, KnownAttribute.NativeInteger)) { + hasNativeIntegersAttribute = true; + var ctor = attr.DecodeValue(Metadata.MetadataExtensions.minimalCorlibTypeProvider); + if (ctor.FixedArguments.Length == 1) { + var arg = ctor.FixedArguments[0]; + if (arg.Value is ImmutableArray> values + && values.All(v => v.Value is bool)) { + nativeIntegersAttributeData = values.SelectArray(v => (bool)v.Value); + } + } } else if ((options & TypeSystemOptions.Tuple) != 0 && attrType.IsKnownType(metadata, KnownAttribute.TupleElementNames)) { var ctor = attr.DecodeValue(Metadata.MetadataExtensions.minimalCorlibTypeProvider); if (ctor.FixedArguments.Length == 1) { @@ -88,11 +100,12 @@ namespace ICSharpCode.Decompiler.TypeSystem } } } - if (hasDynamicAttribute || nullability != Nullability.Oblivious || nullableAttributeData != null + if (hasDynamicAttribute || hasNativeIntegersAttribute || nullability != Nullability.Oblivious || nullableAttributeData != null || (options & (TypeSystemOptions.Tuple | TypeSystemOptions.KeepModifiers)) != TypeSystemOptions.KeepModifiers) { var visitor = new ApplyAttributeTypeVisitor( compilation, hasDynamicAttribute, dynamicAttributeData, + hasNativeIntegersAttribute, nativeIntegersAttributeData, options, tupleElementNames, nullability, nullableAttributeData ); @@ -109,6 +122,8 @@ namespace ICSharpCode.Decompiler.TypeSystem readonly ICompilation compilation; readonly bool hasDynamicAttribute; readonly bool[] dynamicAttributeData; + readonly bool hasNativeIntegersAttribute; + readonly bool[] nativeIntegersAttributeData; readonly TypeSystemOptions options; readonly string[] tupleElementNames; readonly Nullability defaultNullability; @@ -117,12 +132,17 @@ namespace ICSharpCode.Decompiler.TypeSystem int tupleTypeIndex = 0; int nullabilityTypeIndex = 0; - private ApplyAttributeTypeVisitor(ICompilation compilation, bool hasDynamicAttribute, bool[] dynamicAttributeData, TypeSystemOptions options, string[] tupleElementNames, + private ApplyAttributeTypeVisitor(ICompilation compilation, + bool hasDynamicAttribute, bool[] dynamicAttributeData, + bool hasNativeIntegersAttribute, bool[] nativeIntegersAttributeData, + TypeSystemOptions options, string[] tupleElementNames, Nullability defaultNullability, Nullability[] nullableAttributeData) { this.compilation = compilation ?? throw new ArgumentNullException(nameof(compilation)); this.hasDynamicAttribute = hasDynamicAttribute; this.dynamicAttributeData = dynamicAttributeData; + this.hasNativeIntegersAttribute = hasNativeIntegersAttribute; + this.nativeIntegersAttributeData = nativeIntegersAttributeData; this.options = options; this.tupleElementNames = tupleElementNames; this.defaultNullability = defaultNullability; @@ -247,11 +267,18 @@ namespace ICSharpCode.Decompiler.TypeSystem public override IType VisitTypeDefinition(ITypeDefinition type) { IType newType = type; - if (type.KnownTypeCode == KnownTypeCode.Object && hasDynamicAttribute) { + var ktc = type.KnownTypeCode; + if (ktc == KnownTypeCode.Object && hasDynamicAttribute) { if (dynamicAttributeData == null || dynamicTypeIndex >= dynamicAttributeData.Length) newType = SpecialType.Dynamic; else if (dynamicAttributeData[dynamicTypeIndex]) newType = SpecialType.Dynamic; + } else if ((ktc == KnownTypeCode.IntPtr || ktc == KnownTypeCode.UIntPtr) && hasNativeIntegersAttribute) { + // native integers use the same indexing logic as 'dynamic' + if (nativeIntegersAttributeData == null || dynamicTypeIndex > nativeIntegersAttributeData.Length) + newType = (ktc == KnownTypeCode.IntPtr ? SpecialType.NInt : SpecialType.NUInt); + else if (nativeIntegersAttributeData[dynamicTypeIndex]) + newType = (ktc == KnownTypeCode.IntPtr ? SpecialType.NInt : SpecialType.NUInt); } if (type.IsReferenceType == true) { Nullability nullability = GetNullability(); diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 4ba6622bf..98fd65194 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -110,10 +110,15 @@ namespace ICSharpCode.Decompiler.TypeSystem /// ReadOnlyMethods = 0x800, /// + /// [NativeIntegerAttribute] is used to replace 'IntPtr' types with the 'nint' type. + /// + NativeIntegers = 0x1000, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods + | NativeIntegers } /// @@ -145,6 +150,8 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.NullabilityAnnotations; if (settings.ReadOnlyMethods) typeSystemOptions |= TypeSystemOptions.ReadOnlyMethods; + if (settings.NativeIntegers) + typeSystemOptions |= TypeSystemOptions.NativeIntegers; return typeSystemOptions; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index 46cb1c99d..75290771f 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -194,6 +194,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation switch (attributeType.Name) { case "DynamicAttribute": return (options & TypeSystemOptions.Dynamic) != 0; + case "NativeIntegerAttribute": + return (options & TypeSystemOptions.NativeIntegers) != 0; case "TupleElementNamesAttribute": return (options & TypeSystemOptions.Tuple) != 0; case "ExtensionAttribute": diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index 0e7b33a7b..f55cdb36f 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -98,11 +98,14 @@ namespace ICSharpCode.Decompiler.TypeSystem // Security attributes: PermissionSet, + + // C# 9 attributes: + NativeInteger, } static class KnownAttributes { - internal const int Count = (int)KnownAttribute.PermissionSet + 1; + internal const int Count = (int)KnownAttribute.NativeInteger + 1; static readonly TopLevelTypeName[] typeNames = new TopLevelTypeName[Count]{ default, @@ -160,6 +163,8 @@ namespace ICSharpCode.Decompiler.TypeSystem new TopLevelTypeName("System.Runtime.InteropServices", nameof(MarshalAsAttribute)), // Security attributes: new TopLevelTypeName("System.Security.Permissions", "PermissionSetAttribute"), + // C# 9 attributes: + new TopLevelTypeName("System.Runtime.CompilerServices", "NativeIntegerAttribute"), }; public static ref readonly TopLevelTypeName GetTypeName(this KnownAttribute attr) diff --git a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs index 0f8d8a2a4..5ad28ed06 100644 --- a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs @@ -15,6 +15,18 @@ namespace ICSharpCode.Decompiler.TypeSystem ReplaceClassTypeParametersWithDummy = false, ReplaceMethodTypeParametersWithDummy = false, DynamicAndObject = true, + IntPtrToNInt = true, + TupleToUnderlyingType = true, + RemoveModOpt = true, + RemoveModReq = true, + RemoveNullability = true, + }; + + internal static readonly NormalizeTypeVisitor IgnoreNullabilityAndTuples = new NormalizeTypeVisitor { + ReplaceClassTypeParametersWithDummy = false, + ReplaceMethodTypeParametersWithDummy = false, + DynamicAndObject = false, + IntPtrToNInt = false, TupleToUnderlyingType = true, RemoveModOpt = true, RemoveModReq = true, @@ -33,6 +45,7 @@ namespace ICSharpCode.Decompiler.TypeSystem public bool ReplaceClassTypeParametersWithDummy = true; public bool ReplaceMethodTypeParametersWithDummy = true; public bool DynamicAndObject = true; + public bool IntPtrToNInt = true; public bool TupleToUnderlyingType = true; public bool RemoveNullability = true; @@ -51,13 +64,18 @@ namespace ICSharpCode.Decompiler.TypeSystem public override IType VisitTypeDefinition(ITypeDefinition type) { - if (DynamicAndObject && type.KnownTypeCode == KnownTypeCode.Object) { - // Instead of normalizing dynamic->object, - // we do this the opposite direction, so that we don't need a compilation to find the object type. - if (RemoveNullability) - return SpecialType.Dynamic; - else - return SpecialType.Dynamic.ChangeNullability(type.Nullability); + switch (type.KnownTypeCode) { + case KnownTypeCode.Object when DynamicAndObject: + // Instead of normalizing dynamic->object, + // we do this the opposite direction, so that we don't need a compilation to find the object type. + if (RemoveNullability) + return SpecialType.Dynamic; + else + return SpecialType.Dynamic.ChangeNullability(type.Nullability); + case KnownTypeCode.IntPtr when IntPtrToNInt: + return SpecialType.NInt; + case KnownTypeCode.UIntPtr when IntPtrToNInt: + return SpecialType.NUInt; } return base.VisitTypeDefinition(type); } diff --git a/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs b/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs index 4eb6677ce..dda869426 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs @@ -36,7 +36,17 @@ namespace ICSharpCode.Decompiler.TypeSystem /// A reflection class used to represent dynamic. /// public sealed class Dynamic {} - + + /// + /// A reflection class used to represent nint. + /// + public sealed class NInt { } + + /// + /// A reflection class used to represent nuint. + /// + public sealed class NUInt { } + /// /// A reflection class used to represent an unbound type argument. /// @@ -101,6 +111,10 @@ namespace ICSharpCode.Decompiler.TypeSystem } else if (type.DeclaringType != null) { if (type == typeof(Dynamic)) return SpecialType.Dynamic; + else if (type == typeof(NInt)) + return SpecialType.NInt; + else if (type == typeof(NUInt)) + return SpecialType.NUInt; else if (type == typeof(Null)) return SpecialType.NullType; else if (type == typeof(UnboundTypeArgument)) diff --git a/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs b/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs index 082376efa..1cd54683b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs @@ -46,7 +46,17 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Type representing the C# 'dynamic' type. /// public readonly static SpecialType Dynamic = new SpecialType(TypeKind.Dynamic, "dynamic", isReferenceType: true); - + + /// + /// Type representing the C# 9 'nint' type. + /// + public readonly static SpecialType NInt = new SpecialType(TypeKind.NInt, "nint", isReferenceType: false); + + /// + /// Type representing the C# 9 'nuint' type. + /// + public readonly static SpecialType NUInt = new SpecialType(TypeKind.NUInt, "nuint", isReferenceType: false); + /// /// Type representing the result of the C# '__arglist()' expression. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs b/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs index 5f107d6fa..8bc26d43c 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs @@ -94,5 +94,14 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Modified type, with required modifier. /// ModReq, + + /// + /// C# 9 nint + /// + NInt, + /// + /// C# 9 nuint + /// + NUInt, } } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs b/ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs index 4741fef7b..e92748db9 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs @@ -36,6 +36,8 @@ namespace ICSharpCode.Decompiler.TypeSystem case TypeKind.Pointer: case TypeKind.ByReference: case TypeKind.Class: + case TypeKind.NInt: + case TypeKind.NUInt: return NativeIntSize; case TypeKind.Enum: type = type.GetEnumUnderlyingType(); @@ -131,6 +133,22 @@ namespace ICSharpCode.Decompiler.TypeSystem } } + /// + /// Gets whether the type is a C# 9 native integer type: nint or nuint. + /// + /// Returns false for (U)IntPtr. + /// + public static bool IsCSharpNativeIntegerType(this IType type) + { + switch (type.Kind) { + case TypeKind.NInt: + case TypeKind.NUInt: + return true; + default: + return false; + } + } + /// /// Gets whether the type is a C# primitive integer type: byte, sbyte, short, ushort, int, uint, long and ulong. /// @@ -243,6 +261,8 @@ namespace ICSharpCode.Decompiler.TypeSystem case TypeKind.ByReference: return StackType.Ref; case TypeKind.Pointer: + case TypeKind.NInt: + case TypeKind.NUInt: return StackType.I; case TypeKind.TypeParameter: // Type parameters are always considered StackType.O, even @@ -305,8 +325,13 @@ namespace ICSharpCode.Decompiler.TypeSystem public static Sign GetSign(this IType type) { type = type.SkipModifiers(); - if (type.Kind == TypeKind.Pointer) - return Sign.Unsigned; + switch (type.Kind) { + case TypeKind.Pointer: + case TypeKind.NUInt: + return Sign.Unsigned; + case TypeKind.NInt: + return Sign.Signed; + } var typeDef = type.GetEnumUnderlyingType().GetDefinition(); if (typeDef == null) return Sign.None; @@ -375,8 +400,12 @@ namespace ICSharpCode.Decompiler.TypeSystem public static PrimitiveType ToPrimitiveType(this IType type) { type = type.SkipModifiers(); - if (type.Kind == TypeKind.Unknown) return PrimitiveType.Unknown; - if (type.Kind == TypeKind.ByReference) return PrimitiveType.Ref; + switch (type.Kind) { + case TypeKind.Unknown: return PrimitiveType.Unknown; + case TypeKind.ByReference: return PrimitiveType.Ref; + case TypeKind.NInt: return PrimitiveType.I; + case TypeKind.NUInt: return PrimitiveType.U; + } var def = type.GetEnumUnderlyingType().GetDefinition(); return def != null ? def.KnownTypeCode.ToPrimitiveType() : PrimitiveType.None; } diff --git a/ILSpy.Tests/ILSpy.Tests.csproj b/ILSpy.Tests/ILSpy.Tests.csproj index ec0e6bf79..6027ba7aa 100644 --- a/ILSpy.Tests/ILSpy.Tests.csproj +++ b/ILSpy.Tests/ILSpy.Tests.csproj @@ -46,8 +46,8 @@ - - + + diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs index bd2e430c8..6845088a3 100644 --- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs +++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs @@ -298,12 +298,15 @@ namespace ICSharpCode.ILSpy case "ushort": case "ulong": case "unmanaged": + case "nint": + case "nuint": color = valueTypeKeywordsColor; break; case "class": case "object": case "string": case "void": + case "dynamic": color = referenceTypeKeywordsColor; break; } @@ -327,7 +330,7 @@ namespace ICSharpCode.ILSpy { color = valueKeywordColor; } - if ((identifier.Name == "dynamic" || identifier.Name == "var") && identifier.Parent is AstType) + if (identifier.Name == "var" && identifier.Parent is AstType) color = queryKeywordsColor; switch (GetCurrentDefinition()) { case ITypeDefinition t: diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 52c7e4644..8b0253026 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -107,6 +107,7 @@ namespace ICSharpCode.ILSpy new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp7_2.ToString(), "C# 7.2 / VS 2017.4"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp7_3.ToString(), "C# 7.3 / VS 2017.7"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp8_0.ToString(), "C# 8.0 / VS 2019"), + new LanguageVersion(Decompiler.CSharp.LanguageVersion.Preview.ToString(), "C# 9.0 (experimental)"), }; } return versions; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index ddd8eb01a..84a05cf4c 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -920,6 +920,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Use nint/nuint types. + /// + public static string DecompilerSettings_NativeIntegers { + get { + return ResourceManager.GetString("DecompilerSettings.NativeIntegers", resourceCulture); + } + } + /// /// Looks up a localized string similar to Nullable reference types. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 59997c57a..62a8ffa83 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -873,6 +873,9 @@ Do you want to continue? Use new SDK style format for generated project files (*.csproj) + + Use nint/nuint types + Base Types