diff --git a/.github/workflows/build-frontends.yml b/.github/workflows/build-frontends.yml index c35da7dab2..c216fdaae8 100644 --- a/.github/workflows/build-frontends.yml +++ b/.github/workflows/build-frontends.yml @@ -15,7 +15,7 @@ jobs: with: fetch-depth: 0 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: dotnet-version: 6.0.x diff --git a/.github/workflows/build-ilspy.yml b/.github/workflows/build-ilspy.yml index e6a91feee0..2b6b3ed2cc 100644 --- a/.github/workflows/build-ilspy.yml +++ b/.github/workflows/build-ilspy.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.1 + uses: microsoft/setup-msbuild@v1.3 - name: Install dotnet-format run: dotnet tool install -g dotnet-format --version "6.2.315104" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json @@ -37,7 +37,7 @@ jobs: shell: pwsh run: | .\BuildTools\ghactions-install.ps1 - Get-ChildItem Env: | Where-Object {$_.Name -Match "^ILSPY_"} | %{ echo "::set-output name=$($_.Name)::$($_.Value)" } + Get-ChildItem Env: | Where-Object {$_.Name -Match "^ILSPY_"} | %{ echo "$($_.Name)=$($_.Value)" } | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append - name: Restore the application run: msbuild ILSpy.sln /t:Restore /p:Configuration=${{ matrix.configuration }} /p:Platform=$env:BuildPlatform @@ -60,7 +60,7 @@ jobs: path: 'test-results/${{ matrix.configuration }}.xml' - name: Create Test Report - uses: test-summary/action@v1 + uses: test-summary/action@v2 if: always() with: paths: "test-results/${{ matrix.configuration }}.xml" @@ -78,14 +78,30 @@ jobs: - name: Zip ILSpy (framework-dependent) run: 7z a -tzip $env:StagingDirectory\ILSpy_binaries.zip .\ILSpy\bin\${{ matrix.configuration }}\net6.0-windows\*.dll .\ILSpy\bin\${{ matrix.configuration }}\net6.0-windows\*.exe .\ILSpy\bin\${{ matrix.configuration }}\net6.0-windows\*.config .\ILSpy\bin\${{ matrix.configuration }}\net6.0-windows\*.json .\ILSpy\bin\${{ matrix.configuration }}\net6.0-windows\*\ILSpy.resources.dll .\ILSpy\bin\${{ matrix.configuration }}\net6.0-windows\*\ILSpy.ReadyToRun.Plugin.resources.dll - - name: Zip ILSpy Release (self-contained win-x64) - if: matrix.configuration == 'release' + - name: Publish x64/arm64 framework-dependent/self-contained shell: pwsh + run: .\publish.ps1 + + - name: Zip ILSpy Release (x64 self-contained) + if: matrix.configuration == 'release' + run: 7z a -tzip $env:StagingDirectory\ILSpy_selfcontained_x64.zip .\ILSpy\bin\Release\net6.0-windows\win-x64\publish\selfcontained\* + + - name: Zip ILSpy Release (arm64 framework-dependent) + if: matrix.configuration == 'release' + run: 7z a -tzip $env:StagingDirectory\ILSpy_binaries_arm64.zip .\ILSpy\bin\Release\net6.0-windows\win-arm64\publish\fwdependent\* + + - name: Build Installer (x64 and arm64, framework-dependent) + if: matrix.configuration == 'release' + run: | + msbuild ILSpy.Installer.sln /t:Restore /p:Configuration="Release" /p:Platform="Any CPU" + msbuild ILSpy.Installer.sln /p:Configuration="Release" /p:Platform="Any CPU" + msbuild ILSpy.Installer.sln /p:Configuration="Release" /p:Platform="Any CPU" /p:DefineConstants="ARM64" + + - name: Build VS Extensions (for 2017-2019 and 2022) + if: matrix.configuration == 'release' run: | - dotnet publish ./ILSpy/ILSpy.csproj -c Release --no-restore --self-contained -r win-x64 - dotnet publish ./ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj -c Release --no-restore --self-contained -r win-x64 - dotnet publish ./ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj -c Release --no-restore --self-contained -r win-x64 - 7z a -tzip $env:StagingDirectory\ILSpy_selfcontained_x64.zip .\ILSpy\bin\Release\net6.0-windows\win-x64\publish\* + msbuild ILSpy.VSExtensions.sln /t:Restore /p:Configuration="Release" /p:Platform="Any CPU" + msbuild ILSpy.VSExtensions.sln /p:Configuration="Release" /p:Platform="Any CPU" # https://github.com/actions/upload-artifact - name: Upload VSIX (VS 2019) release build artifacts @@ -137,7 +153,7 @@ jobs: path: ${{ env.StagingDirectory }}\ILSpy_binaries.zip if-no-files-found: error - - name: Upload self-contained zip build artifacts (Release-only) + - name: Upload x64 self-contained zip (Release-only) if: matrix.configuration == 'release' uses: actions/upload-artifact@v3 with: @@ -145,12 +161,28 @@ jobs: path: ${{ env.StagingDirectory }}\ILSpy_selfcontained_x64.zip if-no-files-found: error - - name: Upload installer artifact + - name: Upload arm64 framework-dependent zip (Release-only) + if: matrix.configuration == 'release' + uses: actions/upload-artifact@v3 + with: + name: ILSpy arm64 ${{ steps.version.outputs.ILSPY_VERSION_NUMBER }} (${{ matrix.configuration }}) + path: ${{ env.StagingDirectory }}\ILSpy_binaries_arm64.zip + if-no-files-found: error + + - name: Upload x64 installer artifact + if: matrix.configuration == 'release' + uses: actions/upload-artifact@v3 + with: + name: ILSpy Installer x64 ${{ steps.version.outputs.ILSPY_VERSION_NUMBER }} (${{ matrix.configuration }}) + path: ILSpy.Installer\wix\*-x64.msi + if-no-files-found: error + + - name: Upload arm64 installer artifact if: matrix.configuration == 'release' uses: actions/upload-artifact@v3 with: - name: ILSpy Installer ${{ steps.version.outputs.ILSPY_VERSION_NUMBER }} (${{ matrix.configuration }}) - path: ILSpy.Installer\wix\*.msi + name: ILSpy Installer arm64 ${{ steps.version.outputs.ILSPY_VERSION_NUMBER }} (${{ matrix.configuration }}) + path: ILSpy.Installer\wix\*-arm64.msi if-no-files-found: error - name: Upload ilspycmd release build artifacts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4cc0d34d7d..9d33c4b969 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -28,7 +28,7 @@ jobs: languages: ${{ matrix.language }} - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: dotnet-version: 6.0.x diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 3a3dafb137..86c6960a3c 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -8,7 +8,8 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v3.0.0 + + - uses: dessant/lock-threads@v4.0.1 with: github-token: ${{ github.token }} issue-inactive-days: '90' diff --git a/.gitignore b/.gitignore index 9e00f3b055..be16796fde 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ _ReSharper*/ *.ReSharper *.patch .vs/ +.idea/ /ILSpy.AddIn*/Packages/* /ILSpy.AddIn*/source.extension.vsixmanifest /ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/*.dll @@ -18,3 +19,4 @@ multitargeting.props ILSpy.Installer/wix/ /VERSION /ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.cs +*/.vscode/ diff --git a/BuildTools/pre-commit b/BuildTools/pre-commit index 9a77bea3c1..a55e3bdc49 100644 --- a/BuildTools/pre-commit +++ b/BuildTools/pre-commit @@ -12,9 +12,9 @@ if [ ! -d "$DOTNET_PATH" ]; then fi "$DOTNET_PATH/dotnet-format.exe" --version -#if git diff --quiet --ignore-submodules; then -# "$DOTNET_PATH/dotnet-format.exe" whitespace --no-restore --verbosity detailed ILSpy.sln -# git add -u -- \*\*.cs -#else +if git diff --quiet --ignore-submodules; then + "$DOTNET_PATH/dotnet-format.exe" whitespace --no-restore --verbosity detailed ILSpy.sln + git add -u -- \*\*.cs +else exec "$DOTNET_PATH/dotnet-format.exe" whitespace --verify-no-changes --no-restore --verbosity detailed ILSpy.sln -#fi +fi diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs index 4db4ad4554..9b87d99bee 100644 --- a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs @@ -33,8 +33,8 @@ public void Report(DecompilationProgress value) lock (syncObject) { completed++; - progress = new ProgressRecord(1, "Decompiling " + fileName, $"Completed {completed} of {value.TotalNumberOfFiles}: {value.Status}") { - PercentComplete = (int)(completed * 100.0 / value.TotalNumberOfFiles) + progress = new ProgressRecord(1, "Decompiling " + fileName, $"Completed {completed} of {value.TotalUnits}: {value.Status}") { + PercentComplete = (int)(completed * 100.0 / value.TotalUnits) }; } } diff --git a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj index 8e73fc14ae..c50801c161 100644 --- a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj +++ b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/ICSharpCode.Decompiler.PowerShell/README.md b/ICSharpCode.Decompiler.PowerShell/README.md index 2bac472f43..acb6e671d0 100644 --- a/ICSharpCode.Decompiler.PowerShell/README.md +++ b/ICSharpCode.Decompiler.PowerShell/README.md @@ -4,20 +4,21 @@ Built using https://github.com/PowerShell/PowerShell/blob/master/docs/cmdlet-exa Sample usage: Demo.ps1 -Tested with: PowerShell 5.1 on Windows, PowerShell Core on Windows and Mac (Beta9) +Tested with: PowerShell 5.1 on Windows, PowerShell 7+ on Windows and Mac ## Missing -.psd1 for deploying to https://www.powershellgallery.com/ +Publishing to https://www.powershellgallery.com/ +* https://learn.microsoft.com/en-us/powershell/gallery/how-to/publishing-packages/publishing-a-package +* https://learn.microsoft.com/en-us/powershell/gallery/concepts/publishing-guidelines ## Links for developing PS cmdlets -* https://docs.microsoft.com/en-us/powershell/gallery/psgallery/creating-and-publishing-an-item +* https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/how-to-write-a-simple-cmdlet +* https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands * https://github.com/mmaitre314/PowerShellGet-Test-Binary-Module * https://www.red-gate.com/simple-talk/dotnet/net-development/using-c-to-create-powershell-cmdlets-beyond-the-basics/ -* https://msdn.microsoft.com/en-us/library/dd878294(v=VS.85).aspx Writing a Windows PowerShell Cmdlet -* https://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx Approved verbs * https://www.google.com/search?q=write+a+module+for+powershell+core \ No newline at end of file diff --git a/ICSharpCode.Decompiler.PowerShell/manifest.psd1 b/ICSharpCode.Decompiler.PowerShell/manifest.psd1 new file mode 100644 index 0000000000..575226e49b --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/manifest.psd1 @@ -0,0 +1,129 @@ +@{ + # Script module or binary module file associated with this manifest. + RootModule = 'ICSharpCode.Decompiler.PowerShell.dll' + + # Version number of this module. + ModuleVersion = '8.0.0.0' + + # Supported PSEditions + # CompatiblePSEditions = @() + + # ID used to uniquely identify this module + GUID = '198b4312-cbe7-417e-81a7-1aaff467ef06' + + # Author of this module + Author = 'ILSpy Contributors' + + # Company or vendor of this module + CompanyName = 'ic#code' + + # Copyright statement for this module + Copyright = 'Copyright 2011-2023 AlphaSierraPapa' + + # Description of the functionality provided by this module + Description = 'PowerShell front-end for ILSpy' + + # Minimum version of the PowerShell engine required by this module + # PowerShellVersion = '' + + # Name of the PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = @() + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @( + 'Get-DecompiledProject', + 'Get-DecompiledSource', + 'Get-DecompiledTypes', + 'Get-Decompiler', + 'Get-DecompilerVersion' + ) + + # Variables to export from this module + VariablesToExport = '*' + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/icsharpcode/ILSpy' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' + +} + diff --git a/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj b/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj index c60a25481a..8c8d40ddc3 100644 --- a/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj +++ b/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj @@ -2,7 +2,7 @@ Exe - net6.0-windows + net7.0 enable diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs index 443485a815..ed4a3f2bb2 100644 --- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs @@ -1,4 +1,4 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -317,7 +317,7 @@ public async Task Jmp() public async Task StackTests() { // IL contains .corflags = 32BITREQUIRED - await RunIL("StackTests.il", asmOptions: AssemblerOptions.Force32Bit); + await RunIL("StackTests.il", CompilerOptions.Force32Bit, AssemblerOptions.Force32Bit); } [Test] @@ -398,10 +398,6 @@ public async Task DynamicTests([ValueSource(nameof(noMonoOptions))] CompilerOpti [Test] public async Task MiniJSON([ValueSource(nameof(defaultOptions))] CompilerOptions options) { - if (options.HasFlag(CompilerOptions.UseMcs2_6_4)) - { - Assert.Ignore("Decompiler bug with mono!"); - } await RunCS(options: options); } @@ -487,6 +483,11 @@ async Task RunIL(string testFileName, CompilerOptions options = CompilerOptions. string outputFile = null; CompilerResults decompiledOutputFile = null; + bool optionsForce32Bit = options.HasFlag(CompilerOptions.Force32Bit); + bool asmOptionsForce32Bit = asmOptions.HasFlag(AssemblerOptions.Force32Bit); + + Assert.AreEqual(optionsForce32Bit, asmOptionsForce32Bit, "Inconsistent architecture."); + try { options |= CompilerOptions.UseTestRunner; diff --git a/ICSharpCode.Decompiler.Tests/DisassemblerPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/DisassemblerPrettyTestRunner.cs index 8cec9947be..9636facc1c 100644 --- a/ICSharpCode.Decompiler.Tests/DisassemblerPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/DisassemblerPrettyTestRunner.cs @@ -58,15 +58,28 @@ public async Task SecurityDeclarations() await Run(); } - async Task Run([CallerMemberName] string testName = null) + [Test] + public async Task SortMembers() + { + await Run(ilExpectedFile: Path.Combine(TestCasePath, "SortMembers.expected.il"), asmOptions: AssemblerOptions.SortedOutput); + } + + [Test] + public async Task InterfaceImplAttributes() + { + await Run(); + } + + async Task Run([CallerMemberName] string testName = null, string ilExpectedFile = null, AssemblerOptions asmOptions = AssemblerOptions.None) { - var ilExpectedFile = Path.Combine(TestCasePath, testName + ".il"); + var ilInputFile = Path.Combine(TestCasePath, testName + ".il"); + ilExpectedFile ??= ilInputFile; var ilResultFile = Path.Combine(TestCasePath, testName + ".result.il"); - var executable = await Tester.AssembleIL(ilExpectedFile, AssemblerOptions.Library).ConfigureAwait(false); - var disassembled = await Tester.Disassemble(executable, ilResultFile, AssemblerOptions.UseOwnDisassembler).ConfigureAwait(false); + var executable = await Tester.AssembleIL(ilInputFile, AssemblerOptions.Library).ConfigureAwait(false); + var disassembled = await Tester.Disassemble(executable, ilResultFile, AssemblerOptions.UseOwnDisassembler | asmOptions).ConfigureAwait(false); - CodeAssert.FilesAreEqual(ilExpectedFile, ilResultFile); + CodeAssert.FilesAreEqual(ilExpectedFile, disassembled); } } } diff --git a/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs b/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs index efdd021d30..2ce551e791 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs @@ -20,7 +20,7 @@ public override void VisitAttribute(CSharp.Syntax.Attribute attribute) if (section.Attributes.Count == 0) section.Remove(); } - if (section.AttributeTarget == "module" && type.Identifier == "UnverifiableCode") + if (section.AttributeTarget == "module" && type.Identifier is "UnverifiableCode" or "RefSafetyRules") { attribute.Remove(); if (section.Attributes.Count == 0) @@ -34,35 +34,6 @@ public void Run(AstNode rootNode, TransformContext context) } } - public class RemoveEmbeddedAttributes : DepthFirstAstVisitor, IAstTransform - { - HashSet attributeNames = new HashSet() { - "System.Runtime.CompilerServices.IsReadOnlyAttribute", - "System.Runtime.CompilerServices.IsByRefLikeAttribute", - "System.Runtime.CompilerServices.IsUnmanagedAttribute", - "System.Runtime.CompilerServices.NullableAttribute", - "System.Runtime.CompilerServices.NullableContextAttribute", - "System.Runtime.CompilerServices.NativeIntegerAttribute", - "Microsoft.CodeAnalysis.EmbeddedAttribute", - }; - - public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) - { - var typeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition; - if (typeDefinition == null || !attributeNames.Contains(typeDefinition.FullName)) - return; - if (typeDeclaration.Parent is NamespaceDeclaration ns && ns.Members.Count == 1) - ns.Remove(); - else - typeDeclaration.Remove(); - } - - public void Run(AstNode rootNode, TransformContext context) - { - rootNode.AcceptVisitor(this); - } - } - public class RemoveNamespaceMy : DepthFirstAstVisitor, IAstTransform { public override void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs index d871bed941..967f564e6b 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs @@ -72,7 +72,7 @@ public static async Task CompileVB(string sourceFileName, Compi { references = references.Concat(new[] { "-r:\"Microsoft.VisualBasic.dll\"" }); } - string otherOptions = $"-noconfig " + + string otherOptions = $"-nologo -noconfig " + "-optioninfer+ -optionexplicit+ " + $"-langversion:{languageVersion} " + $"/optimize{(flags.HasFlag(CompilerOptions.Optimize) ? "+ " : "- ")}"; @@ -122,8 +122,14 @@ public static async Task CompileVB(string sourceFileName, Compi var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); - Console.WriteLine("output: " + result.StandardOutput); - Console.WriteLine("errors: " + result.StandardError); + if (!string.IsNullOrWhiteSpace(result.StandardOutput)) + { + Console.WriteLine("output:" + Environment.NewLine + result.StandardOutput); + } + if (!string.IsNullOrWhiteSpace(result.StandardError)) + { + Console.WriteLine("errors:" + Environment.NewLine + result.StandardError); + } Assert.AreEqual(0, result.ExitCode, "vbc failed"); return results; diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 5a55e10c29..6219c2a1af 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -69,6 +69,7 @@ public enum CompilerOptions UseMcs5_23 = 0x2000, UseTestRunner = 0x4000, NullableEnable = 0x8000, + ReferenceUnsafe = 0x10000, UseMcsMask = UseMcs2_6_4 | UseMcs5_23, UseRoslynMask = UseRoslyn1_3_2 | UseRoslyn2_10_0 | UseRoslyn3_11_0 | UseRoslynLatest } @@ -84,6 +85,8 @@ public enum AssemblerOptions UseOwnDisassembler = 0x8, /// Work around bug in .NET 5 ilasm (https://github.com/dotnet/runtime/issues/32400) UseLegacyAssembler = 0x10, + /// UseSortByNameFilter, implies UseOwnDisassembler + SortedOutput = 0x20, } public static partial class Tester @@ -102,9 +105,9 @@ static Tester() TesterPath = Path.GetDirectoryName(typeof(Tester).Assembly.Location); TestCasePath = Path.Combine(TesterPath, "../../../../TestCases"); #if DEBUG - testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Debug/net6.0-windows"); + testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Debug/net7.0"); #else - testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net6.0-windows"); + testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net7.0"); #endif packagesPropsFile = Path.Combine(TesterPath, "../../../../../packages.props"); roslynLatestVersion = XDocument.Load(packagesPropsFile).XPathSelectElement("//RoslynVersion").Value; @@ -175,13 +178,19 @@ public static async Task AssembleIL(string sourceFileName, AssemblerOpti } var command = Cli.Wrap(ilasmPath) - .WithArguments($"/nologo {otherOptions}/output=\"{outputFile}\" \"{sourceFileName}\"") + .WithArguments($"/quiet {otherOptions}/output=\"{outputFile}\" \"{sourceFileName}\"") .WithValidation(CommandResultValidation.None); var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); - Console.WriteLine("output: " + result.StandardOutput); - Console.WriteLine("errors: " + result.StandardError); + if (!string.IsNullOrWhiteSpace(result.StandardOutput)) + { + Console.WriteLine("output:" + Environment.NewLine + result.StandardOutput); + } + if (!string.IsNullOrWhiteSpace(result.StandardError)) + { + Console.WriteLine("errors:" + Environment.NewLine + result.StandardError); + } Assert.AreEqual(0, result.ExitCode, "ilasm failed"); return outputFile; @@ -189,7 +198,7 @@ public static async Task AssembleIL(string sourceFileName, AssemblerOpti public static async Task Disassemble(string sourceFileName, string outputFile, AssemblerOptions asmOptions) { - if (asmOptions.HasFlag(AssemblerOptions.UseOwnDisassembler)) + if (asmOptions.HasFlag(AssemblerOptions.UseOwnDisassembler) || asmOptions.HasFlag(AssemblerOptions.SortedOutput)) { using (var peFileStream = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read)) using (var peFile = new PEFile(sourceFileName, peFileStream)) @@ -198,7 +207,11 @@ public static async Task Disassemble(string sourceFileName, string outpu var metadata = peFile.Metadata; var output = new PlainTextOutput(writer); ReflectionDisassembler rd = new ReflectionDisassembler(output, CancellationToken.None); - rd.AssemblyResolver = new UniversalAssemblyResolver(sourceFileName, true, null); + if (asmOptions.HasFlag(AssemblerOptions.SortedOutput)) + { + rd.EntityProcessor = new SortByNameProcessor(); + } + rd.AssemblyResolver = new UniversalAssemblyResolver(sourceFileName, throwOnError: true, null); rd.DetectControlStructure = false; rd.WriteAssemblyReferences(metadata); if (metadata.IsAssembly) @@ -223,8 +236,14 @@ public static async Task Disassemble(string sourceFileName, string outpu var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); - Console.WriteLine("output: " + result.StandardOutput); - Console.WriteLine("errors: " + result.StandardError); + if (!string.IsNullOrWhiteSpace(result.StandardOutput)) + { + Console.WriteLine("output:" + Environment.NewLine + result.StandardOutput); + } + if (!string.IsNullOrWhiteSpace(result.StandardError)) + { + Console.WriteLine("errors:" + Environment.NewLine + result.StandardError); + } Assert.AreEqual(0, result.ExitCode, "ildasm failed"); // Unlike the .imagebase directive (which is a fixed value when compiling with /deterministic), @@ -251,8 +270,8 @@ private static string ReplacePrivImplDetails(string il) } static readonly string coreRefAsmPath = new DotNetCorePathFinder(TargetFrameworkIdentifier.NET, - new Version(6, 0), "Microsoft.NETCore.App") - .GetReferenceAssemblyPath(".NETCoreApp,Version=v6.0"); + new Version(7, 0), "Microsoft.NETCore.App") + .GetReferenceAssemblyPath(".NETCoreApp,Version=v7.0"); public static readonly string RefAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2"); @@ -289,7 +308,7 @@ private static string ReplacePrivImplDetails(string il) const string targetFrameworkAttributeSnippet = @" -[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp,Version=v6.0"", FrameworkDisplayName = """")] +[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp,Version=v7.0"", FrameworkDisplayName = """")] "; @@ -324,6 +343,7 @@ public static List GetPreprocessorSymbols(CompilerOptions flags) { preprocessorSymbols.Add("NETCORE"); preprocessorSymbols.Add("NET60"); + preprocessorSymbols.Add("NET70"); } preprocessorSymbols.Add("ROSLYN"); preprocessorSymbols.Add("CS60"); @@ -352,10 +372,7 @@ public static List GetPreprocessorSymbols(CompilerOptions flags) { preprocessorSymbols.Add("ROSLYN4"); preprocessorSymbols.Add("CS100"); - if (flags.HasFlag(CompilerOptions.Preview)) - { - preprocessorSymbols.Add("CS110"); - } + preprocessorSymbols.Add("CS110"); } } else if ((flags & CompilerOptions.UseMcsMask) != 0) @@ -425,7 +442,11 @@ public static async Task CompileCSharp(string sourceFileName, C { references = references.Concat(new[] { "-r:\"Microsoft.VisualBasic.dll\"" }); } - string otherOptions = $"-noconfig " + + if (useRoslyn && !targetNet40 && flags.HasFlag(CompilerOptions.ReferenceUnsafe)) + { + references = references.Concat(new[] { "-r:\"System.Runtime.CompilerServices.Unsafe.dll\"" }); + } + string otherOptions = $"-nologo -noconfig " + $"-langversion:{languageVersion} " + $"-unsafe -o{(flags.HasFlag(CompilerOptions.Optimize) ? "+ " : "- ")}"; @@ -477,12 +498,18 @@ public static async Task CompileCSharp(string sourceFileName, C var command = Cli.Wrap(cscPath) .WithArguments($"{otherOptions} -lib:{libPath} {string.Join(" ", references)} -out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}") .WithValidation(CommandResultValidation.None); - Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); + //Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(result.StandardOutput)) + { + Console.WriteLine("output:" + Environment.NewLine + result.StandardOutput); + } + if (!string.IsNullOrWhiteSpace(result.StandardError)) + { + Console.WriteLine("errors:" + Environment.NewLine + result.StandardError); + } - Console.WriteLine("output: " + result.StandardOutput); - Console.WriteLine("errors: " + result.StandardError); Assert.AreEqual(0, result.ExitCode, "csc failed"); return results; @@ -533,12 +560,18 @@ public static async Task CompileCSharp(string sourceFileName, C var command = Cli.Wrap(mcsPath) .WithArguments($"{otherOptions}-out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}") .WithValidation(CommandResultValidation.None); - Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); + //Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); - Console.WriteLine("output: " + result.StandardOutput); - Console.WriteLine("errors: " + result.StandardError); + if (!string.IsNullOrWhiteSpace(result.StandardOutput)) + { + Console.WriteLine("output:" + Environment.NewLine + result.StandardOutput); + } + if (!string.IsNullOrWhiteSpace(result.StandardError)) + { + Console.WriteLine("errors:" + Environment.NewLine + result.StandardError); + } Assert.AreEqual(0, result.ExitCode, "mcs failed"); return results; @@ -553,7 +586,7 @@ internal static DecompilerSettings GetSettings(CompilerOptions cscOptions) CompilerOptions.UseRoslyn1_3_2 => CSharp.LanguageVersion.CSharp6, CompilerOptions.UseRoslyn2_10_0 => CSharp.LanguageVersion.CSharp7_3, CompilerOptions.UseRoslyn3_11_0 => CSharp.LanguageVersion.CSharp9_0, - _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp10_0, + _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp11_0, }; DecompilerSettings settings = new(langVersion) { // Never use file-scoped namespaces @@ -810,8 +843,14 @@ public static async Task SignAssembly(string assemblyPath, string keyFilePath) var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); Assert.AreEqual(0, result.ExitCode, "sn failed"); - Console.WriteLine("output: " + result.StandardOutput); - Console.WriteLine("errors: " + result.StandardError); + if (!string.IsNullOrWhiteSpace(result.StandardOutput)) + { + Console.WriteLine("output:" + Environment.NewLine + result.StandardOutput); + } + if (!string.IsNullOrWhiteSpace(result.StandardError)) + { + Console.WriteLine("errors:" + Environment.NewLine + result.StandardError); + } } public static async Task FindMSBuild() diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 90c8558727..5af3b8fabc 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ True - 1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162 + 1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602 False False @@ -17,6 +17,7 @@ false True + True True ..\ICSharpCode.Decompiler\ICSharpCode.Decompiler.snk @@ -33,11 +34,11 @@ - TRACE;DEBUG;ROSLYN;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 + TRACE;DEBUG;ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 - TRACE;ROSLYN;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 + TRACE;ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 @@ -45,7 +46,7 @@ - + @@ -57,10 +58,11 @@ - + + @@ -73,6 +75,9 @@ + + + @@ -110,6 +115,13 @@ + + + + + + + @@ -211,6 +223,7 @@ + @@ -312,6 +325,7 @@ + @@ -326,6 +340,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 237bdd4029..5338bb7c99 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -263,6 +263,12 @@ public async Task GuessAccessors() await Run(); } + [Test] + public async Task EmptyBodies() + { + await Run(); + } + async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null, AssemblerOptions assemblerOptions = AssemblerOptions.Library) { diff --git a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs index 174711c8b5..991c59caef 100644 --- a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection.Metadata; @@ -9,13 +8,10 @@ using System.Xml.Linq; using ICSharpCode.Decompiler.CSharp; -using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Tests.Helpers; -using ICSharpCode.Decompiler.TypeSystem; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.DiaSymReader.Tools; using NUnit.Framework; @@ -72,6 +68,60 @@ public void CustomPdbId() } } + [Test] + public void ProgressReporting() + { + // Generate a PDB for an assembly and validate that the progress reporter is called with reasonable values + (string peFileName, string pdbFileName) = CompileTestCase(nameof(ProgressReporting)); + + var moduleDefinition = new PEFile(peFileName); + var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage); + var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings()); + + var lastFilesWritten = 0; + var totalFiles = -1; + + Action reportFunc = progress => { + if (totalFiles == -1) + { + // Initialize value on first call + totalFiles = progress.TotalUnits; + } + + Assert.AreEqual(progress.TotalUnits, totalFiles); + Assert.AreEqual(progress.UnitsCompleted, lastFilesWritten + 1); + + lastFilesWritten = progress.UnitsCompleted; + }; + + using (FileStream pdbStream = File.Open(Path.Combine(TestCasePath, nameof(ProgressReporting) + ".pdb"), FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + pdbStream.SetLength(0); + PortablePdbWriter.WritePdb(moduleDefinition, decompiler, new DecompilerSettings(), pdbStream, noLogo: true, progress: new TestProgressReporter(reportFunc)); + + pdbStream.Position = 0; + var metadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); + var generatedPdbId = new BlobContentId(metadataReader.DebugMetadataHeader.Id); + } + + Assert.AreEqual(totalFiles, lastFilesWritten); + } + + private class TestProgressReporter : IProgress + { + private Action reportFunc; + + public TestProgressReporter(Action reportFunc) + { + this.reportFunc = reportFunc; + } + + public void Report(DecompilationProgress value) + { + reportFunc(value); + } + } + private void TestGeneratePdb([CallerMemberName] string testName = null) { const PdbToXmlOptions options = PdbToXmlOptions.IncludeEmbeddedSources | PdbToXmlOptions.ThrowOnError | PdbToXmlOptions.IncludeTokens | PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeMethodSpans; diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index b2542be8d1..c341835e0e 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -1,4 +1,4 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -135,15 +135,7 @@ public void AllFilesHaveTests() CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, }; - static readonly CompilerOptions[] roslynLatestOnlyWithNet40Options = - { - CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, - CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, - CompilerOptions.UseRoslynLatest, - CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, - }; - - static readonly CompilerOptions[] roslynLatestOnlyOptions = + static readonly CompilerOptions[] roslyn4OrNewerOptions = { CompilerOptions.UseRoslynLatest, CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, @@ -261,7 +253,7 @@ public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions c } [Test] - public async Task SwitchExpressions([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions) + public async Task SwitchExpressions([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) { await RunForLibrary(cscOptions: cscOptions); } @@ -275,7 +267,7 @@ public async Task ReduceNesting([ValueSource(nameof(defaultOptions))] CompilerOp [Test] public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + await RunForLibrary(cscOptions: cscOptions); } [Test] @@ -320,6 +312,12 @@ public async Task LiftedOperators([ValueSource(nameof(defaultOptions))] Compiler await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task Operators([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task Generics([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { @@ -329,18 +327,17 @@ public async Task Generics([ValueSource(nameof(defaultOptions))] CompilerOptions [Test] public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { - // legacy csc generates a dead store in debug builds - RemoveDeadStores = (cscOptions == CompilerOptions.None), - UseExpressionBodyForCalculatedGetterOnlyProperties = false, - FileScopedNamespaces = false, - }); + DecompilerSettings settings = Tester.GetSettings(cscOptions); + // legacy csc generates a dead store in debug builds + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false; + await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings); } [Test] public async Task LocalFunctions([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + await RunForLibrary(cscOptions: cscOptions); } [Test] @@ -376,7 +373,7 @@ public async Task CheckedUnchecked([ValueSource(nameof(defaultOptions))] Compile [Test] public async Task UnsafeCode([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions); + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.ReferenceUnsafe); } [Test] @@ -494,13 +491,13 @@ public async Task NullableRefTypes([ValueSource(nameof(roslyn3OrNewerOptions))] } [Test] - public async Task NativeInts([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions) + public async Task NativeInts([ValueSource(nameof(roslyn3OrNewerWithNet40Options))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + await RunForLibrary(cscOptions: cscOptions); } [Test] - public async Task FileScopedNamespaces([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions) + public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) { await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings()); } @@ -512,13 +509,13 @@ public async Task Structs([ValueSource(nameof(defaultOptionsWithMcs))] CompilerO } [Test] - public async Task FunctionPointers([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions) + public async Task FunctionPointers([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + await RunForLibrary(cscOptions: cscOptions); } [Test] - public async Task Records([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions) + public async Task Records([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) { await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); } @@ -547,6 +544,12 @@ public async Task RefLocalsAndReturns([ValueSource(nameof(roslyn2OrNewerOptions) await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task RefFields([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task ThrowExpressions([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) { @@ -568,12 +571,6 @@ public async Task QualifierTests([ValueSource(nameof(defaultOptions))] CompilerO [Test] public async Task TupleTests([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) { - if (cscOptions.HasFlag(CompilerOptions.UseRoslynLatest)) - { - Assert.Ignore("DefaultInterpolatedStringHandler is not yet supported!"); - return; - } - await RunForLibrary(cscOptions: cscOptions); } @@ -586,7 +583,7 @@ public async Task NamedArguments([ValueSource(nameof(defaultOptions))] CompilerO [Test] public async Task OptionalArguments([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + await RunForLibrary(cscOptions: cscOptions); } [Test] @@ -610,11 +607,6 @@ public async Task AssemblyCustomAttributes([ValueSource(nameof(defaultOptions))] [Test] public async Task CustomAttributes([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - if (cscOptions.HasFlag(CompilerOptions.UseRoslynLatest)) - { - // Test C# 11 generic attributes - cscOptions |= CompilerOptions.Preview; - } await RunForLibrary(cscOptions: cscOptions); } @@ -669,7 +661,7 @@ public async Task TypeMemberTests([ValueSource(nameof(defaultOptions))] Compiler [Test] public async Task YieldReturn([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + await RunForLibrary(cscOptions: cscOptions); } [Test] @@ -703,9 +695,9 @@ public async Task CovariantReturns([ValueSource(nameof(roslyn3OrNewerOptions))] } [Test] - public async Task StaticAbstractInterfaceMembers([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions) + public async Task StaticAbstractInterfaceMembers([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + await RunForLibrary(cscOptions: cscOptions); } [Test] diff --git a/ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs index 46b297807a..5a72ad5b2c 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -104,7 +104,7 @@ public void PreferUIntOverLong_FromIntLiteral() Assert.AreSame(c1, r.BestCandidate); } - [Test, Ignore("Broken after migration to ICS.Decompiler")] + [Test] public void NullableIntAndNullableUIntIsAmbiguous() { OverloadResolution r = new OverloadResolution(compilation, MakeArgumentList(typeof(ushort?))); @@ -300,7 +300,7 @@ public void BetterConversionByLambdaReturnValue_ExpressionTree() Assert.AreEqual(OverloadResolutionErrors.None, r.BestCandidateErrors); } - [Test, Ignore("Broken on SRM branch???")] + [Test] public void Lambda_DelegateAndExpressionTreeOverloadsAreAmbiguous() { var m1 = MakeMethod(typeof(Func)); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DecimalFields.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DecimalFields.cs index f3873bc387..c843b422a0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DecimalFields.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DecimalFields.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -36,7 +36,31 @@ public static int Main() Console.WriteLine(field2); Console.WriteLine(field3); Console.WriteLine(field4); + Console.WriteLine(IntToDecimal()); + Console.WriteLine(UIntToDecimal()); + Console.WriteLine(LongToDecimal()); + Console.WriteLine(ULongToDecimal()); return 0; } + + public static decimal IntToDecimal() + { + return (decimal)int.MaxValue; + } + + public static decimal UIntToDecimal() + { + return (decimal)uint.MaxValue; + } + + public static decimal LongToDecimal() + { + return (decimal)long.MaxValue; + } + + public static decimal ULongToDecimal() + { + return (decimal)ulong.MaxValue; + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs index 6d829ce95b..e206872a5f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs @@ -36,6 +36,9 @@ static void Main() Issue1747(); CallAmbiguousOutParam(); CallWithInParam(); +#if CS90 + NativeIntTests(new IntPtr(1), 2); +#endif Issue2444.M2(); Issue2741.B.Test(new Issue2741.C()); } @@ -337,6 +340,34 @@ static void InVsRegularParam(int i) #endif #endregion +#if CS90 + static void NativeIntTests(IntPtr i1, nint i2) + { + Console.WriteLine("NativeIntTests(i1):"); + ObjectOrLong((object)i1); + ObjectOrLong((long)i1); + Console.WriteLine("NativeIntTests(i2):"); + ObjectOrLong((object)i2); + ObjectOrLong((long)i2); + Console.WriteLine("NativeIntTests(new IntPtr):"); + ObjectOrLong((object)new IntPtr(3)); + ObjectOrLong((long)new IntPtr(3)); + Console.WriteLine("NativeIntTests(IntPtr.Zero):"); + ObjectOrLong((object)IntPtr.Zero); + ObjectOrLong((long)IntPtr.Zero); + } + + static void ObjectOrLong(object o) + { + Console.WriteLine("object " + o); + } + + static void ObjectOrLong(long l) + { + Console.WriteLine("long " + l); + } +#endif + #region #2444 public struct Issue2444 { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il index 9c178a2ce7..47ebea33a1 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il @@ -79,7 +79,34 @@ pointless: box native int call void [mscorlib]System.Console::WriteLine(string, object) - /* + ldstr "Int32OrNativeReordered(0x7fffffff, false) = {0}" + ldc.i4 0x7fffffff + ldc.i4 0 + call native int Program::Int32OrNativeReordered(int32, bool) + box native int + call void [mscorlib]System.Console::WriteLine(string, object) + + ldstr "Int32OrNativeReordered(0x7fffffff, true) = {0}" + ldc.i4 0x7fffffff + ldc.i4 1 + call native int Program::Int32OrNativeReordered(int32, bool) + box native int + call void [mscorlib]System.Console::WriteLine(string, object) + + ldstr "Int32OrNativeReordered(-1, false) = {0}" + ldc.i4.m1 + ldc.i4 0 + call native int Program::Int32OrNativeReordered(int32, bool) + box native int + call void [mscorlib]System.Console::WriteLine(string, object) + + ldstr "Int32OrNativeReordered(-1, true) = {0}" + ldc.i4.m1 + ldc.i4 1 + call native int Program::Int32OrNativeReordered(int32, bool) + box native int + call void [mscorlib]System.Console::WriteLine(string, object) + ldstr "Int32OrNativeLoopStyle(0x7fffffff):" call void [mscorlib]System.Console::WriteLine(string) ldc.i4 0x7fffffff @@ -101,7 +128,6 @@ pointless: call native int Program::Int32OrNativeDeadCode(int32) box native int call void [mscorlib]System.Console::WriteLine(string, object) - */ ldc.i4 0x7fffffff call void Program::RunInt32OrNativeMultiUse(int32) @@ -127,7 +153,6 @@ pointless: ret } - /* .method public static native int Int32OrNativeReordered(int32 val, bool use_native) { // The spec is ambiguous whether the addition will be in 32-bits or native size. @@ -187,7 +212,6 @@ pointless: conv.u br after_if } - */ .method public static void RunInt32OrNativeMultiUse(int32 val) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/.gitignore b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/.gitignore new file mode 100644 index 0000000000..5bb64d7b20 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/.gitignore @@ -0,0 +1,2 @@ +*.result.il +*.dll diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/InterfaceImplAttributes.il b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/InterfaceImplAttributes.il new file mode 100644 index 0000000000..8ae75838c0 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/InterfaceImplAttributes.il @@ -0,0 +1,75 @@ +.assembly extern mscorlib +{ + .publickeytoken = ( + b7 7a 5c 56 19 34 e0 89 + ) + .ver 4:0:0:0 +} +.assembly InterfaceImplAttributes +{ + .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( + 01 00 07 31 2e 30 2e 30 2e 30 00 00 + ) + .hash algorithm 0x00008004 // SHA1 + .ver 1:0:0:0 +} + +.module InterfaceImplAttributes.dll +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WindowsCui +.corflags 0x00000001 // ILOnly + +.class private auto ansi '' +{ +} // end of class + +.class public auto ansi beforefieldinit TestType + extends [mscorlib]System.Object + implements ITestInterfaceA +{ + .interfaceimpl type ITestInterfaceA + .custom instance void TestAttributeA::.ctor() = ( + 01 00 00 00 + ) + + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method TestType::.ctor + +} // end of class TestType + +.class interface public auto ansi abstract ITestInterfaceA +{ +} // end of class ITestInterfaceA + +.class public auto ansi beforefieldinit TestAttributeA + extends [mscorlib]System.Attribute +{ + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2058 + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Attribute::.ctor() + IL_0006: ret + } // end of method TestAttributeA::.ctor + +} // end of class TestAttributeA + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SortMembers.expected.il b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SortMembers.expected.il new file mode 100644 index 0000000000..2d5208085d --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SortMembers.expected.il @@ -0,0 +1,395 @@ +.assembly extern mscorlib +{ + .publickeytoken = ( + b7 7a 5c 56 19 34 e0 89 + ) + .ver 4:0:0:0 +} +.assembly SecurityDeclarations +{ + .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( + 01 00 07 31 2e 30 2e 30 2e 30 00 00 + ) + .hash algorithm 0x00008004 // SHA1 + .ver 1:0:0:0 +} + +.module SecurityDeclarations.dll +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WindowsCui +.corflags 0x00000001 // ILOnly + +.class private auto ansi '' +{ +} // end of class + +.class private auto ansi beforefieldinit SecurityDeclarations.NestedArrays + extends [mscorlib]System.Object +{ + .permissionset assert = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field object TestBoxed2 = object(object[4](int32(1) int32(2) int32(3) object[3](int32(4) int32(5) int32(6)))) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20d4 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method NestedArrays::.ctor + +} // end of class SecurityDeclarations.NestedArrays + +.class private auto ansi beforefieldinit SecurityDeclarations.SecurityAttrTest + extends [mscorlib]System.Security.Permissions.SecurityAttribute +{ + // Fields + .field private valuetype SecurityDeclarations.TestEnum[] _testEnumArray + .field private int32[] _testInt32Array + .field private string[] _testStringArray + .field private class [mscorlib]System.Type[] _testTypeArray + .field public object TestBoxed + .field public object TestBoxed2 + .field public object TestBoxedArray + .field public object TestBoxedString + .field public object TestBoxedType + .field public valuetype SecurityDeclarations.TestEnum TestEnumType + .field public int32 TestInt32 + .field public string TestString + .field public class [mscorlib]System.Type TestType + + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor ( + valuetype [mscorlib]System.Security.Permissions.SecurityAction action + ) cil managed + { + // Method begins at RVA 0x2059 + // Header size: 1 + // Code size: 10 (0xa) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call instance void [mscorlib]System.Security.Permissions.SecurityAttribute::.ctor(valuetype [mscorlib]System.Security.Permissions.SecurityAction) + IL_0007: nop + IL_0008: nop + IL_0009: ret + } // end of method SecurityAttrTest::.ctor + + .method public hidebysig virtual + instance class [mscorlib]System.Security.IPermission CreatePermission () cil managed + { + // Method begins at RVA 0x2064 + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: nop + IL_0001: newobj instance void [mscorlib]System.NotImplementedException::.ctor() + IL_0006: throw + } // end of method SecurityAttrTest::CreatePermission + + .method public hidebysig specialname + instance valuetype SecurityDeclarations.TestEnum[] get_TestEnumArray () cil managed + { + // Method begins at RVA 0x208e + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::_testEnumArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestEnumArray + + .method public hidebysig specialname + instance int32[] get_TestInt32Array () cil managed + { + // Method begins at RVA 0x207d + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld int32[] SecurityDeclarations.SecurityAttrTest::_testInt32Array + IL_0006: ret + } // end of method SecurityAttrTest::get_TestInt32Array + + .method public hidebysig specialname + instance string[] get_TestStringArray () cil managed + { + // Method begins at RVA 0x206c + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld string[] SecurityDeclarations.SecurityAttrTest::_testStringArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestStringArray + + .method public hidebysig specialname + instance class [mscorlib]System.Type[] get_TestTypeArray () cil managed + { + // Method begins at RVA 0x209f + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::_testTypeArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestTypeArray + + .method public hidebysig specialname + instance void set_TestEnumArray ( + valuetype SecurityDeclarations.TestEnum[] 'value' + ) cil managed + { + // Method begins at RVA 0x2096 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::_testEnumArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestEnumArray + + .method public hidebysig specialname + instance void set_TestInt32Array ( + int32[] 'value' + ) cil managed + { + // Method begins at RVA 0x2085 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld int32[] SecurityDeclarations.SecurityAttrTest::_testInt32Array + IL_0007: ret + } // end of method SecurityAttrTest::set_TestInt32Array + + .method public hidebysig specialname + instance void set_TestStringArray ( + string[] 'value' + ) cil managed + { + // Method begins at RVA 0x2074 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld string[] SecurityDeclarations.SecurityAttrTest::_testStringArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestStringArray + + .method public hidebysig specialname + instance void set_TestTypeArray ( + class [mscorlib]System.Type[] 'value' + ) cil managed + { + // Method begins at RVA 0x20a7 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::_testTypeArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestTypeArray + + // Properties + .property instance valuetype SecurityDeclarations.TestEnum[] TestEnumArray() + { + .get instance valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::get_TestEnumArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestEnumArray(valuetype SecurityDeclarations.TestEnum[]) + } + .property instance int32[] TestInt32Array() + { + .get instance int32[] SecurityDeclarations.SecurityAttrTest::get_TestInt32Array() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestInt32Array(int32[]) + } + .property instance string[] TestStringArray() + { + .get instance string[] SecurityDeclarations.SecurityAttrTest::get_TestStringArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestStringArray(string[]) + } + .property instance class [mscorlib]System.Type[] TestTypeArray() + { + .get instance class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::get_TestTypeArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestTypeArray(class [mscorlib]System.Type[]) + } + +} // end of class SecurityDeclarations.SecurityAttrTest + +.class private auto ansi beforefieldinit SecurityDeclarations.SimpleType + extends [mscorlib]System.Object +{ + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method SimpleType::.ctor + +} // end of class SecurityDeclarations.SimpleType + +.class private auto ansi sealed SecurityDeclarations.TestEnum + extends [mscorlib]System.Enum +{ + // Fields + .field public static literal valuetype SecurityDeclarations.TestEnum A = int32(0) + .field public static literal valuetype SecurityDeclarations.TestEnum B = int32(1) + .field public static literal valuetype SecurityDeclarations.TestEnum C = int32(2) + .field public specialname rtspecialname int32 value__ + +} // end of class SecurityDeclarations.TestEnum + +.class private auto ansi beforefieldinit SecurityDeclarations.TestEnumTypes + extends [mscorlib]System.Object +{ + .permissionset inheritcheck = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field enum SecurityDeclarations.TestEnum TestEnumType = int32(0) + field object TestBoxed = object(int32(1)) + property enum SecurityDeclarations.TestEnum[] TestEnumArray = int32[3](0 1 2) + field object TestBoxed2 = object(object[4](int32(0) int32(1) int32(2) object[1](int32(3)))) + field object TestBoxedArray = object(int32[3](0 1 2)) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20c2 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestEnumTypes::.ctor + +} // end of class SecurityDeclarations.TestEnumTypes + +.class private auto ansi beforefieldinit SecurityDeclarations.TestInt32Types + extends [mscorlib]System.Object +{ + .permissionset permitonly = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field int32 TestInt32 = int32(5) + field object TestBoxed = object(int32(10)) + property int32[] TestInt32Array = int32[3](1 2 3) + field object TestBoxedArray = object(int32[3](4 5 6)) + field object TestBoxed2 = object(object[3](int32(7) int32(8) int32(9))) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20cb + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestInt32Types::.ctor + +} // end of class SecurityDeclarations.TestInt32Types + +.class private auto ansi beforefieldinit SecurityDeclarations.TestStringTypes + extends [mscorlib]System.Object +{ + .permissionset assert = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field string TestString = string('Hello World!') + field object TestBoxedString = object(string('Boxed String')) + property string[] TestStringArray = string[2]('a' 'b') + field object TestBoxedArray = object(string[2]('c' 'd')) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20b0 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestStringTypes::.ctor + +} // end of class SecurityDeclarations.TestStringTypes + +.class private sequential ansi sealed beforefieldinit SecurityDeclarations.TestStruct + extends [mscorlib]System.ValueType +{ + .pack 0 + .size 1 + +} // end of class SecurityDeclarations.TestStruct + +.class private auto ansi beforefieldinit SecurityDeclarations.TestTypeTypes + extends [mscorlib]System.Object +{ + .permissionset demand = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field type TestType = type(SecurityDeclarations.SimpleType) + field object TestBoxed = object(type(SecurityDeclarations.TestEnum)) + property type[] TestTypeArray = type[2](SecurityDeclarations.TestStruct SecurityDeclarations.SimpleType) + field object TestBoxedArray = object(type[2](SecurityDeclarations.TestStringTypes SecurityDeclarations.TestTypeTypes)) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20b9 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestTypeTypes::.ctor + +} // end of class SecurityDeclarations.TestTypeTypes + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SortMembers.il b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SortMembers.il new file mode 100644 index 0000000000..056544a9db --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SortMembers.il @@ -0,0 +1,381 @@ +.assembly extern mscorlib +{ + .publickeytoken = ( + b7 7a 5c 56 19 34 e0 89 + ) + .ver 4:0:0:0 +} +.assembly SecurityDeclarations +{ + .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( + 01 00 07 31 2e 30 2e 30 2e 30 00 00 + ) + .hash algorithm 0x00008004 // SHA1 + .ver 1:0:0:0 +} + +.module SecurityDeclarations.dll +// MVID: {761F919A-2373-48EB-9282-9DAB26913D43} +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WindowsCui +.corflags 0x00000001 // ILOnly + + +.class private auto ansi '' +{ +} // end of class + +.class private sequential ansi sealed beforefieldinit SecurityDeclarations.TestStruct + extends [mscorlib]System.ValueType +{ + .pack 0 + .size 1 + +} // end of class SecurityDeclarations.TestStruct + +.class private auto ansi beforefieldinit SecurityDeclarations.SimpleType + extends [mscorlib]System.Object +{ + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method SimpleType::.ctor + +} // end of class SecurityDeclarations.SimpleType + +.class private auto ansi sealed SecurityDeclarations.TestEnum + extends [mscorlib]System.Enum +{ + // Fields + .field public specialname rtspecialname int32 value__ + .field public static literal valuetype SecurityDeclarations.TestEnum A = int32(0) + .field public static literal valuetype SecurityDeclarations.TestEnum B = int32(1) + .field public static literal valuetype SecurityDeclarations.TestEnum C = int32(2) + +} // end of class SecurityDeclarations.TestEnum + +.class private auto ansi beforefieldinit SecurityDeclarations.SecurityAttrTest + extends [mscorlib]System.Security.Permissions.SecurityAttribute +{ + // Fields + .field private string[] _testStringArray + .field private int32[] _testInt32Array + .field private valuetype SecurityDeclarations.TestEnum[] _testEnumArray + .field private class [mscorlib]System.Type[] _testTypeArray + .field public int32 TestInt32 + .field public class [mscorlib]System.Type TestType + .field public valuetype SecurityDeclarations.TestEnum TestEnumType + .field public object TestBoxed + .field public object TestBoxed2 + .field public string TestString + .field public object TestBoxedString + .field public object TestBoxedArray + .field public object TestBoxedType + + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor ( + valuetype [mscorlib]System.Security.Permissions.SecurityAction action + ) cil managed + { + // Method begins at RVA 0x2059 + // Code size 10 (0xa) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call instance void [mscorlib]System.Security.Permissions.SecurityAttribute::.ctor(valuetype [mscorlib]System.Security.Permissions.SecurityAction) + IL_0007: nop + IL_0008: nop + IL_0009: ret + } // end of method SecurityAttrTest::.ctor + + .method public hidebysig virtual + instance class [mscorlib]System.Security.IPermission CreatePermission () cil managed + { + // Method begins at RVA 0x2064 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: nop + IL_0001: newobj instance void [mscorlib]System.NotImplementedException::.ctor() + IL_0006: throw + } // end of method SecurityAttrTest::CreatePermission + + .method public hidebysig specialname + instance string[] get_TestStringArray () cil managed + { + // Method begins at RVA 0x206c + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld string[] SecurityDeclarations.SecurityAttrTest::_testStringArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestStringArray + + .method public hidebysig specialname + instance void set_TestStringArray ( + string[] 'value' + ) cil managed + { + // Method begins at RVA 0x2074 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld string[] SecurityDeclarations.SecurityAttrTest::_testStringArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestStringArray + + .method public hidebysig specialname + instance int32[] get_TestInt32Array () cil managed + { + // Method begins at RVA 0x207d + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld int32[] SecurityDeclarations.SecurityAttrTest::_testInt32Array + IL_0006: ret + } // end of method SecurityAttrTest::get_TestInt32Array + + .method public hidebysig specialname + instance void set_TestInt32Array ( + int32[] 'value' + ) cil managed + { + // Method begins at RVA 0x2085 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld int32[] SecurityDeclarations.SecurityAttrTest::_testInt32Array + IL_0007: ret + } // end of method SecurityAttrTest::set_TestInt32Array + + .method public hidebysig specialname + instance valuetype SecurityDeclarations.TestEnum[] get_TestEnumArray () cil managed + { + // Method begins at RVA 0x208e + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::_testEnumArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestEnumArray + + .method public hidebysig specialname + instance void set_TestEnumArray ( + valuetype SecurityDeclarations.TestEnum[] 'value' + ) cil managed + { + // Method begins at RVA 0x2096 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::_testEnumArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestEnumArray + + .method public hidebysig specialname + instance class [mscorlib]System.Type[] get_TestTypeArray () cil managed + { + // Method begins at RVA 0x209f + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::_testTypeArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestTypeArray + + .method public hidebysig specialname + instance void set_TestTypeArray ( + class [mscorlib]System.Type[] 'value' + ) cil managed + { + // Method begins at RVA 0x20a7 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::_testTypeArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestTypeArray + + // Properties + .property instance string[] TestStringArray() + { + .get instance string[] SecurityDeclarations.SecurityAttrTest::get_TestStringArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestStringArray(string[]) + } + .property instance int32[] TestInt32Array() + { + .get instance int32[] SecurityDeclarations.SecurityAttrTest::get_TestInt32Array() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestInt32Array(int32[]) + } + .property instance valuetype SecurityDeclarations.TestEnum[] TestEnumArray() + { + .get instance valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::get_TestEnumArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestEnumArray(valuetype SecurityDeclarations.TestEnum[]) + } + .property instance class [mscorlib]System.Type[] TestTypeArray() + { + .get instance class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::get_TestTypeArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestTypeArray(class [mscorlib]System.Type[]) + } + +} // end of class SecurityDeclarations.SecurityAttrTest + +.class private auto ansi beforefieldinit SecurityDeclarations.TestStringTypes + extends [mscorlib]System.Object +{ + .permissionset assert = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field string TestString = string('Hello World!') + field object TestBoxedString = object(string('Boxed String')) + property string[] TestStringArray = string[2]('a' 'b') + field object TestBoxedArray = object(string[2]('c' 'd')) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestStringTypes::.ctor + +} // end of class SecurityDeclarations.TestStringTypes + +.class private auto ansi beforefieldinit SecurityDeclarations.TestTypeTypes + extends [mscorlib]System.Object +{ + .permissionset demand = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field type TestType = type(SecurityDeclarations.SimpleType) + field object TestBoxed = object(type(SecurityDeclarations.TestEnum)) + property type[] TestTypeArray = type[2](SecurityDeclarations.TestStruct SecurityDeclarations.SimpleType) + field object TestBoxedArray = object(type[2](SecurityDeclarations.TestStringTypes SecurityDeclarations.TestTypeTypes)) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestTypeTypes::.ctor + +} // end of class SecurityDeclarations.TestTypeTypes + +.class private auto ansi beforefieldinit SecurityDeclarations.TestEnumTypes + extends [mscorlib]System.Object +{ + .permissionset inheritcheck = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field enum SecurityDeclarations.TestEnum TestEnumType = int32(0) + field object TestBoxed = object(int32(1)) + property enum SecurityDeclarations.TestEnum[] TestEnumArray = int32[3](0 1 2) + field object TestBoxed2 = object(object[4](int32(0) int32(1) int32(2) object[1](int32(3)))) + field object TestBoxedArray = object(int32[3](0 1 2)) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestEnumTypes::.ctor + +} // end of class SecurityDeclarations.TestEnumTypes + +.class private auto ansi beforefieldinit SecurityDeclarations.TestInt32Types + extends [mscorlib]System.Object +{ + .permissionset permitonly = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field int32 TestInt32 = int32(5) + field object TestBoxed = object(int32(10)) + property int32[] TestInt32Array = int32[3](1 2 3) + field object TestBoxedArray = object(int32[3](4 5 6)) + field object TestBoxed2 = object(object[3](int32(7) int32(8) int32(9))) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestInt32Types::.ctor + +} // end of class SecurityDeclarations.TestInt32Types + +.class private auto ansi beforefieldinit SecurityDeclarations.NestedArrays + extends [mscorlib]System.Object +{ + .permissionset assert = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field object TestBoxed2 = object(object[4](int32(1) int32(2) int32(3) object[3](int32(4) int32(5) int32(6)))) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method NestedArrays::.ctor + +} // end of class SecurityDeclarations.NestedArrays + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.cs new file mode 100644 index 0000000000..73998e2bd4 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.cs @@ -0,0 +1,14 @@ +internal class EmptyBodies +{ + public static void RetVoid() + { + } + public static int RetInt() + { + /*Error: Method body consists only of 'ret', but nothing is being returned. Decompiled assembly might be a reference assembly.*/; + } + public static void Nop() + { + /*Error: End of method reached without returning.*/; + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.il new file mode 100644 index 0000000000..bd14a188ca --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.il @@ -0,0 +1,45 @@ +#define CORE_ASSEMBLY "System.Runtime" + +.assembly extern CORE_ASSEMBLY +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 4:0:0:0 +} + +.class private auto ansi beforefieldinit EmptyBodies + extends [CORE_ASSEMBLY]System.Object +{ + // I cannot test a truly empty body because the assembler will automatically add a ret instruction. + + .method public hidebysig static void RetVoid () cil managed + { + .maxstack 8 + ret + } + + .method public hidebysig static int32 RetInt () cil managed + { + .maxstack 8 + ret + } + + .method public hidebysig static void Nop () cil managed + { + .maxstack 8 + nop + } + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x206e + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Example::.ctor + +} // end of class EmptyBodies diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs index 37d64c6e5b..c9e0f56e07 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs @@ -15,9 +15,8 @@ public void MethodUnknownClass() //IL_0007: Expected O, but got Unknown UnknownClass val = new UnknownClass(); int? unknownProperty = val.UnknownProperty; - int? num = unknownProperty.GetValueOrDefault(); - val.UnknownProperty = num; - int? num2 = num; + int? num2 = (val.UnknownProperty = unknownProperty.GetValueOrDefault()); + int? num3 = num2; List list = new List { val[unknownProperty.Value] ?? "", val.NotProperty, @@ -51,10 +50,9 @@ public void MethodUnknownGenericClass() //IL_00e1: Expected O, but got Unknown //IL_00e1: Expected O, but got Unknown UnknownGenericClass val = new UnknownGenericClass(); - UnknownEventArgs unknownProperty = val.UnknownProperty; - val.UnknownProperty = unknownProperty; + UnknownEventArgs val2 = (val.UnknownProperty = val.UnknownProperty); List list = new List { - val[((object)unknownProperty).GetHashCode()] ?? "", + val[((object)val2).GetHashCode()] ?? "", val.NotProperty, val.get_NotPropertyWithGeneric(42), val[42], @@ -63,18 +61,17 @@ public void MethodUnknownGenericClass() }; val.OnEvent += Instance_OnEvent; val.OnEvent -= Instance_OnEvent; - UnknownEventArgs val2 = val[(UnknownEventArgs)null]; - val[new UnknownEventArgs()] = val2; - UnknownEventArgs val3 = val[new UnknownEventArgs(), new UnknownEventArgs()]; - val[new UnknownEventArgs(), new UnknownEventArgs()] = val3; + UnknownEventArgs val3 = val[(UnknownEventArgs)null]; + val[new UnknownEventArgs()] = val3; + UnknownEventArgs val4 = val[new UnknownEventArgs(), new UnknownEventArgs()]; + val[new UnknownEventArgs(), new UnknownEventArgs()] = val4; } public void MethodUnknownStatic() { - int? unknownProperty = UnknownStaticClass.UnknownProperty; - UnknownStaticClass.UnknownProperty = unknownProperty; + int? num = (UnknownStaticClass.UnknownProperty = UnknownStaticClass.UnknownProperty); List list = new List { - UnknownStaticClass[unknownProperty.Value] ?? "", + UnknownStaticClass[num.Value] ?? "", UnknownStaticClass.NotProperty, UnknownStaticClass.get_NotPropertyWithGeneric(42), UnknownStaticClass[42], @@ -87,10 +84,9 @@ public void MethodUnknownStatic() public void MethodUnknownStaticGeneric() { - string unknownProperty = UnknownStaticGenericClass.UnknownProperty; - UnknownStaticGenericClass.UnknownProperty = unknownProperty; + string text = (UnknownStaticGenericClass.UnknownProperty = UnknownStaticGenericClass.UnknownProperty); List list = new List { - UnknownStaticGenericClass[unknownProperty.Length] ?? "", + UnknownStaticGenericClass[text.Length] ?? "", UnknownStaticGenericClass.NotProperty, UnknownStaticGenericClass.get_NotPropertyWithGeneric(42), UnknownStaticGenericClass[42], @@ -101,6 +97,15 @@ public void MethodUnknownStaticGeneric() UnknownStaticGenericClass.OnEvent -= Instance_OnEvent; } + public void MethodUnknownIndexerInitializer() + { + //IL_0006: Unknown result type (might be due to invalid IL or missing references) + new UnknownClass { + ["a"] = 1, + ["b"] = 2 + }; + } + private void Instance_OnEvent(object sender, EventArgs e) { throw new NotImplementedException(); @@ -121,4 +126,4 @@ private static void Instance_OnEvent(object sender, object e) throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.il index 7fd6756ecc..35fe005d14 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.il @@ -378,6 +378,28 @@ IL_0098: ret } // end of method UnknownClassTest::MethodUnknownStaticGeneric + .method public hidebysig + instance void MethodUnknownIndexerInitializer () cil managed + { + // Method begins at RVA 0x2050 + // Code size 32 (0x20) + .maxstack 8 + + IL_0000: nop + IL_0001: newobj instance void [UnknownAssembly]UnknownNamespace.UnknownClass::.ctor() + IL_0006: dup + IL_0007: ldstr "a" + IL_000c: ldc.i4.1 + IL_000d: callvirt instance void [UnknownAssembly]UnknownNamespace.UnknownClass::set_Item(string, int32) + IL_0012: nop + IL_0013: ldstr "b" + IL_0018: ldc.i4.2 + IL_0019: callvirt instance void [UnknownAssembly]UnknownNamespace.UnknownClass::set_Item(string, int32) + IL_001e: nop + IL_001f: ret + } // end of method C::MethodUnknownIndexerInitializer + + .method /* 100663301 */ private hidebysig instance void Instance_OnEvent ( object sender, diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs index c853317bc3..4ba4af4341 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs @@ -2,16 +2,16 @@ using System.Reflection; using System.Runtime.CompilerServices; +[assembly: AssemblyMetadata(".NETFrameworkAssembly", "")] +[assembly: CLSCompliant(false)] [assembly: AssemblyFileVersion("4.0.0.0")] [assembly: AssemblyInformationalVersion("4.0.0.0")] [assembly: AssemblyTitle("System.Runtime.CompilerServices.Unsafe")] [assembly: AssemblyDescription("System.Runtime.CompilerServices.Unsafe")] -[assembly: AssemblyMetadata(".NETFrameworkAssembly", "")] [assembly: AssemblyMetadata("Serviceable", "True")] [assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] [assembly: AssemblyCompany("Microsoft Corporation")] [assembly: AssemblyProduct("Microsoft® .NET Framework")] -[assembly: CLSCompliant(false)] internal sealed class ExtraUnsafeTests { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il index 58552a14fb..f0fe8207ec 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il @@ -493,9 +493,35 @@ #ifdef netcoreapp #else +.class private auto ansi sealed beforefieldinit Microsoft.CodeAnalysis.EmbeddedAttribute + extends [CORE_ASSEMBLY]System.Attribute +{ + .custom instance void [CORE_ASSEMBLY]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = ( + 01 00 00 00 + ) + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [CORE_ASSEMBLY]System.Attribute::.ctor() + IL_0006: ret + } // end of method EmbeddedAttribute::.ctor + +} // end of class Microsoft.CodeAnalysis.EmbeddedAttribute + .class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.IsReadOnlyAttribute extends [CORE_ASSEMBLY]System.Attribute { + .custom instance void [CORE_ASSEMBLY]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = ( + 01 00 00 00 + ) .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml index e0708da65a..e5d691c3da 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml +++ b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml @@ -1,11 +1,11 @@ - + + + + + + \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AutoProperties.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AutoProperties.cs index 2a9ae64408..8a540a601f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AutoProperties.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AutoProperties.cs @@ -4,6 +4,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { internal class AutoProperties { +#if CS110 + public required int RequiredField; +#endif public int A { get; } = 1; public int B { get; set; } = 2; @@ -22,9 +25,23 @@ internal class AutoProperties public int issue1319 { get; } +#if CS110 + public required int RequiredProperty { get; set; } +#endif + public AutoProperties(int issue1319) { this.issue1319 = issue1319; +#if CS110 + RequiredProperty = 42; + RequiredField = 42; +#endif } } +#if !NET70 + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal sealed class RequiredMemberAttribute : Attribute + { + } +#endif } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index 66da6e06ce..1cc74865c3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -201,6 +201,12 @@ public struct CustomStruct { throw new NotImplementedException(); } +#if CS110 + public static CustomStruct operator >>>(CustomStruct lhs, int rhs) + { + throw new NotImplementedException(); + } +#endif public static CustomStruct operator &(CustomStruct lhs, CustomStruct rhs) { throw new NotImplementedException(); @@ -287,6 +293,18 @@ public struct CustomStruct2 public static string StaticStringProperty { get; set; } + private static void Use(ref byte b) + { + } + + private static void Use(ref sbyte b) + { + } + + private static void Use(ref T num) + { + } + private static CustomStruct2 GetStruct() { throw new NotImplementedException(); @@ -781,9 +799,10 @@ public void IncrementStaticPropertyShort() public static void ByteAddTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p += 5; - //l += 5; + byte b = 0; + p += 5; + b += 5; + Use(ref b); byteField += 5; ByteProp += 5; c.ByteField += 5; @@ -807,9 +826,10 @@ public static void ByteAddTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteSubtractTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p -= 5; - //l -= 5; + byte b = 0; + p -= 5; + b -= 5; + Use(ref b); byteField -= 5; ByteProp -= 5; c.ByteField -= 5; @@ -833,9 +853,10 @@ public static void ByteSubtractTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteMultiplyTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p *= 5; - //l *= 5; + byte b = 0; + p *= 5; + b *= 5; + Use(ref b); byteField *= 5; ByteProp *= 5; c.ByteField *= 5; @@ -859,9 +880,10 @@ public static void ByteMultiplyTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteDivideTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p /= 5; - //l /= 5; + byte b = 0; + p /= 5; + b /= 5; + Use(ref b); byteField /= 5; ByteProp /= 5; c.ByteField /= 5; @@ -885,9 +907,10 @@ public static void ByteDivideTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteModulusTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p %= 5; - //l %= 5; + byte b = 0; + p %= 5; + b %= 5; + Use(ref b); byteField %= 5; ByteProp %= 5; c.ByteField %= 5; @@ -911,9 +934,10 @@ public static void ByteModulusTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteLeftShiftTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p <<= 5; - //l <<= 5; + byte b = 0; + p <<= 5; + b <<= 5; + Use(ref b); byteField <<= 5; ByteProp <<= 5; c.ByteField <<= 5; @@ -937,9 +961,10 @@ public static void ByteLeftShiftTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteRightShiftTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p >>= 5; - //l >>= 5; + byte b = 0; + p >>= 5; + b >>= 5; + Use(ref b); byteField >>= 5; ByteProp >>= 5; c.ByteField >>= 5; @@ -963,9 +988,10 @@ public static void ByteRightShiftTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteBitAndTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p &= 5; - //l &= 5; + byte b = 0; + p &= c.ByteField; + b &= c.ByteField; + Use(ref b); byteField &= 5; ByteProp &= 5; c.ByteField &= 5; @@ -989,9 +1015,10 @@ public static void ByteBitAndTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteBitOrTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p |= 5; - //l |= 5; + byte b = 0; + p |= c.ByteField; + b |= c.ByteField; + Use(ref b); byteField |= 5; ByteProp |= 5; c.ByteField |= 5; @@ -1015,9 +1042,10 @@ public static void ByteBitOrTest(byte p, CustomClass c, CustomStruct2 s) public static void ByteBitXorTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //p ^= 5; - //l ^= 5; + byte b = 0; + p ^= c.ByteField; + b ^= c.ByteField; + Use(ref b); byteField ^= 5; ByteProp ^= 5; c.ByteField ^= 5; @@ -1041,9 +1069,10 @@ public static void ByteBitXorTest(byte p, CustomClass c, CustomStruct2 s) public static void BytePostIncTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //X(p++); - //X(l++); + byte b = 0; + X(p++); + X(b++); + Use(ref b); X(byteField++); X(ByteProp++); X(c.ByteField++); @@ -1067,9 +1096,10 @@ public static void BytePostIncTest(byte p, CustomClass c, CustomStruct2 s) public static void BytePreIncTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //X(++p); - //X(++l); + byte b = 0; + X(++p); + X(++b); + Use(ref b); X(++byteField); X(++ByteProp); X(++c.ByteField); @@ -1092,9 +1122,10 @@ public static void BytePreIncTest(byte p, CustomClass c, CustomStruct2 s) } public static void BytePostDecTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //X(p--); - //X(l--); + byte b = 0; + X(p--); + X(b--); + Use(ref b); X(byteField--); X(ByteProp--); X(c.ByteField--); @@ -1118,9 +1149,10 @@ public static void BytePostDecTest(byte p, CustomClass c, CustomStruct2 s) public static void BytePreDecTest(byte p, CustomClass c, CustomStruct2 s) { - //byte l = 0; - //X(--p); - //X(--l); + byte b = 0; + X(--p); + X(--b); + Use(ref b); X(--byteField); X(--ByteProp); X(--c.ByteField); @@ -1143,9 +1175,10 @@ public static void BytePreDecTest(byte p, CustomClass c, CustomStruct2 s) } public static void SbyteAddTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p += 5; - //l += 5; + sbyte b = 0; + p += 5; + b += 5; + Use(ref b); sbyteField += 5; SbyteProp += 5; c.SbyteField += 5; @@ -1169,9 +1202,10 @@ public static void SbyteAddTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteSubtractTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p -= 5; - //l -= 5; + sbyte b = 0; + p -= 5; + b -= 5; + Use(ref b); sbyteField -= 5; SbyteProp -= 5; c.SbyteField -= 5; @@ -1195,9 +1229,10 @@ public static void SbyteSubtractTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteMultiplyTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p *= 5; - //l *= 5; + sbyte b = 0; + p *= 5; + b *= 5; + Use(ref b); sbyteField *= 5; SbyteProp *= 5; c.SbyteField *= 5; @@ -1221,9 +1256,10 @@ public static void SbyteMultiplyTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteDivideTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p /= 5; - //l /= 5; + sbyte b = 0; + p /= 5; + b /= 5; + Use(ref b); sbyteField /= 5; SbyteProp /= 5; c.SbyteField /= 5; @@ -1247,9 +1283,10 @@ public static void SbyteDivideTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteModulusTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p %= 5; - //l %= 5; + sbyte b = 0; + p %= 5; + b %= 5; + Use(ref b); sbyteField %= 5; SbyteProp %= 5; c.SbyteField %= 5; @@ -1273,9 +1310,10 @@ public static void SbyteModulusTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteLeftShiftTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p <<= 5; - //l <<= 5; + sbyte b = 0; + p <<= 5; + b <<= 5; + Use(ref b); sbyteField <<= 5; SbyteProp <<= 5; c.SbyteField <<= 5; @@ -1299,9 +1337,10 @@ public static void SbyteLeftShiftTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteRightShiftTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p >>= 5; - //l >>= 5; + sbyte b = 0; + p >>= 5; + b >>= 5; + Use(ref b); sbyteField >>= 5; SbyteProp >>= 5; c.SbyteField >>= 5; @@ -1325,9 +1364,10 @@ public static void SbyteRightShiftTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteBitAndTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p &= 5; - //l &= 5; + sbyte b = 0; + p &= 5; + b &= 5; + Use(ref b); sbyteField &= 5; SbyteProp &= 5; c.SbyteField &= 5; @@ -1351,9 +1391,10 @@ public static void SbyteBitAndTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteBitOrTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p |= 5; - //l |= 5; + sbyte b = 0; + p |= 5; + b |= 5; + Use(ref b); sbyteField |= 5; SbyteProp |= 5; c.SbyteField |= 5; @@ -1377,9 +1418,10 @@ public static void SbyteBitOrTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbyteBitXorTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //p ^= 5; - //l ^= 5; + sbyte b = 0; + p ^= 5; + b ^= 5; + Use(ref b); sbyteField ^= 5; SbyteProp ^= 5; c.SbyteField ^= 5; @@ -1403,9 +1445,10 @@ public static void SbyteBitXorTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbytePostIncTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //X(p++); - //X(l++); + sbyte b = 0; + X(p++); + X(b++); + Use(ref b); X(sbyteField++); X(SbyteProp++); X(c.SbyteField++); @@ -1429,9 +1472,10 @@ public static void SbytePostIncTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbytePreIncTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //X(++p); - //X(++l); + sbyte b = 0; + X(++p); + X(++b); + Use(ref b); X(++sbyteField); X(++SbyteProp); X(++c.SbyteField); @@ -1454,9 +1498,10 @@ public static void SbytePreIncTest(sbyte p, CustomClass c, CustomStruct2 s) } public static void SbytePostDecTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //X(p--); - //X(l--); + sbyte b = 0; + X(p--); + X(b--); + Use(ref b); X(sbyteField--); X(SbyteProp--); X(c.SbyteField--); @@ -1480,9 +1525,10 @@ public static void SbytePostDecTest(sbyte p, CustomClass c, CustomStruct2 s) public static void SbytePreDecTest(sbyte p, CustomClass c, CustomStruct2 s) { - //sbyte l = 0; - //X(--p); - //X(--l); + sbyte b = 0; + X(--p); + X(--b); + Use(ref b); X(--sbyteField); X(--SbyteProp); X(--c.SbyteField); @@ -1505,9 +1551,10 @@ public static void SbytePreDecTest(sbyte p, CustomClass c, CustomStruct2 s) } public static void ShortAddTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p += 5; - //l += 5; + short num = 0; + p += 5; + num += 5; + Use(ref num); shortField += 5; ShortProp += 5; c.ShortField += 5; @@ -1531,9 +1578,10 @@ public static void ShortAddTest(short p, CustomClass c, CustomStruct2 s) public static void ShortSubtractTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p -= 5; - //l -= 5; + short num = 0; + p -= 5; + num -= 5; + Use(ref num); shortField -= 5; ShortProp -= 5; c.ShortField -= 5; @@ -1557,9 +1605,10 @@ public static void ShortSubtractTest(short p, CustomClass c, CustomStruct2 s) public static void ShortMultiplyTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p *= 5; - //l *= 5; + short num = 0; + p *= 5; + num *= 5; + Use(ref num); shortField *= 5; ShortProp *= 5; c.ShortField *= 5; @@ -1583,9 +1632,10 @@ public static void ShortMultiplyTest(short p, CustomClass c, CustomStruct2 s) public static void ShortDivideTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p /= 5; - //l /= 5; + short num = 0; + p /= 5; + num /= 5; + Use(ref num); shortField /= 5; ShortProp /= 5; c.ShortField /= 5; @@ -1609,9 +1659,10 @@ public static void ShortDivideTest(short p, CustomClass c, CustomStruct2 s) public static void ShortModulusTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p %= 5; - //l %= 5; + short num = 0; + p %= 5; + num %= 5; + Use(ref num); shortField %= 5; ShortProp %= 5; c.ShortField %= 5; @@ -1635,9 +1686,10 @@ public static void ShortModulusTest(short p, CustomClass c, CustomStruct2 s) public static void ShortLeftShiftTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p <<= 5; - //l <<= 5; + short num = 0; + p <<= 5; + num <<= 5; + Use(ref num); shortField <<= 5; ShortProp <<= 5; c.ShortField <<= 5; @@ -1661,9 +1713,10 @@ public static void ShortLeftShiftTest(short p, CustomClass c, CustomStruct2 s) public static void ShortRightShiftTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p >>= 5; - //l >>= 5; + short num = 0; + p >>= 5; + num >>= 5; + Use(ref num); shortField >>= 5; ShortProp >>= 5; c.ShortField >>= 5; @@ -1685,11 +1738,36 @@ public static void ShortRightShiftTest(short p, CustomClass c, CustomStruct2 s) #endif } +#if CS110 + public static void ShortUnsignedRightShiftTest(short p, CustomClass c, CustomStruct2 s) + { + //X(p >>>= 5); + shortField >>>= 5; + ShortProp >>>= 5; + c.ShortField >>>= 5; + c.ShortProp >>>= 5; + s.ShortField >>>= 5; + s.ShortProp >>>= 5; + customClassField.ShortField >>>= 5; + customClassField.ShortProp >>>= 5; + otherCustomStructField.ShortField >>>= 5; + otherCustomStructField.ShortProp >>>= 5; + CustomClassProp.ShortField >>>= 5; + CustomClassProp.ShortProp >>>= 5; + GetClass().ShortField >>>= 5; + GetClass().ShortProp >>>= 5; + GetRefStruct().ShortField >>>= 5; + GetRefStruct().ShortProp >>>= 5; + GetRefShort() >>>= 5; + } +#endif + public static void ShortBitAndTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p &= 5; - //l &= 5; + short num = 0; + p &= 5; + num &= 5; + Use(ref num); shortField &= 5; ShortProp &= 5; c.ShortField &= 5; @@ -1713,9 +1791,10 @@ public static void ShortBitAndTest(short p, CustomClass c, CustomStruct2 s) public static void ShortBitOrTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p |= 5; - //l |= 5; + short num = 0; + p |= 5; + num |= 5; + Use(ref num); shortField |= 5; ShortProp |= 5; c.ShortField |= 5; @@ -1739,9 +1818,10 @@ public static void ShortBitOrTest(short p, CustomClass c, CustomStruct2 s) public static void ShortBitXorTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //p ^= 5; - //l ^= 5; + short num = 0; + p ^= 5; + num ^= 5; + Use(ref num); shortField ^= 5; ShortProp ^= 5; c.ShortField ^= 5; @@ -1765,9 +1845,10 @@ public static void ShortBitXorTest(short p, CustomClass c, CustomStruct2 s) public static void ShortPostIncTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //X(p++); - //X(l++); + short num = 0; + X(p++); + X(num++); + Use(ref num); X(shortField++); X(ShortProp++); X(c.ShortField++); @@ -1791,9 +1872,10 @@ public static void ShortPostIncTest(short p, CustomClass c, CustomStruct2 s) public static void ShortPreIncTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //X(++p); - //X(++l); + short num = 0; + X(++p); + X(++num); + Use(ref num); X(++shortField); X(++ShortProp); X(++c.ShortField); @@ -1816,9 +1898,10 @@ public static void ShortPreIncTest(short p, CustomClass c, CustomStruct2 s) } public static void ShortPostDecTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //X(p--); - //X(l--); + short num = 0; + X(p--); + X(num--); + Use(ref num); X(shortField--); X(ShortProp--); X(c.ShortField--); @@ -1842,9 +1925,10 @@ public static void ShortPostDecTest(short p, CustomClass c, CustomStruct2 s) public static void ShortPreDecTest(short p, CustomClass c, CustomStruct2 s) { - //short l = 0; - //X(--p); - //X(--l); + short num = 0; + X(--p); + X(--num); + Use(ref num); X(--shortField); X(--ShortProp); X(--c.ShortField); @@ -1867,9 +1951,10 @@ public static void ShortPreDecTest(short p, CustomClass c, CustomStruct2 s) } public static void UshortAddTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p += 5; - //l += 5; + ushort num = 0; + p += 5; + num += 5; + Use(ref num); ushortField += 5; UshortProp += 5; c.UshortField += 5; @@ -1893,9 +1978,10 @@ public static void UshortAddTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortSubtractTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p -= 5; - //l -= 5; + ushort num = 0; + p -= 5; + num -= 5; + Use(ref num); ushortField -= 5; UshortProp -= 5; c.UshortField -= 5; @@ -1919,9 +2005,10 @@ public static void UshortSubtractTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortMultiplyTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p *= 5; - //l *= 5; + ushort num = 0; + p *= 5; + num *= 5; + Use(ref num); ushortField *= 5; UshortProp *= 5; c.UshortField *= 5; @@ -1945,9 +2032,10 @@ public static void UshortMultiplyTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortDivideTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p /= 5; - //l /= 5; + ushort num = 0; + p /= 5; + num /= 5; + Use(ref num); ushortField /= 5; UshortProp /= 5; c.UshortField /= 5; @@ -1971,9 +2059,10 @@ public static void UshortDivideTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortModulusTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p %= 5; - //l %= 5; + ushort num = 0; + p %= 5; + num %= 5; + Use(ref num); ushortField %= 5; UshortProp %= 5; c.UshortField %= 5; @@ -1997,9 +2086,10 @@ public static void UshortModulusTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortLeftShiftTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p <<= 5; - //l <<= 5; + ushort num = 0; + p <<= 5; + num <<= 5; + Use(ref num); ushortField <<= 5; UshortProp <<= 5; c.UshortField <<= 5; @@ -2023,9 +2113,10 @@ public static void UshortLeftShiftTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortRightShiftTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p >>= 5; - //l >>= 5; + ushort num = 0; + p >>= 5; + num >>= 5; + Use(ref num); ushortField >>= 5; UshortProp >>= 5; c.UshortField >>= 5; @@ -2047,11 +2138,38 @@ public static void UshortRightShiftTest(ushort p, CustomClass c, CustomStruct2 s #endif } - public static void UshortBitAndTest(ushort p, CustomClass c, CustomStruct2 s) +#if CS110 + public static void UshortUnsignedRightShiftTest(ushort p, CustomClass c, CustomStruct2 s) { //ushort l = 0; - //p &= 5; - //l &= 5; + //p >>>= 5; + //l >>>= 5; + ushortField >>>= 5; + UshortProp >>>= 5; + c.UshortField >>>= 5; + c.UshortProp >>>= 5; + s.UshortField >>>= 5; + s.UshortProp >>>= 5; + customClassField.UshortField >>>= 5; + customClassField.UshortProp >>>= 5; + otherCustomStructField.UshortField >>>= 5; + otherCustomStructField.UshortProp >>>= 5; + CustomClassProp.UshortField >>>= 5; + CustomClassProp.UshortProp >>>= 5; + GetClass().UshortField >>>= 5; + GetClass().UshortProp >>>= 5; + GetRefStruct().UshortField >>>= 5; + GetRefStruct().UshortProp >>>= 5; + GetRefUshort() >>>= 5; + } +#endif + + public static void UshortBitAndTest(ushort p, CustomClass c, CustomStruct2 s) + { + ushort num = 0; + p &= c.UshortField; + num &= c.UshortField; + Use(ref num); ushortField &= 5; UshortProp &= 5; c.UshortField &= 5; @@ -2075,9 +2193,10 @@ public static void UshortBitAndTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortBitOrTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p |= 5; - //l |= 5; + ushort num = 0; + p |= c.UshortField; + num |= c.UshortField; + Use(ref num); ushortField |= 5; UshortProp |= 5; c.UshortField |= 5; @@ -2101,9 +2220,10 @@ public static void UshortBitOrTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortBitXorTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //p ^= 5; - //l ^= 5; + ushort num = 0; + p ^= c.UshortField; + num ^= c.UshortField; + Use(ref num); ushortField ^= 5; UshortProp ^= 5; c.UshortField ^= 5; @@ -2127,9 +2247,10 @@ public static void UshortBitXorTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortPostIncTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //X(p++); - //X(l++); + ushort num = 0; + X(p++); + X(num++); + Use(ref num); X(ushortField++); X(UshortProp++); X(c.UshortField++); @@ -2153,9 +2274,10 @@ public static void UshortPostIncTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortPreIncTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //X(++p); - //X(++l); + ushort num = 0; + X(++p); + X(++num); + Use(ref num); X(++ushortField); X(++UshortProp); X(++c.UshortField); @@ -2178,9 +2300,10 @@ public static void UshortPreIncTest(ushort p, CustomClass c, CustomStruct2 s) } public static void UshortPostDecTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //X(p--); - //X(l--); + ushort num = 0; + X(p--); + X(num--); + Use(ref num); X(ushortField--); X(UshortProp--); X(c.UshortField--); @@ -2204,9 +2327,10 @@ public static void UshortPostDecTest(ushort p, CustomClass c, CustomStruct2 s) public static void UshortPreDecTest(ushort p, CustomClass c, CustomStruct2 s) { - //ushort l = 0; - //X(--p); - //X(--l); + ushort num = 0; + X(--p); + X(--num); + Use(ref num); X(--ushortField); X(--UshortProp); X(--c.UshortField); @@ -2229,9 +2353,10 @@ public static void UshortPreDecTest(ushort p, CustomClass c, CustomStruct2 s) } public static void IntAddTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p += 5; - //l += 5; + int num = 0; + p += 5; + num += 5; + Use(ref num); intField += 5; IntProp += 5; c.IntField += 5; @@ -2255,9 +2380,10 @@ public static void IntAddTest(int p, CustomClass c, CustomStruct2 s) public static void IntSubtractTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p -= 5; - //l -= 5; + int num = 0; + p -= 5; + num -= 5; + Use(ref num); intField -= 5; IntProp -= 5; c.IntField -= 5; @@ -2281,9 +2407,10 @@ public static void IntSubtractTest(int p, CustomClass c, CustomStruct2 s) public static void IntMultiplyTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p *= 5; - //l *= 5; + int num = 0; + p *= 5; + num *= 5; + Use(ref num); intField *= 5; IntProp *= 5; c.IntField *= 5; @@ -2307,9 +2434,10 @@ public static void IntMultiplyTest(int p, CustomClass c, CustomStruct2 s) public static void IntDivideTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p /= 5; - //l /= 5; + int num = 0; + p /= 5; + num /= 5; + Use(ref num); intField /= 5; IntProp /= 5; c.IntField /= 5; @@ -2333,9 +2461,10 @@ public static void IntDivideTest(int p, CustomClass c, CustomStruct2 s) public static void IntModulusTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p %= 5; - //l %= 5; + int num = 0; + p %= 5; + num %= 5; + Use(ref num); intField %= 5; IntProp %= 5; c.IntField %= 5; @@ -2359,9 +2488,10 @@ public static void IntModulusTest(int p, CustomClass c, CustomStruct2 s) public static void IntLeftShiftTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p <<= 5; - //l <<= 5; + int num = 0; + p <<= 5; + num <<= 5; + Use(ref num); intField <<= 5; IntProp <<= 5; c.IntField <<= 5; @@ -2385,9 +2515,10 @@ public static void IntLeftShiftTest(int p, CustomClass c, CustomStruct2 s) public static void IntRightShiftTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p >>= 5; - //l >>= 5; + int num = 0; + p >>= 5; + num >>= 5; + Use(ref num); intField >>= 5; IntProp >>= 5; c.IntField >>= 5; @@ -2409,11 +2540,36 @@ public static void IntRightShiftTest(int p, CustomClass c, CustomStruct2 s) #endif } +#if CS110 + public static void IntUnsignedRightShiftTest(int p, CustomClass c, CustomStruct2 s) + { + X(p >>>= 5); + intField >>>= 5; + IntProp >>>= 5; + c.IntField >>>= 5; + c.IntProp >>>= 5; + s.IntField >>>= 5; + s.IntProp >>>= 5; + customClassField.IntField >>>= 5; + customClassField.IntProp >>>= 5; + otherCustomStructField.IntField >>>= 5; + otherCustomStructField.IntProp >>>= 5; + CustomClassProp.IntField >>>= 5; + CustomClassProp.IntProp >>>= 5; + GetClass().IntField >>>= 5; + GetClass().IntProp >>>= 5; + GetRefStruct().IntField >>>= 5; + GetRefStruct().IntProp >>>= 5; + GetRefInt() >>>= 5; + } +#endif + public static void IntBitAndTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p &= 5; - //l &= 5; + int num = 0; + p &= 5; + num &= 5; + Use(ref num); intField &= 5; IntProp &= 5; c.IntField &= 5; @@ -2437,9 +2593,10 @@ public static void IntBitAndTest(int p, CustomClass c, CustomStruct2 s) public static void IntBitOrTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p |= 5; - //l |= 5; + int num = 0; + p |= 5; + num |= 5; + Use(ref num); intField |= 5; IntProp |= 5; c.IntField |= 5; @@ -2463,9 +2620,10 @@ public static void IntBitOrTest(int p, CustomClass c, CustomStruct2 s) public static void IntBitXorTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //p ^= 5; - //l ^= 5; + int num = 0; + p ^= 5; + num ^= 5; + Use(ref num); intField ^= 5; IntProp ^= 5; c.IntField ^= 5; @@ -2489,9 +2647,10 @@ public static void IntBitXorTest(int p, CustomClass c, CustomStruct2 s) public static void IntPostIncTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //X(p++); - //X(l++); + int num = 0; + X(p++); + X(num++); + Use(ref num); X(intField++); X(IntProp++); X(c.IntField++); @@ -2515,9 +2674,10 @@ public static void IntPostIncTest(int p, CustomClass c, CustomStruct2 s) public static void IntPreIncTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //X(++p); - //X(++l); + int num = 0; + X(++p); + X(++num); + Use(ref num); X(++intField); X(++IntProp); X(++c.IntField); @@ -2540,9 +2700,10 @@ public static void IntPreIncTest(int p, CustomClass c, CustomStruct2 s) } public static void IntPostDecTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //X(p--); - //X(l--); + int num = 0; + X(p--); + X(num--); + Use(ref num); X(intField--); X(IntProp--); X(c.IntField--); @@ -2566,9 +2727,10 @@ public static void IntPostDecTest(int p, CustomClass c, CustomStruct2 s) public static void IntPreDecTest(int p, CustomClass c, CustomStruct2 s) { - //int l = 0; - //X(--p); - //X(--l); + int num = 0; + X(--p); + X(--num); + Use(ref num); X(--intField); X(--IntProp); X(--c.IntField); @@ -2591,9 +2753,10 @@ public static void IntPreDecTest(int p, CustomClass c, CustomStruct2 s) } public static void UintAddTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p += 5u; - //l += 5u; + uint num = 0u; + p += 5; + num += 5; + Use(ref num); uintField += 5u; UintProp += 5u; c.UintField += 5u; @@ -2617,9 +2780,10 @@ public static void UintAddTest(uint p, CustomClass c, CustomStruct2 s) public static void UintSubtractTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p -= 5u; - //l -= 5u; + uint num = 0u; + p -= 5; + num -= 5; + Use(ref num); uintField -= 5u; UintProp -= 5u; c.UintField -= 5u; @@ -2643,9 +2807,10 @@ public static void UintSubtractTest(uint p, CustomClass c, CustomStruct2 s) public static void UintMultiplyTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p *= 5u; - //l *= 5u; + uint num = 0u; + p *= 5; + num *= 5; + Use(ref num); uintField *= 5u; UintProp *= 5u; c.UintField *= 5u; @@ -2669,9 +2834,10 @@ public static void UintMultiplyTest(uint p, CustomClass c, CustomStruct2 s) public static void UintDivideTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p /= 5u; - //l /= 5u; + uint num = 0u; + p /= 5; + num /= 5; + Use(ref num); uintField /= 5u; UintProp /= 5u; c.UintField /= 5u; @@ -2695,9 +2861,10 @@ public static void UintDivideTest(uint p, CustomClass c, CustomStruct2 s) public static void UintModulusTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p %= 5u; - //l %= 5u; + uint num = 0u; + p %= 5; + num %= 5; + Use(ref num); uintField %= 5u; UintProp %= 5u; c.UintField %= 5u; @@ -2721,9 +2888,10 @@ public static void UintModulusTest(uint p, CustomClass c, CustomStruct2 s) public static void UintLeftShiftTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p <<= 5; - //l <<= 5; + uint num = 0u; + p <<= 5; + num <<= 5; + Use(ref num); uintField <<= 5; UintProp <<= 5; c.UintField <<= 5; @@ -2747,9 +2915,10 @@ public static void UintLeftShiftTest(uint p, CustomClass c, CustomStruct2 s) public static void UintRightShiftTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p >>= 5; - //l >>= 5; + uint num = 0u; + p >>= 5; + num >>= 5; + Use(ref num); uintField >>= 5; UintProp >>= 5; c.UintField >>= 5; @@ -2773,9 +2942,10 @@ public static void UintRightShiftTest(uint p, CustomClass c, CustomStruct2 s) public static void UintBitAndTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p &= 5u; - //l &= 5u; + uint num = 0u; + p &= 5u; + num &= 5u; + Use(ref num); uintField &= 5u; UintProp &= 5u; c.UintField &= 5u; @@ -2799,9 +2969,10 @@ public static void UintBitAndTest(uint p, CustomClass c, CustomStruct2 s) public static void UintBitOrTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p |= 5u; - //l |= 5u; + uint num = 0u; + p |= 5u; + num |= 5u; + Use(ref num); uintField |= 5u; UintProp |= 5u; c.UintField |= 5u; @@ -2825,9 +2996,10 @@ public static void UintBitOrTest(uint p, CustomClass c, CustomStruct2 s) public static void UintBitXorTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //p ^= 5u; - //l ^= 5u; + uint num = 0u; + p ^= 5u; + num ^= 5u; + Use(ref num); uintField ^= 5u; UintProp ^= 5u; c.UintField ^= 5u; @@ -2851,9 +3023,10 @@ public static void UintBitXorTest(uint p, CustomClass c, CustomStruct2 s) public static void UintPostIncTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //X(p++); - //X(l++); + uint num = 0u; + X(p++); + X(num++); + Use(ref num); X(uintField++); X(UintProp++); X(c.UintField++); @@ -2877,9 +3050,10 @@ public static void UintPostIncTest(uint p, CustomClass c, CustomStruct2 s) public static void UintPreIncTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //X(++p); - //X(++l); + uint num = 0u; + X(++p); + X(++num); + Use(ref num); X(++uintField); X(++UintProp); X(++c.UintField); @@ -2902,9 +3076,10 @@ public static void UintPreIncTest(uint p, CustomClass c, CustomStruct2 s) } public static void UintPostDecTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //X(p--); - //X(l--); + uint num = 0u; + X(p--); + X(num--); + Use(ref num); X(uintField--); X(UintProp--); X(c.UintField--); @@ -2928,9 +3103,10 @@ public static void UintPostDecTest(uint p, CustomClass c, CustomStruct2 s) public static void UintPreDecTest(uint p, CustomClass c, CustomStruct2 s) { - //uint l = 0; - //X(--p); - //X(--l); + uint num = 0u; + X(--p); + X(--num); + Use(ref num); X(--uintField); X(--UintProp); X(--c.UintField); @@ -2953,9 +3129,10 @@ public static void UintPreDecTest(uint p, CustomClass c, CustomStruct2 s) } public static void LongAddTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p += 5L; - //l += 5L; + long num = 0L; + p += 5; + num += 5; + Use(ref num); longField += 5L; LongProp += 5L; c.LongField += 5L; @@ -2979,9 +3156,10 @@ public static void LongAddTest(long p, CustomClass c, CustomStruct2 s) public static void LongSubtractTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p -= 5L; - //l -= 5L; + long num = 0L; + p -= 5; + num -= 5; + Use(ref num); longField -= 5L; LongProp -= 5L; c.LongField -= 5L; @@ -3005,9 +3183,10 @@ public static void LongSubtractTest(long p, CustomClass c, CustomStruct2 s) public static void LongMultiplyTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p *= 5L; - //l *= 5L; + long num = 0L; + p *= 5; + num *= 5; + Use(ref num); longField *= 5L; LongProp *= 5L; c.LongField *= 5L; @@ -3031,9 +3210,10 @@ public static void LongMultiplyTest(long p, CustomClass c, CustomStruct2 s) public static void LongDivideTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p /= 5L; - //l /= 5L; + long num = 0L; + p /= 5; + num /= 5; + Use(ref num); longField /= 5L; LongProp /= 5L; c.LongField /= 5L; @@ -3057,9 +3237,10 @@ public static void LongDivideTest(long p, CustomClass c, CustomStruct2 s) public static void LongModulusTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p %= 5L; - //l %= 5L; + long num = 0L; + p %= 5; + num %= 5; + Use(ref num); longField %= 5L; LongProp %= 5L; c.LongField %= 5L; @@ -3083,9 +3264,10 @@ public static void LongModulusTest(long p, CustomClass c, CustomStruct2 s) public static void LongLeftShiftTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p <<= 5; - //l <<= 5; + long num = 0L; + p <<= 5; + num <<= 5; + Use(ref num); longField <<= 5; LongProp <<= 5; c.LongField <<= 5; @@ -3109,9 +3291,10 @@ public static void LongLeftShiftTest(long p, CustomClass c, CustomStruct2 s) public static void LongRightShiftTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p >>= 5; - //l >>= 5; + long num = 0L; + p >>= 5; + num >>= 5; + Use(ref num); longField >>= 5; LongProp >>= 5; c.LongField >>= 5; @@ -3135,9 +3318,10 @@ public static void LongRightShiftTest(long p, CustomClass c, CustomStruct2 s) public static void LongBitAndTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p &= 5L; - //l &= 5L; + long num = 0L; + p &= 5; + num &= 5; + Use(ref num); longField &= 5L; LongProp &= 5L; c.LongField &= 5L; @@ -3161,9 +3345,10 @@ public static void LongBitAndTest(long p, CustomClass c, CustomStruct2 s) public static void LongBitOrTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p |= 5L; - //l |= 5L; + long num = 0L; + p |= 5; + num |= 5; + Use(ref num); longField |= 5L; LongProp |= 5L; c.LongField |= 5L; @@ -3187,9 +3372,10 @@ public static void LongBitOrTest(long p, CustomClass c, CustomStruct2 s) public static void LongBitXorTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //p ^= 5L; - //l ^= 5L; + long num = 0L; + p ^= 5; + num ^= 5; + Use(ref num); longField ^= 5L; LongProp ^= 5L; c.LongField ^= 5L; @@ -3213,9 +3399,10 @@ public static void LongBitXorTest(long p, CustomClass c, CustomStruct2 s) public static void LongPostIncTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //X(p++); - //X(l++); + long num = 0L; + X(p++); + X(num++); + Use(ref num); X(longField++); X(LongProp++); X(c.LongField++); @@ -3239,9 +3426,10 @@ public static void LongPostIncTest(long p, CustomClass c, CustomStruct2 s) public static void LongPreIncTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //X(++p); - //X(++l); + long num = 0L; + X(++p); + X(++num); + Use(ref num); X(++longField); X(++LongProp); X(++c.LongField); @@ -3264,9 +3452,10 @@ public static void LongPreIncTest(long p, CustomClass c, CustomStruct2 s) } public static void LongPostDecTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //X(p--); - //X(l--); + long num = 0L; + X(p--); + X(num--); + Use(ref num); X(longField--); X(LongProp--); X(c.LongField--); @@ -3290,9 +3479,10 @@ public static void LongPostDecTest(long p, CustomClass c, CustomStruct2 s) public static void LongPreDecTest(long p, CustomClass c, CustomStruct2 s) { - //long l = 0; - //X(--p); - //X(--l); + long num = 0L; + X(--p); + X(--num); + Use(ref num); X(--longField); X(--LongProp); X(--c.LongField); @@ -3315,9 +3505,10 @@ public static void LongPreDecTest(long p, CustomClass c, CustomStruct2 s) } public static void UlongAddTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p += 5uL; - //l += 5uL; + ulong num = 0uL; + p += 5; + num += 5; + Use(ref num); ulongField += 5uL; UlongProp += 5uL; c.UlongField += 5uL; @@ -3341,9 +3532,10 @@ public static void UlongAddTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongSubtractTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p -= 5uL; - //l -= 5uL; + ulong num = 0uL; + p -= 5; + num -= 5; + Use(ref num); ulongField -= 5uL; UlongProp -= 5uL; c.UlongField -= 5uL; @@ -3367,9 +3559,10 @@ public static void UlongSubtractTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongMultiplyTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p *= 5uL; - //l *= 5uL; + ulong num = 0uL; + p *= 5; + num *= 5; + Use(ref num); ulongField *= 5uL; UlongProp *= 5uL; c.UlongField *= 5uL; @@ -3393,9 +3586,10 @@ public static void UlongMultiplyTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongDivideTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p /= 5uL; - //l /= 5uL; + ulong num = 0uL; + p /= 5; + num /= 5; + Use(ref num); ulongField /= 5uL; UlongProp /= 5uL; c.UlongField /= 5uL; @@ -3419,9 +3613,10 @@ public static void UlongDivideTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongModulusTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p %= 5uL; - //l %= 5uL; + ulong num = 0uL; + p %= 5; + num %= 5; + Use(ref num); ulongField %= 5uL; UlongProp %= 5uL; c.UlongField %= 5uL; @@ -3445,9 +3640,10 @@ public static void UlongModulusTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongLeftShiftTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p <<= 5; - //l <<= 5; + ulong num = 0uL; + p <<= 5; + num <<= 5; + Use(ref num); ulongField <<= 5; UlongProp <<= 5; c.UlongField <<= 5; @@ -3471,9 +3667,10 @@ public static void UlongLeftShiftTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongRightShiftTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p >>= 5; - //l >>= 5; + ulong num = 0uL; + p >>= 5; + num >>= 5; + Use(ref num); ulongField >>= 5; UlongProp >>= 5; c.UlongField >>= 5; @@ -3497,9 +3694,10 @@ public static void UlongRightShiftTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongBitAndTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p &= 5uL; - //l &= 5uL; + ulong num = 0uL; + p &= 5; + num &= 5; + Use(ref num); ulongField &= 5uL; UlongProp &= 5uL; c.UlongField &= 5uL; @@ -3523,9 +3721,10 @@ public static void UlongBitAndTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongBitOrTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p |= 5uL; - //l |= 5uL; + ulong num = 0uL; + p |= 5; + num |= 5; + Use(ref num); ulongField |= 5uL; UlongProp |= 5uL; c.UlongField |= 5uL; @@ -3549,9 +3748,10 @@ public static void UlongBitOrTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongBitXorTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //p ^= 5uL; - //l ^= 5uL; + ulong num = 0uL; + p ^= 5; + num ^= 5; + Use(ref num); ulongField ^= 5uL; UlongProp ^= 5uL; c.UlongField ^= 5uL; @@ -3575,9 +3775,10 @@ public static void UlongBitXorTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongPostIncTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //X(p++); - //X(l++); + ulong num = 0uL; + X(p++); + X(num++); + Use(ref num); X(ulongField++); X(UlongProp++); X(c.UlongField++); @@ -3601,9 +3802,10 @@ public static void UlongPostIncTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongPreIncTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //X(++p); - //X(++l); + ulong num = 0uL; + X(++p); + X(++num); + Use(ref num); X(++ulongField); X(++UlongProp); X(++c.UlongField); @@ -3626,9 +3828,10 @@ public static void UlongPreIncTest(ulong p, CustomClass c, CustomStruct2 s) } public static void UlongPostDecTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //X(p--); - //X(l--); + ulong num = 0uL; + X(p--); + X(num--); + Use(ref num); X(ulongField--); X(UlongProp--); X(c.UlongField--); @@ -3652,9 +3855,10 @@ public static void UlongPostDecTest(ulong p, CustomClass c, CustomStruct2 s) public static void UlongPreDecTest(ulong p, CustomClass c, CustomStruct2 s) { - //ulong l = 0; - //X(--p); - //X(--l); + ulong num = 0uL; + X(--p); + X(--num); + Use(ref num); X(--ulongField); X(--UlongProp); X(--c.UlongField); @@ -3677,9 +3881,10 @@ public static void UlongPreDecTest(ulong p, CustomClass c, CustomStruct2 s) } public static void CustomClassAddTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p += (CustomClass)null; - //l += (CustomClass)null; + CustomClass num = null; + p += (CustomClass)null; + num += (CustomClass)null; + Use(ref num); customClassField += (CustomClass)null; CustomClassProp += (CustomClass)null; c.CustomClassField += (CustomClass)null; @@ -3703,9 +3908,10 @@ public static void CustomClassAddTest(CustomClass p, CustomClass c, CustomStruct public static void CustomClassSubtractTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p -= (CustomClass)null; - //l -= (CustomClass)null; + CustomClass num = null; + p -= (CustomClass)null; + num -= (CustomClass)null; + Use(ref num); customClassField -= (CustomClass)null; CustomClassProp -= (CustomClass)null; c.CustomClassField -= (CustomClass)null; @@ -3729,9 +3935,10 @@ public static void CustomClassSubtractTest(CustomClass p, CustomClass c, CustomS public static void CustomClassMultiplyTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p *= (CustomClass)null; - //l *= (CustomClass)null; + CustomClass num = null; + p *= (CustomClass)null; + num *= (CustomClass)null; + Use(ref num); customClassField *= (CustomClass)null; CustomClassProp *= (CustomClass)null; c.CustomClassField *= (CustomClass)null; @@ -3755,9 +3962,10 @@ public static void CustomClassMultiplyTest(CustomClass p, CustomClass c, CustomS public static void CustomClassDivideTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p /= (CustomClass)null; - //l /= (CustomClass)null; + CustomClass num = null; + p /= (CustomClass)null; + num /= (CustomClass)null; + Use(ref num); customClassField /= (CustomClass)null; CustomClassProp /= (CustomClass)null; c.CustomClassField /= (CustomClass)null; @@ -3781,9 +3989,10 @@ public static void CustomClassDivideTest(CustomClass p, CustomClass c, CustomStr public static void CustomClassModulusTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p %= (CustomClass)null; - //l %= (CustomClass)null; + CustomClass num = null; + p %= (CustomClass)null; + num %= (CustomClass)null; + Use(ref num); customClassField %= (CustomClass)null; CustomClassProp %= (CustomClass)null; c.CustomClassField %= (CustomClass)null; @@ -3807,9 +4016,10 @@ public static void CustomClassModulusTest(CustomClass p, CustomClass c, CustomSt public static void CustomClassLeftShiftTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p <<= 5; - //l <<= 5; + CustomClass num = null; + p <<= 5; + num <<= 5; + Use(ref num); customClassField <<= 5; CustomClassProp <<= 5; c.CustomClassField <<= 5; @@ -3833,9 +4043,10 @@ public static void CustomClassLeftShiftTest(CustomClass p, CustomClass c, Custom public static void CustomClassRightShiftTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p >>= 5; - //l >>= 5; + CustomClass num = null; + p >>= 5; + num >>= 5; + Use(ref num); customClassField >>= 5; CustomClassProp >>= 5; c.CustomClassField >>= 5; @@ -3859,9 +4070,10 @@ public static void CustomClassRightShiftTest(CustomClass p, CustomClass c, Custo public static void CustomClassBitAndTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p &= (CustomClass)null; - //l &= (CustomClass)null; + CustomClass num = null; + p &= (CustomClass)null; + num &= (CustomClass)null; + Use(ref num); customClassField &= (CustomClass)null; CustomClassProp &= (CustomClass)null; c.CustomClassField &= (CustomClass)null; @@ -3885,9 +4097,10 @@ public static void CustomClassBitAndTest(CustomClass p, CustomClass c, CustomStr public static void CustomClassBitOrTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p |= (CustomClass)null; - //l |= (CustomClass)null; + CustomClass num = null; + p |= (CustomClass)null; + num |= (CustomClass)null; + Use(ref num); customClassField |= (CustomClass)null; CustomClassProp |= (CustomClass)null; c.CustomClassField |= (CustomClass)null; @@ -3911,9 +4124,10 @@ public static void CustomClassBitOrTest(CustomClass p, CustomClass c, CustomStru public static void CustomClassBitXorTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //p ^= (CustomClass)null; - //l ^= (CustomClass)null; + CustomClass num = null; + p ^= (CustomClass)null; + num ^= (CustomClass)null; + Use(ref num); customClassField ^= (CustomClass)null; CustomClassProp ^= (CustomClass)null; c.CustomClassField ^= (CustomClass)null; @@ -3937,9 +4151,10 @@ public static void CustomClassBitXorTest(CustomClass p, CustomClass c, CustomStr public static void CustomClassPostIncTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //X(p++); - //X(l++); + CustomClass num = null; + X(p++); + X(num++); + Use(ref num); X(customClassField++); X(CustomClassProp++); X(c.CustomClassField++); @@ -3963,9 +4178,10 @@ public static void CustomClassPostIncTest(CustomClass p, CustomClass c, CustomSt public static void CustomClassPreIncTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //X(++p); - //X(++l); + CustomClass num = null; + X(++p); + X(++num); + Use(ref num); X(++customClassField); X(++CustomClassProp); X(++c.CustomClassField); @@ -3988,9 +4204,10 @@ public static void CustomClassPreIncTest(CustomClass p, CustomClass c, CustomStr } public static void CustomClassPostDecTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //X(p--); - //X(l--); + CustomClass num = null; + X(p--); + X(num--); + Use(ref num); X(customClassField--); X(CustomClassProp--); X(c.CustomClassField--); @@ -4014,9 +4231,10 @@ public static void CustomClassPostDecTest(CustomClass p, CustomClass c, CustomSt public static void CustomClassPreDecTest(CustomClass p, CustomClass c, CustomStruct2 s) { - //CustomClass l = null; - //X(--p); - //X(--l); + CustomClass num = null; + X(--p); + X(--num); + Use(ref num); X(--customClassField); X(--CustomClassProp); X(--c.CustomClassField); @@ -4039,9 +4257,10 @@ public static void CustomClassPreDecTest(CustomClass p, CustomClass c, CustomStr } public static void CustomStructAddTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p += default(CustomStruct); - //l += default(CustomStruct); + CustomStruct num = default(CustomStruct); + p += default(CustomStruct); + num += default(CustomStruct); + Use(ref num); customStructField += default(CustomStruct); CustomStructProp += default(CustomStruct); c.CustomStructField += default(CustomStruct); @@ -4065,9 +4284,10 @@ public static void CustomStructAddTest(CustomStruct p, CustomClass c, CustomStru public static void CustomStructSubtractTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p -= default(CustomStruct); - //l -= default(CustomStruct); + CustomStruct num = default(CustomStruct); + p -= default(CustomStruct); + num -= default(CustomStruct); + Use(ref num); customStructField -= default(CustomStruct); CustomStructProp -= default(CustomStruct); c.CustomStructField -= default(CustomStruct); @@ -4091,9 +4311,10 @@ public static void CustomStructSubtractTest(CustomStruct p, CustomClass c, Custo public static void CustomStructMultiplyTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p *= default(CustomStruct); - //l *= default(CustomStruct); + CustomStruct num = default(CustomStruct); + p *= default(CustomStruct); + num *= default(CustomStruct); + Use(ref num); customStructField *= default(CustomStruct); CustomStructProp *= default(CustomStruct); c.CustomStructField *= default(CustomStruct); @@ -4117,9 +4338,10 @@ public static void CustomStructMultiplyTest(CustomStruct p, CustomClass c, Custo public static void CustomStructDivideTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p /= default(CustomStruct); - //l /= default(CustomStruct); + CustomStruct num = default(CustomStruct); + p /= default(CustomStruct); + num /= default(CustomStruct); + Use(ref num); customStructField /= default(CustomStruct); CustomStructProp /= default(CustomStruct); c.CustomStructField /= default(CustomStruct); @@ -4143,9 +4365,10 @@ public static void CustomStructDivideTest(CustomStruct p, CustomClass c, CustomS public static void CustomStructModulusTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p %= default(CustomStruct); - //l %= default(CustomStruct); + CustomStruct num = default(CustomStruct); + p %= default(CustomStruct); + num %= default(CustomStruct); + Use(ref num); customStructField %= default(CustomStruct); CustomStructProp %= default(CustomStruct); c.CustomStructField %= default(CustomStruct); @@ -4169,9 +4392,10 @@ public static void CustomStructModulusTest(CustomStruct p, CustomClass c, Custom public static void CustomStructLeftShiftTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p <<= 5; - //l <<= 5; + CustomStruct num = default(CustomStruct); + p <<= 5; + num <<= 5; + Use(ref num); customStructField <<= 5; CustomStructProp <<= 5; c.CustomStructField <<= 5; @@ -4195,9 +4419,10 @@ public static void CustomStructLeftShiftTest(CustomStruct p, CustomClass c, Cust public static void CustomStructRightShiftTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p >>= 5; - //l >>= 5; + CustomStruct num = default(CustomStruct); + p >>= 5; + num >>= 5; + Use(ref num); customStructField >>= 5; CustomStructProp >>= 5; c.CustomStructField >>= 5; @@ -4219,11 +4444,38 @@ public static void CustomStructRightShiftTest(CustomStruct p, CustomClass c, Cus #endif } - public static void CustomStructBitAndTest(CustomStruct p, CustomClass c, CustomStruct2 s) +#if CS110 + public static void CustomStructUnsignedRightShiftTest(CustomStruct p, CustomClass c, CustomStruct2 s) { //CustomStruct l = default(CustomStruct); - //p &= default(CustomStruct); - //l &= default(CustomStruct); + //p >>>= 5; + //l >>>= 5; + customStructField >>>= 5; + CustomStructProp >>>= 5; + c.CustomStructField >>>= 5; + c.CustomStructProp >>>= 5; + s.CustomStructField >>>= 5; + s.CustomStructProp >>>= 5; + customClassField.CustomStructField >>>= 5; + customClassField.CustomStructProp >>>= 5; + otherCustomStructField.CustomStructField >>>= 5; + otherCustomStructField.CustomStructProp >>>= 5; + CustomClassProp.CustomStructField >>>= 5; + CustomClassProp.CustomStructProp >>>= 5; + GetClass().CustomStructField >>>= 5; + GetClass().CustomStructProp >>>= 5; + GetRefStruct().CustomStructField >>>= 5; + GetRefStruct().CustomStructProp >>>= 5; + GetRefCustomStruct() >>>= 5; + } +#endif + + public static void CustomStructBitAndTest(CustomStruct p, CustomClass c, CustomStruct2 s) + { + CustomStruct num = default(CustomStruct); + p &= default(CustomStruct); + num &= default(CustomStruct); + Use(ref num); customStructField &= default(CustomStruct); CustomStructProp &= default(CustomStruct); c.CustomStructField &= default(CustomStruct); @@ -4247,9 +4499,10 @@ public static void CustomStructBitAndTest(CustomStruct p, CustomClass c, CustomS public static void CustomStructBitOrTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p |= default(CustomStruct); - //l |= default(CustomStruct); + CustomStruct num = default(CustomStruct); + p |= default(CustomStruct); + num |= default(CustomStruct); + Use(ref num); customStructField |= default(CustomStruct); CustomStructProp |= default(CustomStruct); c.CustomStructField |= default(CustomStruct); @@ -4273,9 +4526,10 @@ public static void CustomStructBitOrTest(CustomStruct p, CustomClass c, CustomSt public static void CustomStructBitXorTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //p ^= default(CustomStruct); - //l ^= default(CustomStruct); + CustomStruct num = default(CustomStruct); + p ^= default(CustomStruct); + num ^= default(CustomStruct); + Use(ref num); customStructField ^= default(CustomStruct); CustomStructProp ^= default(CustomStruct); c.CustomStructField ^= default(CustomStruct); @@ -4299,9 +4553,10 @@ public static void CustomStructBitXorTest(CustomStruct p, CustomClass c, CustomS public static void CustomStructPostIncTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //X(p++); - //X(l++); + CustomStruct num = default(CustomStruct); + X(p++); + X(num++); + Use(ref num); X(customStructField++); X(CustomStructProp++); X(c.CustomStructField++); @@ -4325,9 +4580,10 @@ public static void CustomStructPostIncTest(CustomStruct p, CustomClass c, Custom public static void CustomStructPreIncTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //X(++p); - //X(++l); + CustomStruct num = default(CustomStruct); + X(++p); + X(++num); + Use(ref num); X(++customStructField); X(++CustomStructProp); X(++c.CustomStructField); @@ -4350,9 +4606,10 @@ public static void CustomStructPreIncTest(CustomStruct p, CustomClass c, CustomS } public static void CustomStructPostDecTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //X(p--); - //X(l--); + CustomStruct num = default(CustomStruct); + X(p--); + X(num--); + Use(ref num); X(customStructField--); X(CustomStructProp--); X(c.CustomStructField--); @@ -4376,9 +4633,10 @@ public static void CustomStructPostDecTest(CustomStruct p, CustomClass c, Custom public static void CustomStructPreDecTest(CustomStruct p, CustomClass c, CustomStruct2 s) { - //CustomStruct l = default(CustomStruct); - //X(--p); - //X(--l); + CustomStruct num = default(CustomStruct); + X(--p); + X(--num); + Use(ref num); X(--customStructField); X(--CustomStructProp); X(--c.CustomStructField); @@ -4581,12 +4839,25 @@ public uint PostIncrementIndexer(string name) return M()[name]++; } -#if false public unsafe int PostIncrementOfPointer(int* ptr) { return *(ptr++); } -#endif + + public unsafe int PostIncrementOfSmallIntegerPointerDereference(byte* ptr) + { + return (*ptr)++ * (*ptr)++; + } + + public unsafe int PreIncrementOfSmallIntegerPointerDereference(byte* ptr) + { + return ++(*ptr) * ++(*ptr); + } + + public unsafe int CompoundAssignSmallIntegerPointerDereference(byte* ptr) + { + return (*ptr += 5) * (*ptr += 5); + } public int PostDecrementInstanceField() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstantsTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstantsTests.cs index 8032484ce7..5df746cc0c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstantsTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstantsTests.cs @@ -1,10 +1,25 @@ -using System; +#if !(CS110 && NET70) +using System; +#endif using System.Threading.Tasks; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { internal class ConstantsTests { +#if CS90 + public nint? NullableNInt() + { + return null; + } + + public nuint? NullableNUInt() + { + return null; + } +#endif + +#if !(CS110 && NET70) public IntPtr? NullableIntPtr() { return null; @@ -14,6 +29,7 @@ internal class ConstantsTests { return null; } +#endif public ulong Issue1308(ulong u = 8uL) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes.cs index df63a36354..b99c9fb14d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomAttributes.cs @@ -163,8 +163,13 @@ public static void BoxedLiteralsArray() public static void UseGenericAttribute() { } - // TODO: add test for generic attributes with arguments of type T - // This is blocked by https://github.com/dotnet/runtime/issues/58073 + [Generic(42)] + [Generic("Hi")] + [Generic("Hi")] + [Generic((short)42)] + public static void UseGenericAttributeWithArg() + { + } #endif } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index 4046bebd16..6fe5791806 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -601,6 +601,50 @@ public Func TestLambda(Issue1867 x) } } + internal class Issue2791 + { + public void M() + { + Run(delegate (object o) { + try + { + List list = o as List; + Action action = delegate { + list.Select((int x) => x * 2); + }; +#if OPT && ROSLYN + Action obj = delegate { +#else + Action action2 = delegate { +#endif + list.Select((int x) => x * 2); + }; + Console.WriteLine(); + action(); + Console.WriteLine(); +#if OPT && ROSLYN + obj(); +#else + action2(); +#endif + } + catch (Exception) + { + Console.WriteLine("catch"); + } + finally + { + Console.WriteLine("finally"); + } + }, null); + } + + private void Run(ParameterizedThreadStart del, object x) + { + del(x); + } + } + [AttributeUsage(AttributeTargets.All)] internal class MyAttribute : Attribute { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs index 897bcabd4e..01edd50d21 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs @@ -42,7 +42,12 @@ public void Method(dynamic a) } } + public interface I + { + } + private static dynamic field; + private static volatile dynamic volatileField; private static object objectField; public dynamic Property { get; set; } @@ -431,10 +436,17 @@ private static bool ConstantTarget(dynamic a) return true.Equals(a); } +#if CS110 && NET70 + private static nint NewIntPtr(dynamic a) + { + return new nint(a); + } +#else private static IntPtr NewIntPtr(dynamic a) { return new IntPtr(a); } +#endif private static dynamic GetDynamic(int i) { @@ -492,6 +504,26 @@ private static int ExplicitCast(object o) return (int)(dynamic)o; } + private static dynamic GetI() + { + return null; + } + + public I Test() + { + return GetI(); + } + + public I Test1() + { + return (I)GetI(); + } + + public I Test2() + { + return (I)(object)GetI(); + } + #if CS72 public void RefParams(ref object a, ref dynamic b, ref dynamic c) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs index faa752bcfe..9423c42542 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs @@ -1,4 +1,6 @@ -using System; +#if !(CS110 && NET70) +using System; +#endif using System.Text; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -17,10 +19,17 @@ public static void Overloaded(int a) return &Overloaded; } +#if !(CS110 && NET70) public unsafe IntPtr GetAddressAsIntPtr() { return (IntPtr)(delegate*)(&Overloaded); } +#endif + + public unsafe nint GetAddressAsNInt() + { + return (nint)(delegate*)(&Overloaded); + } public unsafe void* GetAddressAsVoidPtr() { @@ -93,6 +102,7 @@ public class B internal class FunctionPointersWithNativeIntegerTypes { public unsafe delegate* F1; + #if !(CS110 && NET70) public unsafe delegate* F2; public unsafe delegate* F3; public unsafe delegate* F4; @@ -100,6 +110,8 @@ internal class FunctionPointersWithNativeIntegerTypes public unsafe delegate*> F6; public unsafe delegate*, IntPtr> F7; public unsafe delegate*> F8; + public unsafe delegate*> F9; + #endif } internal class FunctionPointersWithRefParams diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index 8feb4fdf56..3844d22cbe 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -382,6 +382,9 @@ public interface IData public static ReadOnlySpan StaticData3 => new byte[3] { 1, 2, 3 }; public static Span StaticData3Span => new byte[3] { 1, 2, 3 }; +#endif +#if CS110 && !NET40 + public static ReadOnlySpan UTF8Literal => "Hello, world!"u8; #endif #endregion diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index c4d784fd46..677f6ebafe 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -844,10 +844,10 @@ int ZZZ_1() #if CS90 public void Issue2196() { - EnumWindows(IntPtr.Zero, IntPtr.Zero); + EnumWindows(0L, 0L); [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "EnumWindows")] - static extern int EnumWindows(IntPtr hWnd, IntPtr lParam); + static extern int EnumWindows(long hWnd, long lParam); } #endif } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs index 69d3a3e0ce..2cb9f7a26d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs @@ -26,23 +26,36 @@ internal class NativeInts private const nint nint_const = 42; private const nuint nuint_const = 99u; +#if CS110 && NET70 + // C#11 on .NET7 no longer uses NativeIntegerAttribute, + // instead nint+IntPtr are considered to be the same type. + private nint intptr; + private nuint uintptr; +#else private IntPtr intptr; private UIntPtr uintptr; +#endif private nint i; private nuint u; private int i32; private uint u32; private long i64; private ulong u64; +#if !(CS110 && NET70) private (IntPtr, nint, UIntPtr, nuint) tuple_field; private (object, int, IntPtr, nint, UIntPtr, nuint) tuple_field2; private Dictionary dict1; private Dictionary dict2; private Dictionary dict3; private Dictionary dict4; +#endif + private Dictionary dict5; public void Convert() { + i = (nint)u; + u = (nuint)i; +#if !(CS110 && NET70) intptr = i; intptr = (nint)u; intptr = (nint)(nuint)uintptr; @@ -58,15 +71,18 @@ public void Convert() u = (nuint)i; u = uintptr; u = (nuint)(nint)intptr; +#endif } public void Convert2() { i32 = (int)i; i = i32; +#if !(CS110 && NET70) intptr = (IntPtr)i32; i64 = (long)intptr; +#endif i64 = i; i = (nint)i64; @@ -79,7 +95,9 @@ public void Convert2() public void Arithmetic() { +#if !(CS110 && NET70) Console.WriteLine((nint)intptr * 2); +#endif Console.WriteLine(i * 2); Console.WriteLine(i + (nint)u); @@ -155,6 +173,7 @@ public void CompoundAssign() { GetInstance(3).u *= 2u; } +#if !(CS110 && NET70) GetInstance(4).intptr += (nint)i32; checked { @@ -164,10 +183,13 @@ public void CompoundAssign() } // multiplication results in compiler-error without the cast GetInstance(6).intptr *= (nint)2; +#endif - GetInstance(7).i <<= i32; + GetInstance(7).i += i32; + GetInstance(8).i <<= i32; } +#if !(CS110 && NET70) public void LocalTypeFromStore() { nint num = 42; @@ -188,10 +210,19 @@ public void LocalTypeFromStore() intptr = num3; intptr = intPtr; } - +#endif public void LocalTypeFromUse() { +#if CS110 && NET70 + nint num = intptr; + nint num2 = intptr; + + Console.WriteLine(); + + intptr = num; + i = num2 + 1; +#else IntPtr intPtr = intptr; nint num = intptr; @@ -199,6 +230,7 @@ public void LocalTypeFromUse() intptr = intPtr; i = num + 1; +#endif } public nint NegateUnsigned(nuint x) @@ -206,6 +238,11 @@ public nint NegateUnsigned(nuint x) return (nint)(0 - x); } + public bool CompareToMinus3(nuint x) + { + return x == unchecked((nuint)(-3)); + } + public nint SignedNotFittingIn32Bits() { // Explicit `unchecked` is necessary when casting oversized constant to nint diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs index 49d191b08e..5450173574 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs @@ -111,4 +111,32 @@ private struct Entry private Entry[]? _entries; private IEqualityComparer? _comparer; } + + public class T05_NullableUnconstrainedGeneric + { + public static TValue? Default() + { + return default(TValue); + } + + public static void CallDefault() + { +#if OPT + string? format = Default(); +#else + // With optimizations it's a stack slot, so ILSpy picks a nullable type. + // Without optimizations it's a local, so the nullability is missing. + string format = Default(); +#endif + int num = Default(); +#if CS110 && NET70 + nint num2 = Default(); +#else + int num2 = Default(); +#endif + (object, string) tuple = Default<(object, string)>(); + Console.WriteLine("No inlining"); + Console.WriteLine(format, num, num2, tuple); + } + } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs new file mode 100644 index 0000000000..0e73a79521 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs @@ -0,0 +1,225 @@ +// Copyright (c) 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; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public class AllOperators + { + public static AllOperators operator +(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator -(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator *(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator /(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator %(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator &(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator |(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator ^(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator <<(AllOperators a, int b) + { + return null; + } + + public static AllOperators operator >>(AllOperators a, int b) + { + return null; + } + +#if CS110 + public static AllOperators operator >>>(AllOperators a, int b) + { + return null; + } +#endif + + public static AllOperators operator ~(AllOperators a) + { + return null; + } + + public static AllOperators operator !(AllOperators a) + { + return null; + } + + public static AllOperators operator -(AllOperators a) + { + return null; + } + + public static AllOperators operator +(AllOperators a) + { + return null; + } + + public static AllOperators operator ++(AllOperators a) + { + return null; + } + + public static AllOperators operator --(AllOperators a) + { + return null; + } + + public static bool operator true(AllOperators a) + { + return false; + } + + public static bool operator false(AllOperators a) + { + return false; + } + + public static bool operator ==(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator !=(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator <(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator >(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator <=(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator >=(AllOperators a, AllOperators b) + { + return false; + } + + public static implicit operator AllOperators(int a) + { + return null; + } + + public static explicit operator int(AllOperators a) + { + return 0; + } + } + + public class UseAllOperators + { + private AllOperators a = new AllOperators(); + private AllOperators b = new AllOperators(); + private AllOperators c; + public void Test() + { + c = a + b; + c = a - b; + c = a * b; + c = a / b; + c = a % b; + c = a & b; + c = a | b; + c = a ^ b; + c = a << 5; + c = a >> 5; +#if CS110 + c = a >>> 5; +#endif + c = ~a; + c = !a; + c = -a; + c = +a; + c = ++a; + c = --a; + if (a) + { + Console.WriteLine("a"); + } + if (!a) + { + Console.WriteLine("!a"); + } + if (a == b) + { + Console.WriteLine("a == b"); + } + if (a != b) + { + Console.WriteLine("a != b"); + } + if (a < b) + { + Console.WriteLine("a < b"); + } + if (a > b) + { + Console.WriteLine("a > b"); + } + if (a <= b) + { + Console.WriteLine("a <= b"); + } + if (a >= b) + { + Console.WriteLine("a >= b"); + } + int num = (int)a; + a = num; + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs index d770c1d1b2..f68b896d09 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { @@ -225,5 +226,62 @@ public static void Definition_NullableNInt(nint? p = 100) } #endif + + public static void Issue2920a(int x) + { + } + public static void Issue2920b([DefaultParameterValue(3)] int x) + { + } + public static void Issue2920c(ref int x) + { + } + public static void Issue2920d([DefaultParameterValue(3)] ref int x) + { + } + public static void Issue2920e(out int x) + { + x = 0; + } + public static void Issue2920f([DefaultParameterValue(3)] out int x) + { + x = 0; + } +#if CS70 + public static void Issue2920g(in int x) + { + } + public static void Issue2920h([DefaultParameterValue(3)] in int x) + { + } +#endif + public static void Issue2920i([Optional] int x) + { + } + public static void Issue2920j(int x = 3) + { + } + public static void Issue2920k([Optional] ref int x) + { + } + public static void Issue2920l([Optional][DefaultParameterValue(3)] ref int x) + { + } + public static void Issue2920m([Optional] out int x) + { + x = 0; + } + public static void Issue2920n([Optional][DefaultParameterValue(3)] out int x) + { + x = 0; + } +#if CS70 + public static void Issue2920o([Optional] in int x) + { + } + public static void Issue2920p(in int x = 3) + { + } +#endif } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs index bdfc8cbfad..96d1223228 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -43,5 +43,34 @@ public static Action CapturedOutVarInShortCircuit(Dictionary d) } return null; } + + private bool TryGet(out T result) + { + result = default(T); + return true; + } + + public void M3() + { + TryGet>(out Dictionary data); + + Test(); + + int Test() + { + return data[0].A; + } + } + + public void GetObject(out object obj) + { + obj = null; + } + + public void M4() + { + GetObject(out dynamic obj); + obj.Method(); + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs index b8483ecc10..877c8e3938 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PInvoke.cs @@ -89,9 +89,14 @@ public void CustomMarshal2([MarshalAs(UnmanagedType.CustomMarshaler, MarshalType { } +#if CS110 && NET70 [DllImport("ws2_32.dll", SetLastError = true)] - internal static extern IntPtr ioctlsocket([In] IntPtr socketHandle, [In] int cmd, [In][Out] ref int argp); + internal static extern nint ioctlsocket([In] nint socketHandle, [In] int cmd, [In][Out] ref int argp); +#else + [DllImport("ws2_32.dll", SetLastError = true)] + internal static extern IntPtr ioctlsocket([In] IntPtr socketHandle, [In] int cmd, [In][Out] ref int argp); +#endif public void CallMethodWithInOutParameter() { int argp = 0; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs index 30c0402da3..49a4fba3e0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs @@ -1,5 +1,7 @@ using System; +#if CS100 using System.Runtime.InteropServices; +#endif namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { @@ -127,6 +129,8 @@ public record DerivedGeneric : Pair where T : struct } } + +#if CS100 internal class RecordStructs { public record struct Base(string A); @@ -227,10 +231,38 @@ public Fields Test2(Fields input) } } } +#endif +#if CS110 + public record struct WithRequiredMembers + { + public int A { get; set; } + public required double B { get; set; } + public object C; + public required dynamic D; + } +#endif } +#if !NET60 namespace System.Runtime.CompilerServices { + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + } + } + internal class IsExternalInit { } +#endif +#if !NET70 + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal sealed class RequiredMemberAttribute : Attribute + { + } +#endif +#if !NET60 } +#endif diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs index ece018663c..006cac8614 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { @@ -566,5 +567,19 @@ public void SwitchInTryInLoopContinue() } } } + + private static string ShouldNotDuplicateReturnStatementIntoTry(IDictionary dict) + { + string value; + lock (dict) + { + if (!dict.TryGetValue(1, out value)) + { + value = "test"; + dict.Add(1, value); + } + } + return value; + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs new file mode 100644 index 0000000000..aff38438b7 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs @@ -0,0 +1,87 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + internal class LifetimeTests + { + private static int staticField; + + public Span CreateWithoutCapture(scoped ref int value) + { + // Okay: value is not captured + return new Span(ref staticField); + } + + public Span CreateAndCapture(ref int value) + { + // Okay: value Rule 3 specifies that the safe-to-escape be limited to the ref-safe-to-escape + // of the ref argument. That is the *calling method* for value hence this is not allowed. + return new Span(ref value); + } + + public Span ScopedRefSpan(scoped ref Span span) + { + return span; + } + + public Span ScopedSpan(scoped Span span) + { + return default(Span); + } + + public void OutSpan(out Span span) + { + span = default(Span); + } + + public void Calls() + { + int value = 0; + Span span = CreateWithoutCapture(ref value); + //span = CreateAndCapture(ref value); -- would need scoped local, not yet implemented + span = ScopedRefSpan(ref span); + span = ScopedSpan(span); + OutSpan(out span); + } + } + + internal ref struct RefFields + { + public ref int Field0; + public ref readonly int Field1; + public readonly ref int Field2; + public readonly ref readonly int Field3; + + public int PropertyAccessingRefFieldByValue { + get { + return Field0; + } + set { + Field0 = value; + } + } + + public ref int PropertyReturningRefFieldByReference => ref Field0; + + public void Uses(int[] array) + { + Field1 = ref array[0]; + Field2 = array[0]; + } + + public void ReadonlyLocal() + { + ref readonly int field = ref Field1; + Console.WriteLine("No inlining"); + field.ToString(); + } + + public RefFields(ref int v) + { + Field0 = ref v; + Field1 = ref v; + Field2 = ref v; + Field3 = ref v; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs index 6b5081ca59..553ea0e53e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs @@ -4,11 +4,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.StaticAbstractInterfaceM { public interface I { - abstract static int Capacity { get; } - abstract static int Count { get; set; } - abstract static int SetterOnly { set; } - abstract static event EventHandler E; - abstract static I CreateI(); + static abstract int Capacity { get; } + static abstract int Count { get; set; } + static abstract int SetterOnly { set; } + static abstract event EventHandler E; + static abstract I CreateI(); } public class X : I @@ -66,4 +66,30 @@ public static I CreateI() throw new NotImplementedException(); } } + + internal class ZOperatorTest + { + + public interface IGetNext where T : IGetNext + { + static abstract T operator ++(T other); + } + + public struct WrappedInteger : IGetNext + { + public int Value; + + public static WrappedInteger operator ++(WrappedInteger other) + { + WrappedInteger result = other; + result.Value++; + return result; + } + } + + public void GenericUse(T t) where T : IGetNext + { + ++t; + } + } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs index fd5dedfda9..c796990eb6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs @@ -51,4 +51,28 @@ public StructWithDefaultCtor() } } #endif -} \ No newline at end of file + +#if CS110 + public struct StructWithRequiredMembers + { + public required string FirstName; + public required string LastName { get; set; } + } +#endif +} + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal sealed class RequiredMemberAttribute : Attribute + { + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs index 95f8e62697..7cd3368e63 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs @@ -106,9 +106,21 @@ public int RShiftByte(byte num) public uint RShiftByteWithZeroExtension(byte num) { + // zero extend -> cast to unsigned -> unsigned shift return (uint)num >> 8; } + public int RShiftByteWithZeroExtensionReturnAsSigned(byte num) + { +#if CS110 + // zero extend -> unsigned shift + return num >>> 8; +#else + // zero extend -> cast to unsigned -> unsigned shift -> cast to signed + return (int)((uint)num >> 8); +#endif + } + public int RShiftByteAsSByte(byte num) { return (sbyte)num >> 8; @@ -121,9 +133,25 @@ public int RShiftSByte(sbyte num) public uint RShiftSByteWithZeroExtension(sbyte num) { - return (uint)num >> 8; + return (uint)((byte)num >> 4); } + public uint RShiftSByteWithSignExtension(sbyte num) + { + // sign extend -> cast to unsigned -> unsigned shift + return (uint)num >> 4; + } + + public int RShiftSByteWithSignExtensionReturnAsSigned(sbyte num) + { +#if CS110 + // sign extend -> unsigned shift + return num >>> 4; +#else + // sign extend -> cast to unsigned -> unsigned shift -> cast to signed + return (int)((uint)num >> 4); +#endif + } public int RShiftSByteAsByte(sbyte num) { return (byte)num >> 8; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs index ce890fcd09..9901fd69e1 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs @@ -17,10 +17,58 @@ // DEALINGS IN THE SOFTWARE. using System; +#if !NET40 && ROSLYN +using System.Runtime.CompilerServices; +#endif using System.Runtime.InteropServices; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { + internal class SizeofTest + { + private struct StructWithStaticField + { + public static object StaticObj; + +#if CS110 && NET70 + public nint A; +#else + public IntPtr A; +#endif + } + + private struct UnmanagedStruct + { + public StructWithStaticField Value; + } + + private struct ManagedStruct + { + public object Obj; + } +#if CS73 + private unsafe int GenericMethod() where T : unmanaged + { + return sizeof(T); + } +#endif + + private unsafe void Test(out int s1, out int s2, out int s3) + { + s1 = 0; + s2 = 0; + s3 = 0; +#if CS73 + GenericMethod(); +#endif + s1 = sizeof(UnmanagedStruct); +#if !NET40 && ROSLYN + s2 = Unsafe.SizeOf(); + s3 = Unsafe.SizeOf(); +#endif + } + } + public class UnsafeCode { public struct SimpleStruct @@ -561,6 +609,18 @@ private unsafe static int Issue2305(StructWithFixedSizeMembers value, StringComp return value.Integers[(int)s]; } +#if CS90 + private unsafe static void* CastNIntToVoidPtr(nint intptr) + { + return (void*)intptr; + } + + private unsafe static void* CastNIntToVoidPtr(nuint intptr) + { + return (void*)intptr; + } +#endif +#if !(CS110 && NET70) private unsafe static void* CastToVoidPtr(IntPtr intptr) { return (void*)intptr; @@ -570,6 +630,7 @@ private unsafe static int Issue2305(StructWithFixedSizeMembers value, StringComp { return (void*)intptr; } +#endif private unsafe static void* CastToVoidPtr(int* intptr) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs index 431f00088a..fb9dcf4aa0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs @@ -1,4 +1,4 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -101,24 +101,23 @@ public static IEnumerable YieldReturnWithTryFinally() yield return 2; } -#if TODO - // TODO: adjust lock-pattern for this case public static IEnumerable YieldReturnInLock1(object o) { - lock (o) { + lock (o) + { yield return 1; } } public static IEnumerable YieldReturnInLock2(object o) { - lock (o) { + lock (o) + { yield return 1; o = null; yield return 2; } } -#endif public static IEnumerable YieldReturnWithNestedTryFinally(bool breakInMiddle) { @@ -450,4 +449,4 @@ internal IEnumerable ForLoopWithYieldReturn(int end, int evil) } } } -} \ No newline at end of file +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/.gitignore b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/.gitignore index 6a7461313b..701b61388b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/.gitignore +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/.gitignore @@ -1 +1,3 @@ -*.dll +/*.dll +/*.exe +/*.pdb \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs index 43b6ab9506..6cb502dd0c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs @@ -1,45 +1,255 @@ using System; -using System.IO; +using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading.Tasks; -namespace EquivalentCSharpConsoleApp +using Microsoft.VisualBasic.CompilerServices; + +namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty { - static class Program + public class Async { - public static void Main(string[] args) + private int memberField; + + private static bool True() + { + return true; + } + + public async void SimpleVoidMethod() + { + Console.WriteLine("Before"); + await Task.Delay(TimeSpan.FromSeconds(1.0)); + Console.WriteLine("After"); + } + + public async void VoidMethodWithoutAwait() + { + Console.WriteLine("No Await"); + } + + public async void EmptyVoidMethod() + { + } + + public async void AwaitYield() + { + await Task.Yield(); + } + + public async void AwaitDefaultYieldAwaitable() + { +#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) + YieldAwaitable yieldAwaitable = default(YieldAwaitable); + YieldAwaitable yieldAwaitable2 = yieldAwaitable; + await yieldAwaitable2; +#else + await default(YieldAwaitable); +#endif + } + + public async void AwaitDefaultHopToThreadPool() + { +#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) + HopToThreadPoolAwaitable hopToThreadPoolAwaitable = default(HopToThreadPoolAwaitable); + HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = hopToThreadPoolAwaitable; + await hopToThreadPoolAwaitable2; +#else + await default(HopToThreadPoolAwaitable); +#endif + } + + public async Task SimpleVoidTaskMethod() + { + Console.WriteLine("Before"); + await Task.Delay(TimeSpan.FromSeconds(1.0)); + Console.WriteLine("After"); + } + + public async Task TaskMethodWithoutAwait() + { + Console.WriteLine("No Await"); + } + + public async Task CapturingThis() + { + await Task.Delay(memberField); + } + + public async Task CapturingThisWithoutAwait() + { + Console.WriteLine(memberField); + } + + public async Task SimpleBoolTaskMethod() { - var task = new Task(ProcessDataAsync); - task.Start(); - task.Wait(); - Console.ReadLine(); + Console.WriteLine("Before"); + await Task.Delay(TimeSpan.FromSeconds(1.0)); + Console.WriteLine("After"); + return true; } - public async static void ProcessDataAsync() + public async void TwoAwaitsWithDifferentAwaiterTypes() { - Task task = HandleFileAsync("C:\\enable1.txt"); - Console.WriteLine("Please wait, processing"); - int result = await task; - Console.WriteLine("Count: " + result.ToString()); + Console.WriteLine("Before"); + if (await SimpleBoolTaskMethod()) + { + await Task.Delay(TimeSpan.FromSeconds(1.0)); + } + Console.WriteLine("After"); + } + + public async void AwaitInLoopCondition() + { + while (await SimpleBoolTaskMethod()) + { + Console.WriteLine("Body"); + } } - public async static Task HandleFileAsync(string file) + public async Task Issue2366a() { - Console.WriteLine("HandleFile enter"); - int count = 0; - using (StreamReader reader = new StreamReader(file)) + while (true) { - string value = await reader.ReadToEndAsync(); - count += value.Length; - for (var i = 0; i <= 10000; i += 1) + try { - var x = value.GetHashCode(); - if (x == 0) - count -= 1; + await Task.CompletedTask; } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + ProjectData.ClearProjectError(); + } + } + } + + public static async Task GetIntegerSumAsync(IEnumerable items) + { + await Task.Delay(100); + int num = 0; + foreach (int item in items) + { + num = checked(num + item); } + return num; + } - Console.WriteLine("HandleFile exit"); - return count; + public async Task AsyncCatch(bool b, Task task1, Task task2) + { + try + { + Console.WriteLine("Start try"); + await task1; + Console.WriteLine("End try"); + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + Console.WriteLine("No await"); + ProjectData.ClearProjectError(); + } + } + + public async Task AsyncCatchThrow(bool b, Task task1, Task task2) + { + try + { + Console.WriteLine("Start try"); + await task1; + Console.WriteLine("End try"); + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + Console.WriteLine("No await"); + throw; + } + } + + public async Task AsyncFinally(bool b, Task task1, Task task2) + { + try + { + Console.WriteLine("Start try"); + await task1; + Console.WriteLine("End try"); + } + finally + { + Console.WriteLine("No await"); + } + } + + public static async Task AlwaysThrow() + { + throw new Exception(); + } + + public static async Task InfiniteLoop() + { + while (true) + { + } + } + + public static async Task InfiniteLoopWithAwait() + { + while (true) + { + await Task.Delay(10); + } + } + + public async Task AsyncWithLocalVar() + { + object objectValue = RuntimeHelpers.GetObjectValue(new object()); + await UseObj(RuntimeHelpers.GetObjectValue(objectValue)); + await UseObj(RuntimeHelpers.GetObjectValue(objectValue)); + } + + public static async Task UseObj(object a) + { + } + } + + public struct AsyncInStruct + { + private int i; + + public async Task Test(AsyncInStruct xx) + { + checked + { + xx.i++; + i++; + await Task.Yield(); + return i + xx.i; + } + } + } + + public struct HopToThreadPoolAwaitable : INotifyCompletion + { + public bool IsCompleted { get; set; } + + public HopToThreadPoolAwaitable GetAwaiter() + { + return this; + } + + public void OnCompleted(Action continuation) + { + Task.Run(continuation); + } + + void INotifyCompletion.OnCompleted(Action continuation) + { + //ILSpy generated this explicit interface implementation from .override directive in OnCompleted + this.OnCompleted(continuation); + } + + public void GetResult() + { } } -} \ No newline at end of file +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.vb b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.vb index 9f16b85c4c..525ec6049b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.vb +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.vb @@ -1,47 +1,181 @@ Imports System -Imports System.IO - -Module Program - ' Sample taken verbatim from https://www.dotnetperls.com/async-vbnet - Sub Main(args As String()) - Dim task = New Task(AddressOf ProcessDataAsync) - ' Start and wait for task to end. - task.Start() - task.Wait() - Console.ReadLine() - End Sub - - Async Sub ProcessDataAsync() - ' Create a task Of Integer. - ' ... Use HandleFileAsync method with a large file. - Dim task As Task(Of Integer) = HandleFileAsync("C:\enable1.txt") - ' This statement runs while HandleFileAsync executes. - Console.WriteLine("Please wait, processing") - ' Use await to wait for task to complete. - Dim result As Integer = Await task - Console.WriteLine("Count: " + result.ToString()) - End Sub - - Async Function HandleFileAsync(ByVal file As String) As Task(Of Integer) - Console.WriteLine("HandleFile enter") - - ' Open the file. - Dim count As Integer = 0 - Using reader As StreamReader = New StreamReader(file) - Dim value As String = Await reader.ReadToEndAsync() - count += value.Length - - ' Do a slow computation on the file. - For i = 0 To 10000 Step 1 - Dim x = value.GetHashCode() - If x = 0 Then - count -= 1 - End If - - Next - End Using - - Console.WriteLine("HandleFile exit") - Return count - End Function -End Module +Imports System.Collections.Generic +Imports System.Threading.Tasks +Imports System.Runtime.CompilerServices + +Namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty + Public Class Async + Private memberField As Integer + + Private Shared Function [True]() As Boolean + Return True + End Function + + Public Async Sub SimpleVoidMethod() + Console.WriteLine("Before") + Await Task.Delay(TimeSpan.FromSeconds(1.0)) + Console.WriteLine("After") + End Sub + + Public Async Sub VoidMethodWithoutAwait() + Console.WriteLine("No Await") + End Sub + + Public Async Sub EmptyVoidMethod() + End Sub + + Public Async Sub AwaitYield() + Await Task.Yield() + End Sub + + Public Async Sub AwaitDefaultYieldAwaitable() + Await CType(Nothing, YieldAwaitable) + End Sub + + Public Async Sub AwaitDefaultHopToThreadPool() + ' unlike YieldAwaitable which implements ICriticalNotifyCompletion, + ' the HopToThreadPoolAwaitable struct only implements + ' INotifyCompletion, so this results in different codegen + Await CType(Nothing, HopToThreadPoolAwaitable) + End Sub + + Public Async Function SimpleVoidTaskMethod() As Task + Console.WriteLine("Before") + Await Task.Delay(TimeSpan.FromSeconds(1.0)) + Console.WriteLine("After") + End Function + + Public Async Function TaskMethodWithoutAwait() As Task + Console.WriteLine("No Await") + End Function + + Public Async Function CapturingThis() As Task + Await Task.Delay(memberField) + End Function + + Public Async Function CapturingThisWithoutAwait() As Task + Console.WriteLine(memberField) + End Function + + Public Async Function SimpleBoolTaskMethod() As Task(Of Boolean) + Console.WriteLine("Before") + Await Task.Delay(TimeSpan.FromSeconds(1.0)) + Console.WriteLine("After") + Return True + End Function + + Public Async Sub TwoAwaitsWithDifferentAwaiterTypes() + Console.WriteLine("Before") + If Await SimpleBoolTaskMethod() Then + Await Task.Delay(TimeSpan.FromSeconds(1.0)) + End If + Console.WriteLine("After") + End Sub + + Public Async Sub AwaitInLoopCondition() + While Await SimpleBoolTaskMethod() + Console.WriteLine("Body") + End While + End Sub + + Public Async Function Issue2366a() As Task + While True + Try + Await Task.CompletedTask + Catch + End Try + End While + End Function + + Public Shared Async Function GetIntegerSumAsync(ByVal items As IEnumerable(Of Integer)) As Task(Of Integer) + Await Task.Delay(100) + Dim num = 0 + For Each item In items + num += item + Next + Return num + End Function + + Public Async Function AsyncCatch(ByVal b As Boolean, ByVal task1 As Task(Of Integer), ByVal task2 As Task(Of Integer)) As Task + Try + Console.WriteLine("Start try") + Await task1 + Console.WriteLine("End try") + Catch ex As Exception + Console.WriteLine("No await") + End Try + End Function + + Public Async Function AsyncCatchThrow(ByVal b As Boolean, ByVal task1 As Task(Of Integer), ByVal task2 As Task(Of Integer)) As Task + Try + Console.WriteLine("Start try") + Await task1 + Console.WriteLine("End try") + Catch ex As Exception + Console.WriteLine("No await") + Throw + End Try + End Function + + Public Async Function AsyncFinally(ByVal b As Boolean, ByVal task1 As Task(Of Integer), ByVal task2 As Task(Of Integer)) As Task + Try + Console.WriteLine("Start try") + Await task1 + Console.WriteLine("End try") + Finally + Console.WriteLine("No await") + End Try + End Function + + Public Shared Async Function AlwaysThrow() As Task + Throw New Exception + End Function + + Public Shared Async Function InfiniteLoop() As Task + While True + End While + End Function + + Public Shared Async Function InfiniteLoopWithAwait() As Task + While True + Await Task.Delay(10) + End While + End Function + + Public Async Function AsyncWithLocalVar() As Task + Dim a As Object = New Object() + Await UseObj(a) + Await UseObj(a) + End Function + + Public Shared Async Function UseObj(ByVal a As Object) As Task + End Function + End Class + + Public Structure AsyncInStruct + Private i As Integer + + Public Async Function Test(ByVal xx As AsyncInStruct) As Task(Of Integer) + xx.i += 1 + i += 1 + Await Task.Yield() + Return i + xx.i + End Function + End Structure + + Public Structure HopToThreadPoolAwaitable + Implements INotifyCompletion + Public Property IsCompleted As Boolean + + Public Function GetAwaiter() As HopToThreadPoolAwaitable + Return Me + End Function + + Public Sub OnCompleted(ByVal continuation As Action) Implements INotifyCompletion.OnCompleted + Task.Run(continuation) + End Sub + + Public Sub GetResult() + End Sub + End Structure +End Namespace diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBAutomaticEvents.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBAutomaticEvents.cs new file mode 100644 index 0000000000..29bfd5557f --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBAutomaticEvents.cs @@ -0,0 +1,14 @@ +public class VBAutomaticEvents +{ + public delegate void EventWithParameterEventHandler(int EventNumber); + public delegate void EventWithoutParameterEventHandler(); + + public event EventWithParameterEventHandler EventWithParameter; + public event EventWithoutParameterEventHandler EventWithoutParameter; + + public void RaiseEvents() + { + EventWithParameter?.Invoke(1); + EventWithoutParameter?.Invoke(); + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBAutomaticEvents.vb b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBAutomaticEvents.vb new file mode 100644 index 0000000000..361cb6b175 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBAutomaticEvents.vb @@ -0,0 +1,9 @@ +Public Class VBAutomaticEvents + Event EventWithParameter(ByVal EventNumber As Integer) + Event EventWithoutParameter() + + Sub RaiseEvents() + RaiseEvent EventWithParameter(1) + RaiseEvent EventWithoutParameter() + End Sub +End Class diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBNonGenericForEach.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBNonGenericForEach.cs new file mode 100644 index 0000000000..9cb9e05df5 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBNonGenericForEach.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections; +using System.Runtime.CompilerServices; + +public class VBNonGenericForEach +{ + public static void M() + { + ArrayList arrayList = new ArrayList(); + foreach (object item in arrayList) + { +#if ROSLYN && OPT + Console.WriteLine(RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(item))); +#else + object objectValue = RuntimeHelpers.GetObjectValue(item); + Console.WriteLine(RuntimeHelpers.GetObjectValue(objectValue)); +#endif + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBNonGenericForEach.vb b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBNonGenericForEach.vb new file mode 100644 index 0000000000..4f3e72f769 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/VBNonGenericForEach.vb @@ -0,0 +1,11 @@ +Imports System +Imports System.Collections + +Public Class VBNonGenericForEach + Public Shared Sub M() + Dim collection = New ArrayList + For Each element In collection + Console.WriteLine(element) + Next + End Sub +End Class diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.cs new file mode 100644 index 0000000000..0103871bcd --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections.Generic; + +using Microsoft.VisualBasic.CompilerServices; + +namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty +{ + internal struct StructWithYieldReturn + { + private int val; + + public IEnumerable Count() + { + yield return val; + yield return val; + } + } + + public class YieldReturnPrettyTest + { + private int fieldOnThis; + + public static IEnumerable YieldChars { + get { + yield return 'a'; + yield return 'b'; + yield return 'c'; + } + } + + internal static void Print(string name, IEnumerator enumerator) + { + Console.WriteLine(name + ": Test start"); + while (enumerator.MoveNext()) + { + Console.WriteLine(name + ": " + enumerator.Current.ToString()); + } + } + + public static IEnumerable SimpleYieldReturn() + { + yield return "A"; + yield return "B"; + yield return "C"; + } + + public static IEnumerator SimpleYieldReturnEnumerator() + { + yield return "A"; + yield return "B"; + yield return "C"; + } + + public IEnumerable YieldReturnParameters(int p) + { + yield return p; + yield return fieldOnThis; + } + + public IEnumerator YieldReturnParametersEnumerator(int p) + { + yield return p; + yield return fieldOnThis; + } + + public static IEnumerable YieldReturnInLoop() + { + int num = 0; + do + { + yield return num; + num = checked(num + 1); + } while (num <= 99); + } + + public static IEnumerable YieldReturnWithTryFinally() + { + yield return 0; + try + { + yield return 1; + } + finally + { + Console.WriteLine("Finally!"); + } + yield return 2; + } + + public static IEnumerable YieldReturnWithNestedTryFinally(bool breakInMiddle) + { + Console.WriteLine("Start of method - 1"); + yield return "Start of method"; + Console.WriteLine("Start of method - 2"); + try + { + Console.WriteLine("Within outer try - 1"); + yield return "Within outer try"; + Console.WriteLine("Within outer try - 2"); + try + { + Console.WriteLine("Within inner try - 1"); + yield return "Within inner try"; + Console.WriteLine("Within inner try - 2"); + if (breakInMiddle) + { + Console.WriteLine("Breaking..."); + yield break; + } + Console.WriteLine("End of inner try - 1"); + yield return "End of inner try"; + Console.WriteLine("End of inner try - 2"); + } + finally + { + Console.WriteLine("Inner Finally"); + } + Console.WriteLine("End of outer try - 1"); + yield return "End of outer try"; + Console.WriteLine("End of outer try - 2"); + } + finally + { + Console.WriteLine("Outer Finally"); + } + Console.WriteLine("End of method - 1"); + yield return "End of method"; + Console.WriteLine("End of method - 2"); + } + + public static IEnumerable YieldReturnWithTwoNonNestedFinallyBlocks(IEnumerable input) + { + // outer try-finally block + foreach (string item in input) + { + // nested try-finally block + try + { + yield return item; + } + finally + { + Console.WriteLine("Processed " + item); + } + } + yield return "A"; + yield return "B"; + yield return "C"; + yield return "D"; + yield return "E"; + yield return "F"; + // outer try-finally block + foreach (string item2 in input) + { + yield return item2.ToUpper(); + } + } + + public static IEnumerable GetEvenNumbers(int n) + { + int num = checked(n - 1); + for (int i = 0; i <= num; i = checked(i + 1)) + { + if (i % 2 == 0) + { + yield return i; + } + } + } + + public static IEnumerable ExceptionHandling() + { + yield return 'a'; + try + { + Console.WriteLine("1 - try"); + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + Console.WriteLine("1 - catch"); + ProjectData.ClearProjectError(); + } + yield return 'b'; + try + { + try + { + Console.WriteLine("2 - try"); + } + finally + { + Console.WriteLine("2 - finally"); + } + yield return 'c'; + } + finally + { + Console.WriteLine("outer finally"); + } + } + + public static IEnumerable YieldBreakInCatch() + { + yield return 0; + try + { + Console.WriteLine("In Try"); + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + ProjectData.ClearProjectError(); + // yield return is not allowed in catch, but yield break is + yield break; + } + yield return 1; + } + + public static IEnumerable YieldBreakInCatchInTryFinally() + { + try + { + yield return 0; + try + { + Console.WriteLine("In Try"); + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + ProjectData.ClearProjectError(); + // yield return is not allowed in catch, but yield break is + // Note that pre-roslyn, this code triggers a compiler bug: + // If the finally block throws an exception, it ends up getting + // called a second time. + yield break; + } + yield return 1; + } + finally + { + Console.WriteLine("Finally"); + } + } + + public static IEnumerable YieldBreakInTryCatchInTryFinally() + { + try + { + yield return 0; + try + { + Console.WriteLine("In Try"); + // same compiler bug as in YieldBreakInCatchInTryFinally + yield break; + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + Console.WriteLine("Catch"); + ProjectData.ClearProjectError(); + } + yield return 1; + } + finally + { + Console.WriteLine("Finally"); + } + } + + public static IEnumerable YieldBreakInTryFinallyInTryFinally(bool b) + { + try + { + yield return 0; + try + { + Console.WriteLine("In Try"); + if (b) + { + // same compiler bug as in YieldBreakInCatchInTryFinally + yield break; + } + } + finally + { + Console.WriteLine("Inner Finally"); + } + yield return 1; + } + finally + { + Console.WriteLine("Finally"); + } + } + + public static IEnumerable YieldBreakOnly() + { + yield break; + } + + public static IEnumerable UnconditionalThrowInTryFinally() + { + // Here, MoveNext() doesn't call the finally methods at all + // (only indirectly via Dispose()) + try + { + yield return 0; + throw new NotImplementedException(); + } + finally + { + Console.WriteLine("Finally"); + } + } + + public static IEnumerable NestedTryFinallyStartingOnSamePosition() + { + // The first user IL instruction is already in 2 nested try blocks. +#if ((ROSLYN2 && !ROSLYN4) && OPT) + int num = -1; +#endif + try + { +#if ((ROSLYN2 && !ROSLYN4) && OPT) + _ = num - 1; +#endif + try + { + yield return 0; + } + finally + { + Console.WriteLine("Inner Finally"); + } + } + finally + { + Console.WriteLine("Outer Finally"); + } + } + +#if ROSLYN + public static IEnumerable LocalInFinally(T a) where T : IDisposable + { + yield return 1; + try + { + yield return 2; + } + finally + { + T val = a; + val.Dispose(); + val.Dispose(); + } + yield return 3; + } +#endif + + public static IEnumerable GenericYield() where T : new() + { + T val = new T(); + int num = 0; + do + { + yield return val; + num = checked(num + 1); + } while (num <= 2); + } + + public static IEnumerable MultipleYieldBreakInTryFinally(int i) + { + try + { + if (i == 2) + { + yield break; + } + + while (i < 40) + { + if (i % 2 == 0) + { + yield break; + } + i = checked(i + 1); + + yield return i; + } + } + finally + { + Console.WriteLine("finally"); + } + Console.WriteLine("normal exit"); + } + + internal IEnumerable ForLoopWithYieldReturn(int end, int evil) + { + // This loop needs to pick the implicit "yield break;" as exit point + // in order to produce pretty code; not the "throw" which would + // be a less-pretty option. + checked + { + int num = end - 1; + for (int i = 0; i <= num; i++) + { + if (i == evil) + { + throw new InvalidOperationException("Found evil number"); + } + yield return i; + } + } + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.vb b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.vb new file mode 100644 index 0000000000..a12d95b127 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.vb @@ -0,0 +1,300 @@ +Imports System +Imports System.Collections.Generic + +Namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty + Friend Structure StructWithYieldReturn + Private val As Integer + + Public Iterator Function Count() As IEnumerable(Of Integer) + Yield val + Yield val + End Function + End Structure + + Public Class YieldReturnPrettyTest + Private fieldOnThis As Integer + + Public Shared ReadOnly Iterator Property YieldChars As IEnumerable(Of Char) + Get + Yield "a"c + Yield "b"c + Yield "c"c + End Get + End Property + + Friend Shared Sub Print(Of T)(ByVal name As String, ByVal enumerator As IEnumerator(Of T)) + Console.WriteLine(name + ": Test start") + While enumerator.MoveNext() + Console.WriteLine(name + ": " + enumerator.Current.ToString()) + End While + End Sub + + Public Shared Iterator Function SimpleYieldReturn() As IEnumerable(Of String) + Yield "A" + Yield "B" + Yield "C" + End Function + + Public Shared Iterator Function SimpleYieldReturnEnumerator() As IEnumerator(Of String) + Yield "A" + Yield "B" + Yield "C" + End Function + + Public Iterator Function YieldReturnParameters(ByVal p As Integer) As IEnumerable(Of Integer) + Yield p + Yield fieldOnThis + End Function + + Public Iterator Function YieldReturnParametersEnumerator(ByVal p As Integer) As IEnumerator(Of Integer) + Yield p + Yield fieldOnThis + End Function + + Public Shared Iterator Function YieldReturnInLoop() As IEnumerable(Of Integer) + For i = 0 To 99 + Yield i + Next + End Function + + Public Shared Iterator Function YieldReturnWithTryFinally() As IEnumerable(Of Integer) + Yield 0 + Try + Yield 1 + Finally + Console.WriteLine("Finally!") + End Try + Yield 2 + End Function + + Public Shared Iterator Function YieldReturnWithNestedTryFinally(ByVal breakInMiddle As Boolean) As IEnumerable(Of String) + Console.WriteLine("Start of method - 1") + Yield "Start of method" + Console.WriteLine("Start of method - 2") + Try + Console.WriteLine("Within outer try - 1") + Yield "Within outer try" + Console.WriteLine("Within outer try - 2") + Try + Console.WriteLine("Within inner try - 1") + Yield "Within inner try" + Console.WriteLine("Within inner try - 2") + If breakInMiddle Then + Console.WriteLine("Breaking...") + Return + End If + Console.WriteLine("End of inner try - 1") + Yield "End of inner try" + Console.WriteLine("End of inner try - 2") + Finally + Console.WriteLine("Inner Finally") + End Try + Console.WriteLine("End of outer try - 1") + Yield "End of outer try" + Console.WriteLine("End of outer try - 2") + Finally + Console.WriteLine("Outer Finally") + End Try + Console.WriteLine("End of method - 1") + Yield "End of method" + Console.WriteLine("End of method - 2") + End Function + + Public Shared Iterator Function YieldReturnWithTwoNonNestedFinallyBlocks(ByVal input As IEnumerable(Of String)) As IEnumerable(Of String) + ' outer try-finally block + For Each line In input + ' nested try-finally block + Try + Yield line + Finally + Console.WriteLine("Processed " & line) + End Try + Next + Yield "A" + Yield "B" + Yield "C" + Yield "D" + Yield "E" + Yield "F" + ' outer try-finally block + For Each item In input + Yield item.ToUpper() + Next + End Function + + Public Shared Iterator Function GetEvenNumbers(ByVal n As Integer) As IEnumerable(Of Integer) + For i = 0 To n - 1 + If i Mod 2 = 0 Then + Yield i + End If + Next + End Function + + Public Shared Iterator Function ExceptionHandling() As IEnumerable(Of Char) + Yield "a"c + Try + Console.WriteLine("1 - try") + Catch __unusedException1__ As Exception + Console.WriteLine("1 - catch") + End Try + Yield "b"c + Try + Try + Console.WriteLine("2 - try") + Finally + Console.WriteLine("2 - finally") + End Try + Yield "c"c + Finally + Console.WriteLine("outer finally") + End Try + End Function + + Public Shared Iterator Function YieldBreakInCatch() As IEnumerable(Of Integer) + Yield 0 + Try + Console.WriteLine("In Try") + Catch + ' yield return is not allowed in catch, but yield break is + Return + End Try + Yield 1 + End Function + + Public Shared Iterator Function YieldBreakInCatchInTryFinally() As IEnumerable(Of Integer) + Try + Yield 0 + Try + Console.WriteLine("In Try") + Catch + ' yield return is not allowed in catch, but yield break is + ' Note that pre-roslyn, this code triggers a compiler bug: + ' If the finally block throws an exception, it ends up getting + ' called a second time. + Return + End Try + Yield 1 + Finally + Console.WriteLine("Finally") + End Try + End Function + + Public Shared Iterator Function YieldBreakInTryCatchInTryFinally() As IEnumerable(Of Integer) + Try + Yield 0 + Try + Console.WriteLine("In Try") + ' same compiler bug as in YieldBreakInCatchInTryFinally + Return + Catch + Console.WriteLine("Catch") + End Try + Yield 1 + Finally + Console.WriteLine("Finally") + End Try + End Function + + Public Shared Iterator Function YieldBreakInTryFinallyInTryFinally(ByVal b As Boolean) As IEnumerable(Of Integer) + Try + Yield 0 + Try + Console.WriteLine("In Try") + If b Then + ' same compiler bug as in YieldBreakInCatchInTryFinally + Return + End If + + Finally + Console.WriteLine("Inner Finally") + End Try + Yield 1 + Finally + Console.WriteLine("Finally") + End Try + End Function + + Public Shared Iterator Function YieldBreakOnly() As IEnumerable(Of Integer) + Return + End Function + + Public Shared Iterator Function UnconditionalThrowInTryFinally() As IEnumerable(Of Integer) + ' Here, MoveNext() doesn't call the finally methods at all + ' (only indirectly via Dispose()) + Try + Yield 0 + Throw New NotImplementedException() + Finally + Console.WriteLine("Finally") + End Try + End Function + + Public Shared Iterator Function NestedTryFinallyStartingOnSamePosition() As IEnumerable(Of Integer) + ' The first user IL instruction is already in 2 nested try blocks. + Try + Try + Yield 0 + Finally + Console.WriteLine("Inner Finally") + End Try + + Finally + Console.WriteLine("Outer Finally") + End Try + End Function + +#If ROSLYN Then + Public Shared Iterator Function LocalInFinally(Of T As IDisposable)(ByVal a As T) As IEnumerable(Of Integer) + Yield 1 + Try + Yield 2 + Finally + Dim val = a + val.Dispose() + val.Dispose() + End Try + Yield 3 + End Function +#End If + + Public Shared Iterator Function GenericYield(Of T As New)() As IEnumerable(Of T) + Dim val As T = New T() + For i = 0 To 2 + Yield val + Next + End Function + + Public Shared Iterator Function MultipleYieldBreakInTryFinally(ByVal i As Integer) As IEnumerable(Of Integer) + Try + If i = 2 Then + Return + End If + + While i < 40 + If i Mod 2 = 0 Then + Return + End If + i += 1 + + Yield i + End While + + Finally + Console.WriteLine("finally") + End Try + Console.WriteLine("normal exit") + End Function + + Friend Iterator Function ForLoopWithYieldReturn(ByVal [end] As Integer, ByVal evil As Integer) As IEnumerable(Of Integer) + ' This loop needs to pick the implicit "yield break;" as exit point + ' in order to produce pretty code; not the "throw" which would + ' be a less-pretty option. + For i = 0 To [end] - 1 + If i = evil Then + Throw New InvalidOperationException("Found evil number") + End If + Yield i + Next + End Function + End Class +End Namespace diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs index 38435f1d0a..b6346f0fde 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs @@ -616,6 +616,16 @@ public void DefaultConstructorAddedToStruct() Assert.IsTrue(ctors.All(c => c.Accessibility == Accessibility.Public)); } + [Test] + public void NoDefaultConstructorAddedToStruct() + { + var ctors = compilation.FindType(typeof(MyStructWithDefaultCtor)).GetConstructors(); + Assert.AreEqual(1, ctors.Count()); + Assert.IsFalse(ctors.Any(c => c.IsStatic)); + Assert.IsTrue(ctors.All(c => c.ReturnType.Kind == TypeKind.Void)); + Assert.IsTrue(ctors.All(c => c.Accessibility == Accessibility.Public)); + } + [Test] public void NoDefaultConstructorAddedToClass() { diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs index 1a55b5cce5..a526c793ff 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs @@ -140,6 +140,11 @@ public struct MyStructWithCtor public MyStructWithCtor(int a) { } } + public struct MyStructWithDefaultCtor + { + public MyStructWithDefaultCtor() { } + } + public class MyClassWithCtor { private MyClassWithCtor(int a) { } diff --git a/ICSharpCode.Decompiler.Tests/Util/FileUtilityTests.cs b/ICSharpCode.Decompiler.Tests/Util/FileUtilityTests.cs index cef3d91f31..e188f94780 100644 --- a/ICSharpCode.Decompiler.Tests/Util/FileUtilityTests.cs +++ b/ICSharpCode.Decompiler.Tests/Util/FileUtilityTests.cs @@ -16,9 +16,11 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using ICSharpCode.Decompiler.Util; + using NUnit.Framework; -namespace ICSharpCode.Decompiler.Util +namespace ICSharpCode.Decompiler.Tests.Util { [TestFixture] public class FileUtilityTests diff --git a/ICSharpCode.Decompiler.Tests/Util/ResourceReaderWriterTests.cs b/ICSharpCode.Decompiler.Tests/Util/ResourceReaderWriterTests.cs new file mode 100644 index 0000000000..cad0afa930 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/Util/ResourceReaderWriterTests.cs @@ -0,0 +1,189 @@ +// Copyright (c) 2023 Siegfried Pammer +// +// 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.Globalization; +using System.IO; +using System.Linq; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Xml.Linq; +using System.Xml.XPath; + +using ICSharpCode.Decompiler.Util; + +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace ICSharpCode.Decompiler.Tests.Util +{ + [TestFixture] + public class ResourceReaderWriterTests + { + const string WinFormsAssemblyName = ", System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; + const string MSCorLibAssemblyName = ", mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; + + [Serializable] + public class SerializableClass + { + public string Name { get; set; } + public int Age { get; set; } + } + + static readonly object[][] TestWriteCases = { + new object[] { "Decimal", 1.0m, "1.0", "System.Decimal" + MSCorLibAssemblyName }, + new object[] { "TimeSpan", TimeSpan.FromSeconds(42), "00:00:42", "System.TimeSpan" + MSCorLibAssemblyName }, + new object[] { "DateTime", DateTime.Parse("06/18/2023 21:36:30", CultureInfo.InvariantCulture), "06/18/2023 21:36:30", "System.DateTime" + MSCorLibAssemblyName }, + }; + + static readonly object[][] TestReadCases = { + new object[] { "Decimal", 1.0m }, + new object[] { "TimeSpan", TimeSpan.FromSeconds(42) }, + new object[] { "DateTime", DateTime.Parse("06/18/2023 21:36:30", CultureInfo.InvariantCulture) }, + }; + + static MemoryStream ProduceResourcesTestFile(string name, T value) + { + var ms = new MemoryStream(); + var writer = new ResourceWriter(ms); + writer.AddResource(name, value); + writer.Generate(); + ms.Position = 0; + return ms; + } + + static XElement ProduceResXTest(string name, T value) + { + using var ms = new MemoryStream(); + var writer = new ResXResourceWriter(ms); + writer.AddResource(name, value); + writer.Generate(); + ms.Position = 0; + var doc = XDocument.Load(ms); + return doc.XPathSelectElement(".//data"); + } + + [TestCase("Null", null)] + [TestCase("String", "Hello World!")] + [TestCase("Char", 'A')] + [TestCase("Bool", true)] + [TestCase("Bool", false)] + [TestCase("Byte", (byte)1)] + [TestCase("SByte", (sbyte)-1)] + [TestCase("Int16", (short)1)] + [TestCase("UInt16", (ushort)1)] + [TestCase("Int32", 1)] + [TestCase("UInt32", (uint)1)] + [TestCase("Int64", (long)1)] + [TestCase("UInt64", (ulong)1)] + [TestCase("Single", 1.0f)] + [TestCase("Double", 1.0d)] + [TestCase("Bytes", new byte[] { 42, 43, 44 })] + [TestCaseSource(nameof(TestReadCases))] + public void Read(string name, object value) + { + using var testFile = ProduceResourcesTestFile(name, value); + using var reader = new ResourcesFile(testFile); + var items = reader.ToArray(); + Assert.AreEqual(1, items.Length); + Assert.AreEqual(name, items[0].Key); + Assert.AreEqual(value, items[0].Value); + } + + [TestCase("Null", null, null, "System.Resources.ResXNullRef" + WinFormsAssemblyName)] + [TestCase("String", "Hello World!", "Hello World!", null)] + [TestCase("Bool", true, "True", "System.Boolean" + MSCorLibAssemblyName)] + [TestCase("Bool", false, "False", "System.Boolean" + MSCorLibAssemblyName)] + [TestCase("Char", 'A', "A", "System.Char" + MSCorLibAssemblyName)] + [TestCase("Byte", (byte)1, "1", "System.Byte" + MSCorLibAssemblyName)] + [TestCase("SByte", (sbyte)-1, "-1", "System.SByte" + MSCorLibAssemblyName)] + [TestCase("Int16", (short)1, "1", "System.Int16" + MSCorLibAssemblyName)] + [TestCase("UInt16", (ushort)1, "1", "System.UInt16" + MSCorLibAssemblyName)] + [TestCase("Int32", 1, "1", "System.Int32" + MSCorLibAssemblyName)] + [TestCase("UInt32", (uint)1, "1", "System.UInt32" + MSCorLibAssemblyName)] + [TestCase("Int64", (long)1, "1", "System.Int64" + MSCorLibAssemblyName)] + [TestCase("UInt64", (ulong)1, "1", "System.UInt64" + MSCorLibAssemblyName)] + [TestCase("Single", 1.0f, "1", "System.Single" + MSCorLibAssemblyName)] + [TestCase("Double", 1.0d, "1", "System.Double" + MSCorLibAssemblyName)] + [TestCaseSource(nameof(TestWriteCases))] + public void Write(string name, object value, string serializedValue, string typeName) + { + var element = ProduceResXTest(name, value); + Assert.AreEqual(name, element.Attribute("name")?.Value); + if (typeName != null) + { + Assert.AreEqual(typeName, element.Attribute("type")?.Value); + } + var v = element.Element("value"); + Assert.IsNotNull(v); + Assert.IsTrue(v.IsEmpty ? serializedValue == null : v.Value == serializedValue); + } + + [Test] + public void ResXSerializableClassIsRejected() + { + Assert.Throws( + () => ProduceResXTest("Serial", new SerializableClass { Name = "Hugo", Age = 42 }) + ); + } + + [Test] + public void BitmapIsResourceSerializedObject() + { + Stream stream = typeof(ResourceReaderWriterTests).Assembly + .GetManifestResourceStream(typeof(ResourceReaderWriterTests).Namespace + ".Test.resources"); + using var reader = new ResourcesFile(stream); + var items = reader.ToArray(); + Assert.AreEqual(3, items.Length); + var item = items.FirstOrDefault(i => i.Key == "Bitmap"); + Assert.IsNotNull(item.Key); + Assert.IsInstanceOf(item.Value); + } + + [Test] + public void ByteArrayIsSupported() + { + Stream stream = typeof(ResourceReaderWriterTests).Assembly + .GetManifestResourceStream(typeof(ResourceReaderWriterTests).Namespace + ".Test.resources"); + using var reader = new ResourcesFile(stream); + var items = reader.ToArray(); + Assert.AreEqual(3, items.Length); + var item = items.FirstOrDefault(i => i.Key == "Byte[]"); + Assert.IsNotNull(item.Key); + Assert.IsInstanceOf(item.Value); + byte[] array = (byte[])item.Value; + Assert.AreEqual(3, array.Length); + Assert.AreEqual(42, array[0]); + Assert.AreEqual(43, array[1]); + Assert.AreEqual(44, array[2]); + } + + [Test] + public void MemoryStreamIsSupported() + { + Stream stream = typeof(ResourceReaderWriterTests).Assembly + .GetManifestResourceStream(typeof(ResourceReaderWriterTests).Namespace + ".Test.resources"); + using var reader = new ResourcesFile(stream); + var items = reader.ToArray(); + Assert.AreEqual(3, items.Length); + var item = items.FirstOrDefault(i => i.Key == "MemoryStream"); + Assert.IsNotNull(item.Key); + Assert.IsInstanceOf(item.Value); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/Util/Test.resources b/ICSharpCode.Decompiler.Tests/Util/Test.resources new file mode 100644 index 0000000000..375a8c2499 Binary files /dev/null and b/ICSharpCode.Decompiler.Tests/Util/Test.resources differ diff --git a/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs index b5db5269bf..2dcd061392 100644 --- a/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs @@ -1,4 +1,4 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -89,10 +89,10 @@ public void AllFilesHaveTests() CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, }; - [Test, Ignore("Implement VB async/await")] + [Test] public async Task Async([ValueSource(nameof(defaultOptions))] CompilerOptions options) { - await Run(options: options); + await Run(options: options | CompilerOptions.Library); } [Test] // TODO: legacy VB compound assign @@ -125,6 +125,24 @@ public async Task VBPropertiesTest([ValueSource(nameof(defaultOptions))] Compile await Run(options: options | CompilerOptions.Library); } + [Test] + public async Task VBAutomaticEvents([ValueSource(nameof(defaultOptions))] CompilerOptions options) + { + await Run(options: options | CompilerOptions.Library); + } + + [Test] + public async Task VBNonGenericForEach([ValueSource(nameof(defaultOptions))] CompilerOptions options) + { + await Run(options: options | CompilerOptions.Library); + } + + [Test] + public async Task YieldReturn([ValueSource(nameof(defaultOptions))] CompilerOptions options) + { + await Run(options: options | CompilerOptions.Library); + } + async Task Run([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug, DecompilerSettings settings = null) { var vbFile = Path.Combine(TestCasePath, testName + ".vb"); @@ -136,7 +154,7 @@ async Task Run([CallerMemberName] string testName = null, CompilerOptions option } var executable = await Tester.CompileVB(vbFile, options | CompilerOptions.ReferenceVisualBasic, exeFile).ConfigureAwait(false); - var decompiled = await Tester.DecompileCSharp(executable.PathToAssembly, settings).ConfigureAwait(false); + var decompiled = await Tester.DecompileCSharp(executable.PathToAssembly, settings ?? new DecompilerSettings { FileScopedNamespaces = false }).ConfigureAwait(false); CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(options).ToArray()); Tester.RepeatOnIOError(() => File.Delete(decompiled)); diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index c14b830104..9615359d3c 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2014 Daniel Grunwald +// Copyright (c) 2014 Daniel Grunwald // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -367,8 +367,16 @@ bool IsGetterOnlyProperty(string propertyName) return true; } // event-fields are not [CompilerGenerated] - if (settings.AutomaticEvents && metadata.GetTypeDefinition(field.GetDeclaringType()).GetEvents().Any(ev => metadata.GetEventDefinition(ev).Name == field.Name)) - return true; + if (settings.AutomaticEvents) + { + foreach (var ev in metadata.GetTypeDefinition(field.GetDeclaringType()).GetEvents()) + { + var eventName = metadata.GetString(metadata.GetEventDefinition(ev).Name); + var fieldName = metadata.GetString(field.Name); + if (IsEventBackingFieldName(fieldName, eventName, out _)) + return true; + } + } if (settings.ArrayInitializers && metadata.GetString(metadata.GetTypeDefinition(field.GetDeclaringType()).Name).StartsWith("", StringComparison.Ordinal)) { // only hide fields starting with '__StaticArrayInit' @@ -412,6 +420,20 @@ static bool IsAutomaticPropertyBackingField(FieldDefinition field, MetadataReade return false; } + internal static bool IsEventBackingFieldName(string fieldName, string eventName, out int suffixLength) + { + suffixLength = 0; + if (fieldName == eventName) + return true; + var vbSuffixLength = "Event".Length; + if (fieldName.Length == eventName.Length + vbSuffixLength && fieldName.StartsWith(eventName, StringComparison.Ordinal) && fieldName.EndsWith("Event", StringComparison.Ordinal)) + { + suffixLength = vbSuffixLength; + return true; + } + return false; + } + static bool IsAnonymousMethodCacheField(SRM.FieldDefinition field, MetadataReader metadata) { var name = metadata.GetString(field.Name); @@ -508,6 +530,7 @@ static TypeSystemAstBuilder CreateAstBuilder(DecompilerSettings settings) typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors; typeSystemAstBuilder.SupportRecordClasses = settings.RecordClasses; typeSystemAstBuilder.SupportRecordStructs = settings.RecordStructs; + typeSystemAstBuilder.SupportUnsignedRightShift = settings.UnsignedRightShift; typeSystemAstBuilder.AlwaysUseGlobal = settings.AlwaysUseGlobal; return typeSystemAstBuilder; } @@ -827,7 +850,7 @@ private static void ReadCodeMappingInfo(PEFile module, CodeMappingInfo info, Met foreach (var m in closureType.GetMethods()) { var methodDef = module.Metadata.GetMethodDefinition(m); - if (methodDef.Name == memberRef.Name) + if (methodDef.Name == memberRef.Name && m.IsCompilerGeneratedOrIsInCompilerGeneratedClass(module.Metadata)) connectedMethods.Enqueue(m); } } @@ -953,7 +976,7 @@ public SyntaxTree DecompileType(FullTypeName fullTypeName) if (type == null) throw new InvalidOperationException($"Could not find type definition {fullTypeName} in type system."); if (type.ParentModule != typeSystem.MainModule) - throw new NotSupportedException("Decompiling types that are not part of the main module is not supported."); + throw new NotSupportedException($"Type {fullTypeName} was not found in the module being decompiled, but only in {type.ParentModule.Name}"); var decompilationContext = new SimpleTypeResolveContext(typeSystem.MainModule); var decompileRun = CreateDecompileRun(); syntaxTree = new SyntaxTree(); @@ -1266,6 +1289,11 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun { typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings); var entityDecl = typeSystemAstBuilder.ConvertEntity(typeDef); + if (entityDecl is DelegateDeclaration delegateDeclaration) + { + // Fix empty parameter names in delegate declarations + FixParameterNames(delegateDeclaration); + } var typeDecl = entityDecl as TypeDeclaration; if (typeDecl == null) { @@ -1369,16 +1397,12 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun } if (settings.IntroduceRefModifiersOnStructs) { - if (FindAttribute(typeDecl, KnownAttribute.Obsolete, out var attr)) - { - if (obsoleteAttributePattern.IsMatch(attr)) - { - if (attr.Parent is AttributeSection section && section.Attributes.Count == 1) - section.Remove(); - else - attr.Remove(); - } - } + RemoveObsoleteAttribute(typeDecl, "Types with embedded references are not supported in this version of your compiler."); + RemoveCompilerFeatureRequiredAttribute(typeDecl, "RefStructs"); + } + if (settings.RequiredMembers) + { + RemoveAttribute(typeDecl, KnownAttribute.RequiredAttribute); } if (typeDecl.ClassType == ClassType.Enum) { @@ -1551,14 +1575,6 @@ EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, PEF return firstValue == 0 ? EnumValueDisplayMode.None : EnumValueDisplayMode.FirstOnly; } - static readonly Syntax.Attribute obsoleteAttributePattern = new Syntax.Attribute() { - Type = new TypePattern(typeof(ObsoleteAttribute)), - Arguments = { - new PrimitiveExpression("Types with embedded references are not supported in this version of your compiler."), - new Choice() { new PrimitiveExpression(true), new PrimitiveExpression(false) } - } - }; - EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeResolveContext decompilationContext) { Debug.Assert(decompilationContext.CurrentMember == method); @@ -1612,6 +1628,10 @@ EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeRe methodDecl.Modifiers &= ~(Modifiers.New | Modifiers.Virtual); methodDecl.Modifiers |= Modifiers.Override; } + if (method.IsConstructor && settings.RequiredMembers && RemoveCompilerFeatureRequiredAttribute(methodDecl, "RequiredMembers")) + { + RemoveObsoleteAttribute(methodDecl, "Constructors of types with required members are not supported in this version of your compiler."); + } return methodDecl; bool IsTypeHierarchyKnown(IType type) @@ -1773,6 +1793,14 @@ internal static void CleanUpMethodDeclaration(EntityDeclaration entityDecl, Bloc { RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden); } + if (function.StateMachineCompiledWithLegacyVisualBasic) + { + RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough); + if (function.Method?.IsAccessor == true && entityDecl.Parent is EntityDeclaration parentDecl) + { + RemoveAttribute(parentDecl, KnownAttribute.DebuggerStepThrough); + } + } } if (function.IsAsync) { @@ -1812,6 +1840,54 @@ internal static bool RemoveAttribute(EntityDeclaration entityDecl, KnownAttribut return found; } + internal static bool RemoveCompilerFeatureRequiredAttribute(EntityDeclaration entityDecl, string feature) + { + bool found = false; + foreach (var section in entityDecl.Attributes) + { + foreach (var attr in section.Attributes) + { + var symbol = attr.Type.GetSymbol(); + if (symbol is ITypeDefinition td && td.FullTypeName == KnownAttribute.CompilerFeatureRequired.GetTypeName() + && attr.Arguments.Count == 1 && attr.Arguments.SingleOrDefault() is PrimitiveExpression pe + && pe.Value is string s && s == feature) + { + attr.Remove(); + found = true; + } + } + if (section.Attributes.Count == 0) + { + section.Remove(); + } + } + return found; + } + + internal static bool RemoveObsoleteAttribute(EntityDeclaration entityDecl, string message) + { + bool found = false; + foreach (var section in entityDecl.Attributes) + { + foreach (var attr in section.Attributes) + { + var symbol = attr.Type.GetSymbol(); + if (symbol is ITypeDefinition td && td.FullTypeName == KnownAttribute.Obsolete.GetTypeName() + && attr.Arguments.Count >= 1 && attr.Arguments.First() is PrimitiveExpression pe + && pe.Value is string s && s == message) + { + attr.Remove(); + found = true; + } + } + if (section.Attributes.Count == 0) + { + section.Remove(); + } + } + return found; + } + bool FindAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType, out Syntax.Attribute attribute) { attribute = null; @@ -1871,6 +1947,10 @@ EntityDeclaration DoDecompile(IField field, DecompileRun decompileRun, ITypeReso typeSystemAstBuilder.UseSpecialConstants = !(field.DeclaringType.Equals(field.ReturnType) || isMathPIOrE); var fieldDecl = typeSystemAstBuilder.ConvertEntity(field); SetNewModifier(fieldDecl); + if (settings.RequiredMembers && RemoveAttribute(fieldDecl, KnownAttribute.RequiredAttribute)) + { + fieldDecl.Modifiers |= Modifiers.Required; + } if (settings.FixedBuffers && IsFixedField(field, out var elementType, out var elementCount)) { var fixedFieldDecl = new FixedFieldDeclaration(); @@ -1983,6 +2063,10 @@ EntityDeclaration DoDecompile(IProperty property, DecompileRun decompileRun, ITy propertyDecl.Modifiers &= ~(Modifiers.New | Modifiers.Virtual); propertyDecl.Modifiers |= Modifiers.Override; } + if (settings.RequiredMembers && RemoveAttribute(propertyDecl, KnownAttribute.RequiredAttribute)) + { + propertyDecl.Modifiers |= Modifiers.Required; + } return propertyDecl; } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 5213febe30..d256cf6259 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -77,8 +77,8 @@ public IList GetArgumentResolveResults(int skipCount = 0) ResolveResult GetResolveResult(int index, TranslatedExpression expression) { var param = expectedParameters[index]; - if (useImplicitlyTypedOut && param.IsOut) - return OutVarResolveResult.Instance; + if (useImplicitlyTypedOut && param.IsOut && expression.Type is ByReferenceType brt) + return new OutVarResolveResult(brt.ElementType); return expression.ResolveResult; } } @@ -539,6 +539,8 @@ public ExpressionWithResolveResult BuildCollectionInitializerExpression(OpCode c public ExpressionWithResolveResult BuildDictionaryInitializerExpression(OpCode callOpCode, IMethod method, InitializedObjectResolveResult target, IReadOnlyList indices, ILInstruction value = null) { + if (method is null) + throw new ArgumentNullException(nameof(method)); ExpectedTargetDetails expectedTargetDetails = new ExpectedTargetDetails { CallOpCode = callOpCode }; var callArguments = new List(); @@ -1017,6 +1019,10 @@ private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetai { switch (errors) { + case OverloadResolutionErrors.OutVarTypeMismatch: + Debug.Assert(argumentList.UseImplicitlyTypedOut); + argumentList.UseImplicitlyTypedOut = false; + continue; case OverloadResolutionErrors.TypeInferenceFailed: if ((allowedTransforms & CallTransformation.RequireTypeArguments) != 0) { @@ -1318,6 +1324,20 @@ OverloadResolutionErrors IsUnambiguousCall(ExpectedTargetDetails expectedTargetD foundMember = or.GetBestCandidateWithSubstitutedTypeArguments(); if (!IsAppropriateCallTarget(expectedTargetDetails, method, foundMember)) return OverloadResolutionErrors.AmbiguousMatch; + var map = or.GetArgumentToParameterMap(); + for (int i = 0; i < arguments.Length; i++) + { + ResolveResult arg = arguments[i]; + int parameterIndex = map[i]; + if (arg is OutVarResolveResult rr && parameterIndex >= 0) + { + var param = foundMember.Parameters[parameterIndex]; + var paramType = param.Type.UnwrapByRef(); + if (!paramType.Equals(rr.OriginalVariableType)) + return OverloadResolutionErrors.OutVarTypeMismatch; + } + } + return OverloadResolutionErrors.None; } @@ -1533,12 +1553,25 @@ ExpressionWithResolveResult HandleConstructorCall(ExpectedTargetDetails expected CastArguments(argumentList.Arguments, argumentList.ExpectedParameters); break; // make sure that we don't not end up in an infinite loop } + IType returnTypeOverride = null; + if (typeSystem.MainModule.TypeSystemOptions.HasFlag(TypeSystemOptions.NativeIntegersWithoutAttribute)) + { + // For DeclaringType, we don't use nint/nuint (so that DeclaringType.GetConstructors etc. works), + // but in NativeIntegersWithoutAttribute mode we must use nint/nuint for expression types, + // so that the appropriate set of conversions is used for further overload resolution. + if (method.DeclaringType.IsKnownType(KnownTypeCode.IntPtr)) + returnTypeOverride = SpecialType.NInt; + else if (method.DeclaringType.IsKnownType(KnownTypeCode.UIntPtr)) + returnTypeOverride = SpecialType.NUInt; + } return new ObjectCreateExpression( expressionBuilder.ConvertType(method.DeclaringType), argumentList.GetArgumentExpressions() ).WithRR(new CSharpInvocationResolveResult( target, method, argumentList.GetArgumentResolveResults().ToArray(), - isExpandedForm: argumentList.IsExpandedForm, argumentToParameterMap: argumentList.ArgumentToParameterMap + isExpandedForm: argumentList.IsExpandedForm, + argumentToParameterMap: argumentList.ArgumentToParameterMap, + returnTypeOverride: returnTypeOverride )); } } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index e964073951..a9e7620fc2 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Daniel Grunwald +// Copyright (c) 2014-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 @@ -375,7 +375,7 @@ TranslatedExpression IsType(IsInst inst) { var arg = Translate(inst.Argument); arg = UnwrapBoxingConversion(arg); - return new IsExpression(arg.Expression, ConvertType(inst.Type)) + return new IsExpression(arg.Expression, ConvertType(inst.Type.TupleUnderlyingTypeOrSelf())) .WithILInstruction(inst) .WithRR(new TypeIsResolveResult(arg.ResolveResult, inst.Type, compilation.FindType(TypeCode.Boolean))); } @@ -617,6 +617,14 @@ protected internal override TranslatedExpression VisitLdStr(LdStr inst, Translat .WithRR(new ConstantResolveResult(compilation.FindType(KnownTypeCode.String), inst.Value)); } + protected internal override TranslatedExpression VisitLdStrUtf8(LdStrUtf8 inst, TranslationContext context) + { + var type = new ParameterizedType(compilation.FindType(KnownTypeCode.ReadOnlySpanOfT), new[] { compilation.FindType(KnownTypeCode.Byte) }); + return new PrimitiveExpression(inst.Value, LiteralFormat.Utf8Literal) + .WithILInstruction(inst) + .WithRR(new ConstantResolveResult(type, inst.Value)); + } + protected internal override TranslatedExpression VisitLdNull(LdNull inst, TranslationContext context) { return GetDefaultValueExpression(SpecialType.NullType).WithILInstruction(inst); @@ -1200,9 +1208,9 @@ protected internal override TranslatedExpression VisitBinaryNumericInstruction(B case BinaryNumericOperator.BitXor: return HandleBinaryNumeric(inst, BinaryOperatorType.ExclusiveOr, context); case BinaryNumericOperator.ShiftLeft: - return HandleShift(inst, BinaryOperatorType.ShiftLeft); + return HandleShift(inst, BinaryOperatorType.ShiftLeft, context); case BinaryNumericOperator.ShiftRight: - return HandleShift(inst, BinaryOperatorType.ShiftRight); + return HandleShift(inst, BinaryOperatorType.ShiftRight, context); default: throw new ArgumentOutOfRangeException(); } @@ -1579,7 +1587,7 @@ TranslatedExpression HandleBinaryNumeric(BinaryNumericInstruction inst, BinaryOp var rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult); if (rr.IsError || NullableType.GetUnderlyingType(rr.Type).GetStackType() != inst.UnderlyingResultType - || !IsCompatibleWithSign(left.Type, inst.Sign) || !IsCompatibleWithSign(right.Type, inst.Sign)) + || !IsCompatibleWithSign(rr.Type, inst.Sign)) { // Left and right operands are incompatible, so convert them to a common type Sign sign = inst.Sign; @@ -1721,13 +1729,14 @@ static bool BinaryOperatorMightCheckForOverflow(BinaryOperatorType op) case BinaryOperatorType.ExclusiveOr: case BinaryOperatorType.ShiftLeft: case BinaryOperatorType.ShiftRight: + case BinaryOperatorType.UnsignedShiftRight: return false; default: return true; } } - TranslatedExpression HandleShift(BinaryNumericInstruction inst, BinaryOperatorType op) + TranslatedExpression HandleShift(BinaryNumericInstruction inst, BinaryOperatorType op, TranslationContext context) { var left = Translate(inst.Left); var right = Translate(inst.Right); @@ -1736,10 +1745,27 @@ TranslatedExpression HandleShift(BinaryNumericInstruction inst, BinaryOperatorTy Sign sign = inst.Sign; var leftUType = NullableType.GetUnderlyingType(left.Type); - if (leftUType.IsCSharpSmallIntegerType() && sign != Sign.Unsigned && inst.UnderlyingResultType == StackType.I4) + bool couldUseUnsignedRightShift = ( + sign == Sign.Unsigned && op == BinaryOperatorType.ShiftRight && settings.UnsignedRightShift + && (leftUType.IsCSharpPrimitiveIntegerType() || leftUType.IsCSharpNativeIntegerType()) + // If we need to cast to unsigned anyway, don't use >>> operator. + && context.TypeHint.GetSign() != Sign.Unsigned + ); + if (leftUType.IsCSharpSmallIntegerType() && inst.UnderlyingResultType == StackType.I4 && + (sign != Sign.Unsigned || couldUseUnsignedRightShift)) { // With small integer types, C# will promote to int and perform signed shifts. // We thus don't need any casts in this case. + // The >>> operator also promotes to signed int, but then performs an unsigned shift. + if (sign == Sign.Unsigned) + { + op = BinaryOperatorType.UnsignedShiftRight; + } + } + else if (couldUseUnsignedRightShift && leftUType.GetSize() == inst.UnderlyingResultType.GetSize() && leftUType.GetSign() == Sign.Signed) + { + // Use C# 11 unsigned right shift operator. We don't need any casts in this case. + op = BinaryOperatorType.UnsignedShiftRight; } else { @@ -1796,7 +1822,7 @@ protected internal override TranslatedExpression VisitUserDefinedCompoundAssign( else if (inst.Method.Parameters.Count == 2) { var value = Translate(inst.Value).ConvertTo(inst.Method.Parameters[1].Type, this); - AssignmentOperatorType? op = GetAssignmentOperatorTypeFromMetadataName(inst.Method.Name); + AssignmentOperatorType? op = GetAssignmentOperatorTypeFromMetadataName(inst.Method.Name, settings); Debug.Assert(op != null); return new AssignmentExpression(target, op.Value, value) @@ -1814,7 +1840,7 @@ protected internal override TranslatedExpression VisitUserDefinedCompoundAssign( } } - internal static AssignmentOperatorType? GetAssignmentOperatorTypeFromMetadataName(string name) + internal static AssignmentOperatorType? GetAssignmentOperatorTypeFromMetadataName(string name, DecompilerSettings settings) { switch (name) { @@ -1838,6 +1864,8 @@ protected internal override TranslatedExpression VisitUserDefinedCompoundAssign( return AssignmentOperatorType.ShiftLeft; case "op_RightShift": return AssignmentOperatorType.ShiftRight; + case "op_UnsignedRightShift" when settings.UnsignedRightShift: + return AssignmentOperatorType.UnsignedShiftRight; default: return null; } @@ -1879,7 +1907,22 @@ protected internal override TranslatedExpression VisitNumericCompoundAssign(Nume case BinaryNumericOperator.ShiftLeft: return HandleCompoundShift(inst, AssignmentOperatorType.ShiftLeft); case BinaryNumericOperator.ShiftRight: - return HandleCompoundShift(inst, AssignmentOperatorType.ShiftRight); + if (inst.Sign == Sign.Unsigned && inst.Type.GetSign() == Sign.Signed) + { + Debug.Assert(settings.UnsignedRightShift); + return HandleCompoundShift(inst, AssignmentOperatorType.UnsignedShiftRight); + } + else if (inst.Sign == Sign.Unsigned && inst.Type.IsCSharpSmallIntegerType() && settings.UnsignedRightShift) + { + // For small unsigned integer types promoted to signed int, the sign bit will be zero, + // so there is no difference between signed and unsigned shift. + // However the IL still indicates which C# operator was used, so preserve that if the setting allows us to. + return HandleCompoundShift(inst, AssignmentOperatorType.UnsignedShiftRight); + } + else + { + return HandleCompoundShift(inst, AssignmentOperatorType.ShiftRight); + } default: throw new ArgumentOutOfRangeException(); } @@ -1901,7 +1944,16 @@ TranslatedExpression HandleCompoundAssignment(NumericCompoundAssign inst, Assign if (inst.EvalMode == CompoundEvalMode.EvaluatesToOldValue) { Debug.Assert(op == AssignmentOperatorType.Add || op == AssignmentOperatorType.Subtract); - Debug.Assert(inst.Value.MatchLdcI(1) || inst.Value.MatchLdcF4(1) || inst.Value.MatchLdcF8(1)); +#if DEBUG + if (target.Type is PointerType ptrType) + { + ILInstruction instValue = PointerArithmeticOffset.Detect(inst.Value, ptrType.ElementType, inst.CheckForOverflow); + Debug.Assert(instValue is not null); + Debug.Assert(instValue.MatchLdcI(1)); + } + else + Debug.Assert(inst.Value.MatchLdcI(1) || inst.Value.MatchLdcF4(1) || inst.Value.MatchLdcF8(1)); +#endif UnaryOperatorType unary; ExpressionType exprType; if (op == AssignmentOperatorType.Add) @@ -3340,6 +3392,7 @@ private ArrayInitializerExpression BuildArrayInitializerExpression(Block block, { var property = (IProperty)lastElement.Member; Debug.Assert(property.IsIndexer); + Debug.Assert(property.Setter != null, $"Indexer property {property} has no setter"); elementsStack.Peek().Add( new CallBuilder(this, typeSystem, settings) .BuildDictionaryInitializerExpression(lastElement.OpCode, property.Setter, initObjRR, GetIndices(lastElement.Indices, indexVariables).ToList(), info.Values.Single()) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index 839fdbb331..2076139947 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -83,6 +83,10 @@ public void ConvertSymbol(ISymbol symbol, TokenWriter writer, CSharpFormattingOp case ClassType.RecordClass: writer.WriteKeyword(Roles.RecordKeyword, "record"); break; + case ClassType.RecordStruct: + writer.WriteKeyword(Roles.RecordKeyword, "record"); + writer.WriteKeyword(Roles.StructKeyword, "struct"); + break; default: throw new Exception("Invalid value for ClassType"); } @@ -233,6 +237,7 @@ TypeSystemAstBuilder CreateAstBuilder() astBuilder.SupportInitAccessors = (ConversionFlags & ConversionFlags.SupportInitAccessors) != 0; astBuilder.SupportRecordClasses = (ConversionFlags & ConversionFlags.SupportRecordClasses) != 0; astBuilder.SupportRecordStructs = (ConversionFlags & ConversionFlags.SupportRecordStructs) != 0; + astBuilder.SupportUnsignedRightShift = (ConversionFlags & ConversionFlags.SupportUnsignedRightShift) != 0; return astBuilder; } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 5cd4bd8da0..9a4788f704 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -826,6 +826,7 @@ public virtual void VisitBinaryOperatorExpression(BinaryOperatorExpression binar break; case BinaryOperatorType.ShiftLeft: case BinaryOperatorType.ShiftRight: + case BinaryOperatorType.UnsignedShiftRight: spacePolicy = policy.SpaceAroundShiftOperator; break; case BinaryOperatorType.NullCoalescing: @@ -2565,9 +2566,9 @@ public virtual void VisitParameterDeclaration(ParameterDeclaration parameterDecl WriteKeyword(ParameterDeclaration.ThisModifierRole); Space(); } - if (parameterDeclaration.IsRefScoped) + if (parameterDeclaration.IsScopedRef) { - WriteKeyword(ParameterDeclaration.RefScopedRole); + WriteKeyword(ParameterDeclaration.ScopedRefRole); Space(); } switch (parameterDeclaration.ParameterModifier) @@ -2589,11 +2590,6 @@ public virtual void VisitParameterDeclaration(ParameterDeclaration parameterDecl Space(); break; } - if (parameterDeclaration.IsValueScoped) - { - WriteKeyword(ParameterDeclaration.ValueScopedRole); - Space(); - } parameterDeclaration.Type.AcceptVisitor(this); if (!parameterDeclaration.Type.IsNull && !string.IsNullOrEmpty(parameterDeclaration.Name)) { @@ -2835,6 +2831,7 @@ public virtual void VisitPrimitiveType(PrimitiveType primitiveType) { StartNode(primitiveType); writer.WritePrimitiveType(primitiveType.Keyword); + isAfterSpace = false; EndNode(primitiveType); } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/GenericGrammarAmbiguityVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/GenericGrammarAmbiguityVisitor.cs index 74e99d8c07..e7f372f96d 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/GenericGrammarAmbiguityVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/GenericGrammarAmbiguityVisitor.cs @@ -92,6 +92,9 @@ public override bool VisitBinaryOperatorExpression(BinaryOperatorExpression bina case BinaryOperatorType.ShiftRight when genericNestingLevel >= 2: genericNestingLevel -= 2; break; + case BinaryOperatorType.UnsignedShiftRight when genericNestingLevel >= 3: + genericNestingLevel -= 3; + break; default: return true; // stop visiting, no ambiguity found } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs index e279cf021c..2fb3a45460 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs @@ -122,6 +122,7 @@ static PrecedenceLevel GetPrecedence(Expression expr) return PrecedenceLevel.Additive; case BinaryOperatorType.ShiftLeft: case BinaryOperatorType.ShiftRight: + case BinaryOperatorType.UnsignedShiftRight: return PrecedenceLevel.Shift; case BinaryOperatorType.GreaterThan: case BinaryOperatorType.GreaterThanOrEqual: diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs index ede737fd98..6da12dd60e 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs @@ -159,7 +159,10 @@ public override void WritePrimitiveValue(object value, LiteralFormat format = Li return; if (value is string) { - lastWritten = LastWritten.Other; + if (format == LiteralFormat.VerbatimStringLiteral) + lastWritten = LastWritten.KeywordOrIdentifier; + else + lastWritten = LastWritten.Other; } else if (value is char) { diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/TextWriterTokenWriter.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/TextWriterTokenWriter.cs index fc837a1637..e305cb98ed 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/TextWriterTokenWriter.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/TextWriterTokenWriter.cs @@ -269,6 +269,12 @@ public override void WritePrimitiveValue(object value, LiteralFormat format = Li textWriter.Write('"'); textWriter.Write(tmp); textWriter.Write('"'); + if (format == LiteralFormat.Utf8Literal) + { + textWriter.Write("u8"); + column += 2; + Length += 2; + } } else if (value is char) { diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs index d5542fb6ed..26c1a8a587 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs @@ -34,9 +34,8 @@ interface IProjectFileWriter /// /// The target to write to. /// The information about the project being created. - /// A collection of source files to be included into the project, each item is a pair - /// of the project entry type and the file path. + /// A collection of source files to be included into the project. /// The module being decompiled. - void Write(TextWriter target, IProjectInfoProvider project, IEnumerable<(string itemType, string fileName)> files, PEFile module); + void Write(TextWriter target, IProjectInfoProvider project, IEnumerable files, PEFile module); } } diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs index af98d6045f..eb383ba750 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs @@ -25,6 +25,7 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Solution; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { @@ -43,7 +44,7 @@ sealed class ProjectFileWriterDefault : IProjectFileWriter public void Write( TextWriter target, IProjectInfoProvider project, - IEnumerable<(string itemType, string fileName)> files, + IEnumerable files, PEFile module) { const string ns = "http://schemas.microsoft.com/developer/msbuild/2003"; @@ -162,13 +163,18 @@ public void Write( } w.WriteEndElement(); // (References) - foreach (IGrouping gr in from f in files group f.fileName by f.itemType into g orderby g.Key select g) + foreach (IGrouping gr in files.GroupBy(f => f.ItemType).OrderBy(g => g.Key)) { w.WriteStartElement("ItemGroup"); - foreach (string file in gr.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)) + foreach (var item in gr.OrderBy(f => f.FileName, StringComparer.OrdinalIgnoreCase)) { w.WriteStartElement(gr.Key); - w.WriteAttributeString("Include", file); + w.WriteAttributeString("Include", item.FileName); + if (item.AdditionalProperties != null) + { + foreach (var (key, value) in item.AdditionalProperties) + w.WriteAttributeString(key, value); + } w.WriteEndElement(); } w.WriteEndElement(); diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs index 61bb6d21f6..b5273f1d56 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs @@ -66,7 +66,7 @@ enum ProjectType { Default, WinForms, Wpf, Web } public void Write( TextWriter target, IProjectInfoProvider project, - IEnumerable<(string itemType, string fileName)> files, + IEnumerable files, PEFile module) { using (XmlTextWriter xmlWriter = new XmlTextWriter(target)) @@ -76,7 +76,7 @@ public void Write( } } - static void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable<(string itemType, string fileName)> files, PEFile module) + static void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable files, PEFile module) { xml.WriteStartElement("Project"); @@ -188,27 +188,27 @@ static void WriteProjectInfo(XmlTextWriter xml, IProjectInfoProvider project) } } - static void WriteMiscellaneousPropertyGroup(XmlTextWriter xml, IEnumerable<(string itemType, string fileName)> files) + static void WriteMiscellaneousPropertyGroup(XmlTextWriter xml, IEnumerable files) { - var (itemType, fileName) = files.FirstOrDefault(t => t.itemType == "ApplicationIcon"); + var (itemType, fileName) = files.FirstOrDefault(t => t.ItemType == "ApplicationIcon"); if (fileName != null) xml.WriteElementString("ApplicationIcon", fileName); - (itemType, fileName) = files.FirstOrDefault(t => t.itemType == "ApplicationManifest"); + (itemType, fileName) = files.FirstOrDefault(t => t.ItemType == "ApplicationManifest"); if (fileName != null) xml.WriteElementString("ApplicationManifest", fileName); - if (files.Any(t => t.itemType == "EmbeddedResource")) + if (files.Any(t => t.ItemType == "EmbeddedResource")) xml.WriteElementString("RootNamespace", string.Empty); // TODO: We should add CustomToolNamespace for resources, otherwise we should add empty RootNamespace } - static void WriteResources(XmlTextWriter xml, IEnumerable<(string itemType, string fileName)> files) + static void WriteResources(XmlTextWriter xml, IEnumerable files) { // remove phase - foreach (var (itemType, fileName) in files.Where(t => t.itemType == "EmbeddedResource")) + foreach (var item in files.Where(t => t.ItemType == "EmbeddedResource")) { - string buildAction = Path.GetExtension(fileName).ToUpperInvariant() switch { + string buildAction = Path.GetExtension(item.FileName).ToUpperInvariant() switch { ".CS" => "Compile", ".RESX" => "EmbeddedResource", _ => "None" @@ -217,18 +217,23 @@ static void WriteResources(XmlTextWriter xml, IEnumerable<(string itemType, stri continue; xml.WriteStartElement(buildAction); - xml.WriteAttributeString("Remove", fileName); + xml.WriteAttributeString("Remove", item.FileName); xml.WriteEndElement(); } // include phase - foreach (var (itemType, fileName) in files.Where(t => t.itemType == "EmbeddedResource")) + foreach (var item in files.Where(t => t.ItemType == "EmbeddedResource")) { - if (Path.GetExtension(fileName) == ".resx") + if (Path.GetExtension(item.FileName) == ".resx") continue; xml.WriteStartElement("EmbeddedResource"); - xml.WriteAttributeString("Include", fileName); + xml.WriteAttributeString("Include", item.FileName); + if (item.AdditionalProperties != null) + { + foreach (var (key, value) in item.AdditionalProperties) + xml.WriteAttributeString(key, value); + } xml.WriteEndElement(); } } diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 3057cbb6f7..4dd22ee8fd 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -32,6 +32,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; @@ -150,8 +151,8 @@ protected WholeProjectDecompiler( TargetDirectory = targetDirectory; directories.Clear(); var resources = WriteResourceFilesInProject(moduleDefinition).ToList(); - var files = WriteCodeFilesInProject(moduleDefinition, resources.SelectMany(r => r.partialTypes ?? Enumerable.Empty()).ToList(), cancellationToken).ToList(); - files.AddRange(resources.Select(r => (r.itemType, r.fileName))); + var files = WriteCodeFilesInProject(moduleDefinition, resources.SelectMany(r => r.PartialTypes ?? Enumerable.Empty()).ToList(), cancellationToken).ToList(); + files.AddRange(resources); files.AddRange(WriteMiscellaneousFilesInProject(moduleDefinition)); if (StrongNameKeyFile != null) { @@ -169,9 +170,13 @@ protected virtual bool IncludeTypeWhenDecompilingProject(PEFile module, TypeDefi { var metadata = module.Metadata; var typeDef = metadata.GetTypeDefinition(type); - if (metadata.GetString(typeDef.Name) == "" || CSharpDecompiler.MemberIsHidden(module, type, Settings)) + string name = metadata.GetString(typeDef.Name); + string ns = metadata.GetString(typeDef.Namespace); + if (name == "" || CSharpDecompiler.MemberIsHidden(module, type, Settings)) return false; - if (metadata.GetString(typeDef.Namespace) == "XamlGeneratedNamespace" && metadata.GetString(typeDef.Name) == "GeneratedInternalTypeHelper") + if (ns == "XamlGeneratedNamespace" && name == "GeneratedInternalTypeHelper") + return false; + if (!typeDef.IsNested && RemoveEmbeddedAttributes.attributeNames.Contains(ns + "." + name)) return false; return true; } @@ -185,7 +190,7 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) return decompiler; } - IEnumerable<(string itemType, string fileName)> WriteAssemblyInfo(DecompilerTypeSystem ts, CancellationToken cancellationToken) + IEnumerable WriteAssemblyInfo(DecompilerTypeSystem ts, CancellationToken cancellationToken) { var decompiler = CreateDecompiler(ts); decompiler.CancellationToken = cancellationToken; @@ -200,43 +205,63 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) { syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions)); } - return new[] { ("Compile", assemblyInfo) }; + return new[] { new ProjectItemInfo("Compile", assemblyInfo) }; } - IEnumerable<(string itemType, string fileName)> WriteCodeFilesInProject(Metadata.PEFile module, IList partialTypes, CancellationToken cancellationToken) + IEnumerable WriteCodeFilesInProject(Metadata.PEFile module, IList partialTypes, CancellationToken cancellationToken) { var metadata = module.Metadata; - var files = module.Metadata.GetTopLevelTypeDefinitions().Where(td => IncludeTypeWhenDecompilingProject(module, td)).GroupBy( - delegate (TypeDefinitionHandle h) { - var type = metadata.GetTypeDefinition(h); - string file = CleanUpFileName(metadata.GetString(type.Name)) + ".cs"; - string ns = metadata.GetString(type.Namespace); - if (string.IsNullOrEmpty(ns)) - { - return file; - } - else - { - string dir = Settings.UseNestedDirectoriesForNamespaces ? CleanUpPath(ns) : CleanUpDirectoryName(ns); - if (directories.Add(dir)) - Directory.CreateDirectory(Path.Combine(TargetDirectory, dir)); - return Path.Combine(dir, file); - } - }, StringComparer.OrdinalIgnoreCase).ToList(); - int total = files.Count; - var progress = ProgressIndicator; + var files = module.Metadata.GetTopLevelTypeDefinitions().Where(td => IncludeTypeWhenDecompilingProject(module, td)) + .GroupBy(GetFileFileNameForHandle, StringComparer.OrdinalIgnoreCase).ToList(); + var progressReporter = ProgressIndicator; + var progress = new DecompilationProgress { TotalUnits = files.Count, Title = "Exporting project..." }; DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, Settings); - Parallel.ForEach( - Partitioner.Create(files, loadBalance: true), - new ParallelOptions { - MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, - CancellationToken = cancellationToken - }, - delegate (IGrouping file) { - using (StreamWriter w = new StreamWriter(Path.Combine(TargetDirectory, file.Key))) - { + var workList = new HashSet(); + var processedTypes = new HashSet(); + ProcessFiles(files); + while (workList.Count > 0) + { + var additionalFiles = workList + .GroupBy(GetFileFileNameForHandle, StringComparer.OrdinalIgnoreCase).ToList(); + workList.Clear(); + ProcessFiles(additionalFiles); + files.AddRange(additionalFiles); + progress.TotalUnits = files.Count; + } + + return files.Select(f => new ProjectItemInfo("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, cancellationToken)); + + string GetFileFileNameForHandle(TypeDefinitionHandle h) + { + var type = metadata.GetTypeDefinition(h); + string file = SanitizeFileName(metadata.GetString(type.Name) + ".cs"); + string ns = metadata.GetString(type.Namespace); + if (string.IsNullOrEmpty(ns)) + { + return file; + } + else + { + string dir = Settings.UseNestedDirectoriesForNamespaces ? CleanUpPath(ns) : CleanUpDirectoryName(ns); + if (directories.Add(dir)) + Directory.CreateDirectory(Path.Combine(TargetDirectory, dir)); + return Path.Combine(dir, file); + } + } + + void ProcessFiles(List> files) + { + processedTypes.AddRange(files.SelectMany(f => f)); + Parallel.ForEach( + Partitioner.Create(files, loadBalance: true), + new ParallelOptions { + MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, + CancellationToken = cancellationToken + }, + delegate (IGrouping file) { try { + using StreamWriter w = new StreamWriter(Path.Combine(TargetDirectory, file.Key)); CSharpDecompiler decompiler = CreateDecompiler(ts); foreach (var partialType in partialTypes) @@ -245,22 +270,43 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) } decompiler.CancellationToken = cancellationToken; - var syntaxTree = decompiler.DecompileTypes(file.ToArray()); + var declaredTypes = file.ToArray(); + var syntaxTree = decompiler.DecompileTypes(declaredTypes); + + foreach (var node in syntaxTree.Descendants) + { + var td = (node.GetResolveResult() as TypeResolveResult)?.Type.GetDefinition(); + if (td?.ParentModule != ts.MainModule) + continue; + while (td?.DeclaringTypeDefinition != null) + { + td = td.DeclaringTypeDefinition; + } + if (td != null && td.MetadataToken is { IsNil: false } token && !processedTypes.Contains((TypeDefinitionHandle)token)) + { + lock (workList) + { + workList.Add((TypeDefinitionHandle)token); + } + } + } + syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions)); } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) { throw new DecompilerException(module, $"Error decompiling for '{file.Key}'", innerException); } - } - progress?.Report(new DecompilationProgress(total, file.Key)); - }); - return files.Select(f => ("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, cancellationToken)); + progress.Status = file.Key; + Interlocked.Increment(ref progress.UnitsCompleted); + progressReporter?.Report(progress); + }); + } } #endregion #region WriteResourceFilesInProject - protected virtual IEnumerable<(string itemType, string fileName, List partialTypes)> WriteResourceFilesInProject(Metadata.PEFile module) + protected virtual IEnumerable WriteResourceFilesInProject(Metadata.PEFile module) { foreach (var r in module.Resources.Where(r => r.ResourceType == ResourceType.Embedded)) { @@ -270,7 +316,7 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) if (r.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { bool decodedIntoIndividualFiles; - var individualResources = new List<(string itemType, string fileName, List partialTypes)>(); + var individualResources = new List(); try { var resourcesFile = new ResourcesFile(stream); @@ -330,12 +376,12 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) stream.Position = 0; stream.CopyTo(fs); } - yield return ("EmbeddedResource", fileName, null); + yield return new ProjectItemInfo("EmbeddedResource", fileName).With("LogicalName", r.Name); } } } - protected virtual IEnumerable<(string itemType, string fileName, List partialTypes)> WriteResourceToFile(string fileName, string resourceName, Stream entryStream) + protected virtual IEnumerable WriteResourceToFile(string fileName, string resourceName, Stream entryStream) { if (fileName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { @@ -350,7 +396,7 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) writer.AddResource(entry.Key, entry.Value); } } - return new[] { ("EmbeddedResource", resx, (List)null) }; + return new[] { new ProjectItemInfo("EmbeddedResource", resx).With("LogicalName", resourceName) }; } catch (BadImageFormatException) { @@ -365,7 +411,7 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) { entryStream.CopyTo(fs); } - return new[] { ("EmbeddedResource", fileName, (List)null) }; + return new[] { new ProjectItemInfo("EmbeddedResource", fileName).With("LogicalName", resourceName) }; } string GetFileNameForResource(string fullName) @@ -378,7 +424,7 @@ string GetFileNameForResource(string fullName) // the directory part Namespace1\Namespace2\... reuses as many existing directories as // possible, and only the remaining name parts are used as prefix for the filename. // This is not affected by the UseNestedDirectoriesForNamespaces setting. - string[] splitName = fullName.Split(Path.DirectorySeparatorChar); + string[] splitName = fullName.Split('\\', '/'); string fileName = string.Join(".", splitName); string separator = Path.DirectorySeparatorChar.ToString(); for (int i = splitName.Length - 1; i > 0; i--) @@ -396,7 +442,7 @@ string GetFileNameForResource(string fullName) #endregion #region WriteMiscellaneousFilesInProject - protected virtual IEnumerable<(string itemType, string fileName)> WriteMiscellaneousFilesInProject(PEFile module) + protected virtual IEnumerable WriteMiscellaneousFilesInProject(PEFile module) { var resources = module.Reader.ReadWin32Resources(); if (resources == null) @@ -406,21 +452,21 @@ string GetFileNameForResource(string fullName) if (appIcon != null) { File.WriteAllBytes(Path.Combine(TargetDirectory, "app.ico"), appIcon); - yield return ("ApplicationIcon", "app.ico"); + yield return new ProjectItemInfo("ApplicationIcon", "app.ico"); } byte[] appManifest = CreateApplicationManifest(resources); if (appManifest != null && !IsDefaultApplicationManifest(appManifest)) { File.WriteAllBytes(Path.Combine(TargetDirectory, "app.manifest"), appManifest); - yield return ("ApplicationManifest", "app.manifest"); + yield return new ProjectItemInfo("ApplicationManifest", "app.manifest"); } var appConfig = module.FileName + ".config"; if (File.Exists(appConfig)) { File.Copy(appConfig, Path.Combine(TargetDirectory, "app.config"), overwrite: true); - yield return ("ApplicationConfig", Path.GetFileName(appConfig)); + yield return new ProjectItemInfo("ApplicationConfig", Path.GetFileName(appConfig)); } } @@ -570,10 +616,8 @@ public static string SanitizeFileName(string fileName) /// static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName) { + // Remove anything that could be confused with a rooted path. int pos = text.IndexOf(':'); - if (pos > 0) - text = text.Substring(0, pos); - pos = text.IndexOf('`'); if (pos > 0) text = text.Substring(0, pos); text = text.Trim(); @@ -604,6 +648,12 @@ static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName } } } + // Remove generics + pos = text.IndexOf('`'); + if (pos > 0) + { + text = text.Substring(0, pos).Trim(); + } // Whitelist allowed characters, replace everything else: StringBuilder b = new StringBuilder(text.Length + (extension?.Length ?? 0)); foreach (var c in text) @@ -645,7 +695,15 @@ static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName b.Append('-'); string name = b.ToString(); if (extension != null) + { + // make sure that adding the extension to the filename + // does not exceed maxSegmentLength. + // trim the name, if necessary. + if (name.Length + extension.Length > maxSegmentLength) + name = name.Remove(name.Length - extension.Length); name += extension; + } + if (IsReservedFileSystemName(name)) return name + "_"; else if (name == ".") @@ -706,15 +764,24 @@ public static bool CanUseSdkStyleProjectFormat(PEFile module) } } - public readonly struct DecompilationProgress + public record struct ProjectItemInfo(string ItemType, string FileName) { - public readonly int TotalNumberOfFiles; - public readonly string Status; + public List PartialTypes { get; set; } = null; + + public Dictionary AdditionalProperties { get; set; } = null; + + public ProjectItemInfo With(string name, string value) + { + AdditionalProperties ??= new Dictionary(); + AdditionalProperties.Add(name, value); + return this; + } - public DecompilationProgress(int total, string status = null) + public ProjectItemInfo With(IEnumerable> pairs) { - this.TotalNumberOfFiles = total; - this.Status = status ?? ""; + AdditionalProperties ??= new Dictionary(); + AdditionalProperties.AddRange(pairs); + return this; } } } diff --git a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs index 455c8b21b6..6ddc23b437 100644 --- a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs @@ -53,8 +53,8 @@ public RecordDecompiler(IDecompilerTypeSystem dts, ITypeDefinition recordTypeDef this.settings = settings; this.cancellationToken = cancellationToken; this.baseClass = recordTypeDef.DirectBaseTypes.FirstOrDefault(b => b.Kind == TypeKind.Class); - this.isStruct = baseClass.IsKnownType(KnownTypeCode.ValueType); - this.isInheritedRecord = !isStruct && !baseClass.IsKnownType(KnownTypeCode.Object); + this.isStruct = baseClass?.IsKnownType(KnownTypeCode.ValueType) ?? false; + this.isInheritedRecord = !isStruct && !(baseClass?.IsKnownType(KnownTypeCode.Object) ?? false); this.isSealed = recordTypeDef.IsSealed; DetectAutomaticProperties(); this.orderedMembers = DetectMemberOrder(recordTypeDef, backingFieldToAutoProperty); @@ -292,7 +292,7 @@ public bool MethodIsGenerated(IMethod method) // virtual bool Equals(R? other): generated unless user-declared return IsGeneratedEquals(method); } - else if (isInheritedRecord && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(paramType, baseClass) && method.IsOverride) + else if (isInheritedRecord && baseClass != null && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(paramType, baseClass) && method.IsOverride) { // override bool Equals(BaseClass? obj): always generated return true; @@ -445,11 +445,23 @@ private bool IsGeneratedEqualityContract(IProperty property) var getter = property.Getter; if (!(getter != null && !property.CanSet)) return false; - if (property.GetAttributes().Any()) - return false; + var attrs = property.GetAttributes().ToList(); + switch (attrs.Count) + { + case 0: + // Roslyn 3.x does not emit a CompilerGeneratedAttribute on the property itself. + break; + case 1: + // Roslyn 4.4 started doing so. + if (!attrs[0].AttributeType.IsKnownType(KnownAttribute.CompilerGenerated)) + return false; + break; + default: + return false; + } if (getter.GetReturnTypeAttributes().Any()) return false; - var attrs = getter.GetAttributes().ToList(); + attrs = getter.GetAttributes().ToList(); if (attrs.Count != 1) return false; if (!attrs[0].AttributeType.IsKnownType(KnownAttribute.CompilerGenerated)) @@ -760,7 +772,7 @@ private bool IsGeneratedEquals(IMethod method) return false; if (!(conditions[pos] is Call { Method: { Name: "Equals" } } call)) return false; - if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(call.Method.DeclaringType, baseClass)) + if (baseClass != null && !NormalizeTypeVisitor.TypeErasure.EquivalentTypes(call.Method.DeclaringType, baseClass)) return false; if (call.Arguments.Count != 2) return false; diff --git a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs index 459a71a142..bdb77fab44 100644 --- a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs +++ b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs @@ -137,11 +137,13 @@ void CollectNamespaces(IEntity entity, MetadataModule module, CodeMappingInfo ma break; case IProperty property: HandleAttributes(property.GetAttributes()); + CollectNamespacesForTypeReference(property.ReturnType); CollectNamespaces(property.Getter, module, mappingInfo); CollectNamespaces(property.Setter, module, mappingInfo); break; case IEvent @event: HandleAttributes(@event.GetAttributes()); + CollectNamespacesForTypeReference(@event.ReturnType); CollectNamespaces(@event.AddAccessor, module, mappingInfo); CollectNamespaces(@event.RemoveAccessor, module, mappingInfo); break; diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs index b3f453157c..fff06f1343 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs @@ -674,6 +674,27 @@ public OperatorMethod[] ShiftRightOperators { } } } + + OperatorMethod[]? unsignedShiftRightOperators; + + public OperatorMethod[] UnsignedShiftRightOperators { + get { + OperatorMethod[]? ops = LazyInit.VolatileRead(ref unsignedShiftRightOperators); + if (ops != null) + { + return ops; + } + else + { + return LazyInit.GetOrSet(ref unsignedShiftRightOperators, Lift( + new LambdaBinaryOperatorMethod(this, (a, b) => (int)((uint)a >> b)), + new LambdaBinaryOperatorMethod(this, (a, b) => a >> b), + new LambdaBinaryOperatorMethod(this, (a, b) => (long)((ulong)a >> b)), + new LambdaBinaryOperatorMethod(this, (a, b) => a >> b) + )); + } + } + } #endregion #region Equality operators diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index 70b066dc82..f235e0a98a 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -685,7 +685,7 @@ public ResolveResult ResolveBinaryOperator(BinaryOperatorType op, ResolveResult { isNullable = true; } - if (op == BinaryOperatorType.ShiftLeft || op == BinaryOperatorType.ShiftRight) + if (op == BinaryOperatorType.ShiftLeft || op == BinaryOperatorType.ShiftRight || op == BinaryOperatorType.UnsignedShiftRight) { // special case: the shift operators allow "var x = null << null", producing int?. if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null) @@ -805,6 +805,9 @@ public ResolveResult ResolveBinaryOperator(BinaryOperatorType op, ResolveResult case BinaryOperatorType.ShiftRight: methodGroup = operators.ShiftRightOperators; break; + case BinaryOperatorType.UnsignedShiftRight: + methodGroup = operators.UnsignedShiftRightOperators; + break; case BinaryOperatorType.Equality: case BinaryOperatorType.InEquality: case BinaryOperatorType.LessThan: @@ -1223,14 +1226,11 @@ ResolveResult CastTo(IType targetType, bool isNullable, ResolveResult expression ResolveResult rr = ResolveCast(targetType, expression); if (rr.IsError) return rr; - Debug.Assert(rr.IsCompileTimeConstant); - return new ConstantResolveResult(nullableType, rr.ConstantValue); - } - else - { - return Convert(expression, nullableType, - isNullable ? Conversion.ImplicitNullableConversion : Conversion.ImplicitNumericConversion); + if (rr.IsCompileTimeConstant) + return new ConstantResolveResult(nullableType, rr.ConstantValue); } + return Convert(expression, nullableType, + isNullable ? Conversion.ImplicitNullableConversion : Conversion.ImplicitNumericConversion); } #endregion @@ -1259,6 +1259,8 @@ static string GetOverloadableOperatorName(BinaryOperatorType op) return "op_LeftShift"; case BinaryOperatorType.ShiftRight: return "op_RightShift"; + case BinaryOperatorType.UnsignedShiftRight: + return "op_UnsignedRightShift"; case BinaryOperatorType.Equality: return "op_Equality"; case BinaryOperatorType.InEquality: diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolutionErrors.cs b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolutionErrors.cs index 4ef9a7b983..a8fb31416d 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolutionErrors.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolutionErrors.cs @@ -82,6 +82,10 @@ public enum OverloadResolutionErrors /// /// This error does not prevent a candidate from being applicable. /// - MethodConstraintsNotSatisfied = 0x0800 + MethodConstraintsNotSatisfied = 0x0800, + /// + /// Using 'out var' instead of 'out T' would result in loss of type information. + /// + OutVarTypeMismatch = 0x1000, } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/CSharpModifierToken.cs b/ICSharpCode.Decompiler/CSharp/Syntax/CSharpModifierToken.cs index b81a7d76b4..fbe31464dc 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/CSharpModifierToken.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/CSharpModifierToken.cs @@ -66,8 +66,8 @@ protected internal override bool DoMatch(AstNode other, PatternMatching.Match ma Modifiers.Public, Modifiers.Private, Modifiers.Protected, Modifiers.Internal, Modifiers.New, Modifiers.Unsafe, - Modifiers.Abstract, Modifiers.Virtual, Modifiers.Sealed, Modifiers.Static, Modifiers.Override, - Modifiers.Readonly, Modifiers.Volatile, + Modifiers.Static, Modifiers.Abstract, Modifiers.Virtual, Modifiers.Sealed, Modifiers.Override, + Modifiers.Required, Modifiers.Readonly, Modifiers.Volatile, Modifiers.Ref, Modifiers.Extern, Modifiers.Partial, Modifiers.Const, Modifiers.Async, @@ -119,6 +119,8 @@ public static string GetModifierName(Modifiers modifier) return "async"; case Modifiers.Ref: return "ref"; + case Modifiers.Required: + return "required"; case Modifiers.Any: // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST return "any"; @@ -129,50 +131,7 @@ public static string GetModifierName(Modifiers modifier) public static int GetModifierLength(Modifiers modifier) { - switch (modifier) - { - case Modifiers.Private: - return "private".Length; - case Modifiers.Internal: - return "internal".Length; - case Modifiers.Protected: - return "protected".Length; - case Modifiers.Public: - return "public".Length; - case Modifiers.Abstract: - return "abstract".Length; - case Modifiers.Virtual: - return "virtual".Length; - case Modifiers.Sealed: - return "sealed".Length; - case Modifiers.Static: - return "static".Length; - case Modifiers.Override: - return "override".Length; - case Modifiers.Readonly: - return "readonly".Length; - case Modifiers.Const: - return "const".Length; - case Modifiers.New: - return "new".Length; - case Modifiers.Partial: - return "partial".Length; - case Modifiers.Extern: - return "extern".Length; - case Modifiers.Volatile: - return "volatile".Length; - case Modifiers.Unsafe: - return "unsafe".Length; - case Modifiers.Async: - return "async".Length; - case Modifiers.Ref: - return "ref".Length; - case Modifiers.Any: - // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST - return "any".Length; - default: - throw new NotSupportedException("Invalid value for Modifiers"); - } + return GetModifierName(modifier).Length; } public static Modifiers GetModifierValue(string modifier) @@ -215,6 +174,8 @@ public static Modifiers GetModifierValue(string modifier) return Modifiers.Async; case "ref": return Modifiers.Ref; + case "required": + return Modifiers.Required; case "any": // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST return Modifiers.Any; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/AssignmentExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/AssignmentExpression.cs index acd1038a87..378b33bfa5 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/AssignmentExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/AssignmentExpression.cs @@ -47,6 +47,7 @@ public class AssignmentExpression : Expression public readonly static TokenRole ModulusRole = new TokenRole("%="); public readonly static TokenRole ShiftLeftRole = new TokenRole("<<="); public readonly static TokenRole ShiftRightRole = new TokenRole(">>="); + public readonly static TokenRole UnsignedShiftRightRole = new TokenRole(">>>="); public readonly static TokenRole BitwiseAndRole = new TokenRole("&="); public readonly static TokenRole BitwiseOrRole = new TokenRole("|="); public readonly static TokenRole ExclusiveOrRole = new TokenRole("^="); @@ -129,6 +130,8 @@ public static TokenRole GetOperatorRole(AssignmentOperatorType op) return ShiftLeftRole; case AssignmentOperatorType.ShiftRight: return ShiftRightRole; + case AssignmentOperatorType.UnsignedShiftRight: + return UnsignedShiftRightRole; case AssignmentOperatorType.BitwiseAnd: return BitwiseAndRole; case AssignmentOperatorType.BitwiseOr: @@ -164,6 +167,8 @@ public static TokenRole GetOperatorRole(AssignmentOperatorType op) return BinaryOperatorType.ShiftLeft; case AssignmentOperatorType.ShiftRight: return BinaryOperatorType.ShiftRight; + case AssignmentOperatorType.UnsignedShiftRight: + return BinaryOperatorType.UnsignedShiftRight; case AssignmentOperatorType.BitwiseAnd: return BinaryOperatorType.BitwiseAnd; case AssignmentOperatorType.BitwiseOr: @@ -195,6 +200,8 @@ public static ExpressionType GetLinqNodeType(AssignmentOperatorType op, bool che return ExpressionType.LeftShiftAssign; case AssignmentOperatorType.ShiftRight: return ExpressionType.RightShiftAssign; + case AssignmentOperatorType.UnsignedShiftRight: + return ExpressionType.Extension; case AssignmentOperatorType.BitwiseAnd: return ExpressionType.AndAssign; case AssignmentOperatorType.BitwiseOr: @@ -259,6 +266,8 @@ public enum AssignmentOperatorType ShiftLeft, /// left >>= right ShiftRight, + /// left >>>= right + UnsignedShiftRight, /// left &= right BitwiseAnd, diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs index f64752e063..1386e1029c 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs @@ -52,6 +52,7 @@ public class BinaryOperatorExpression : Expression public readonly static TokenRole ModulusRole = new TokenRole("%"); public readonly static TokenRole ShiftLeftRole = new TokenRole("<<"); public readonly static TokenRole ShiftRightRole = new TokenRole(">>"); + public readonly static TokenRole UnsignedShiftRightRole = new TokenRole(">>>"); public readonly static TokenRole NullCoalescingRole = new TokenRole("??"); public readonly static TokenRole RangeRole = new TokenRole(".."); public readonly static TokenRole IsKeywordRole = IsExpression.IsKeywordRole; @@ -151,6 +152,8 @@ public static TokenRole GetOperatorRole(BinaryOperatorType op) return ShiftLeftRole; case BinaryOperatorType.ShiftRight: return ShiftRightRole; + case BinaryOperatorType.UnsignedShiftRight: + return UnsignedShiftRightRole; case BinaryOperatorType.NullCoalescing: return NullCoalescingRole; case BinaryOperatorType.Range: @@ -205,6 +208,7 @@ public static ExpressionType GetLinqNodeType(BinaryOperatorType op, bool checkFo case BinaryOperatorType.NullCoalescing: return ExpressionType.Coalesce; case BinaryOperatorType.Range: + case BinaryOperatorType.UnsignedShiftRight: return ExpressionType.Extension; default: throw new NotSupportedException("Invalid value for BinaryOperatorType"); @@ -262,6 +266,8 @@ public enum BinaryOperatorType ShiftLeft, /// left >> right ShiftRight, + /// left >>> right + UnsignedShiftRight, /// left ?? right NullCoalescing, diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/PrimitiveExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/PrimitiveExpression.cs index 1e2138338a..a1e8fcc3b8 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/PrimitiveExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/PrimitiveExpression.cs @@ -42,6 +42,7 @@ public enum LiteralFormat : byte StringLiteral, VerbatimStringLiteral, CharLiteral, + Utf8Literal, } /// diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Modifiers.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Modifiers.cs index 98464b09cf..43804b1cbf 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Modifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Modifiers.cs @@ -55,6 +55,7 @@ public enum Modifiers Unsafe = 0x8000, Async = 0x10000, Ref = 0x20000, + Required = 0x40000, VisibilityMask = Private | Internal | Protected | Public, diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs index c0f8236b88..4d8677ca7d 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs @@ -57,6 +57,7 @@ public enum OperatorType ExclusiveOr, LeftShift, RightShift, + UnsignedRightShift, Equality, Inequality, GreaterThan, @@ -94,6 +95,7 @@ public class OperatorDeclaration : EntityDeclaration public static readonly TokenRole ExclusiveOrRole = new TokenRole("^"); public static readonly TokenRole LeftShiftRole = new TokenRole("<<"); public static readonly TokenRole RightShiftRole = new TokenRole(">>"); + public static readonly TokenRole UnsignedRightShiftRole = new TokenRole(">>>"); public static readonly TokenRole EqualityRole = new TokenRole("=="); public static readonly TokenRole InequalityRole = new TokenRole("!="); public static readonly TokenRole GreaterThanRole = new TokenRole(">"); @@ -127,6 +129,7 @@ static OperatorDeclaration() names[(int)OperatorType.ExclusiveOr] = new string[] { "^", "op_ExclusiveOr" }; names[(int)OperatorType.LeftShift] = new string[] { "<<", "op_LeftShift" }; names[(int)OperatorType.RightShift] = new string[] { ">>", "op_RightShift" }; + names[(int)OperatorType.UnsignedRightShift] = new string[] { ">>>", "op_UnsignedRightShift" }; names[(int)OperatorType.Equality] = new string[] { "==", "op_Equality" }; names[(int)OperatorType.Inequality] = new string[] { "!=", "op_Inequality" }; names[(int)OperatorType.GreaterThan] = new string[] { ">", "op_GreaterThan" }; @@ -230,6 +233,8 @@ public static TokenRole GetRole(OperatorType type) return LeftShiftRole; case OperatorType.RightShift: return RightShiftRole; + case OperatorType.UnsignedRightShift: + return UnsignedRightShiftRole; case OperatorType.Equality: return EqualityRole; case OperatorType.Inequality: diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs index 8e1dca003e..a86945d665 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs @@ -26,6 +26,8 @@ #nullable enable +using System; + namespace ICSharpCode.Decompiler.CSharp.Syntax { public enum ParameterModifier @@ -42,10 +44,13 @@ public class ParameterDeclaration : AstNode { public static readonly Role AttributeRole = EntityDeclaration.AttributeRole; public static readonly TokenRole ThisModifierRole = new TokenRole("this"); - public static readonly TokenRole RefScopedRole = new TokenRole("scoped"); + public static readonly TokenRole ScopedRefRole = new TokenRole("scoped"); + [Obsolete("Renamed to ScopedRefRole")] + public static readonly TokenRole RefScopedRole = ScopedRefRole; public static readonly TokenRole RefModifierRole = new TokenRole("ref"); public static readonly TokenRole OutModifierRole = new TokenRole("out"); public static readonly TokenRole InModifierRole = new TokenRole("in"); + [Obsolete("C# 11 preview: \"ref scoped\" no longer supported")] public static readonly TokenRole ValueScopedRole = new TokenRole("scoped"); public static readonly TokenRole ParamsModifierRole = new TokenRole("params"); @@ -102,7 +107,7 @@ public AstNodeCollection Attributes { } bool hasThisModifier; - bool isRefScoped, isValueScoped; + bool isScopedRef; public CSharpTokenNode ThisKeyword { get { @@ -122,22 +127,29 @@ public bool HasThisModifier { } } - public bool IsRefScoped { - get { return isRefScoped; } + public bool IsScopedRef { + get { return isScopedRef; } set { ThrowIfFrozen(); - isRefScoped = value; + isScopedRef = value; } } - public bool IsValueScoped { - get { return isValueScoped; } + [Obsolete("Renamed to IsScopedRef")] + public bool IsRefScoped { + get { return isScopedRef; } set { ThrowIfFrozen(); - isValueScoped = value; + isScopedRef = value; } } + [Obsolete("C# 11 preview: \"ref scoped\" no longer supported")] + public bool IsValueScoped { + get { return false; } + set { } + } + ParameterModifier parameterModifier; public ParameterModifier ParameterModifier { diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 3b57da6523..17b8967102 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -225,6 +225,11 @@ void InitProperties() /// public bool SupportRecordStructs { get; set; } + /// + /// Controls whether C# 11 "operator >>>" is supported. + /// + public bool SupportUnsignedRightShift { get; set; } + /// /// Controls whether all fully qualified type names should be prefixed with "global::". /// @@ -1654,8 +1659,7 @@ public ParameterDeclaration ConvertParameter(IParameter parameter) { decl.ParameterModifier = ParameterModifier.Params; } - decl.IsRefScoped = parameter.Lifetime.RefScoped; - decl.IsValueScoped = parameter.Lifetime.ValueScoped; + decl.IsScopedRef = parameter.Lifetime.ScopedRef; if (ShowAttributes) { decl.Attributes.AddRange(ConvertAttributes(parameter.GetAttributes())); @@ -1675,7 +1679,7 @@ public ParameterDeclaration ConvertParameter(IParameter parameter) { decl.Name = parameter.Name; } - if (parameter.IsOptional && parameter.HasConstantValueInSignature && this.ShowConstantValues) + if (parameter.IsOptional && decl.ParameterModifier is ParameterModifier.None or ParameterModifier.In && parameter.HasConstantValueInSignature && this.ShowConstantValues) { try { @@ -1970,6 +1974,10 @@ FieldDeclaration ConvertField(IField field) decl.AddAnnotation(new MemberResolveResult(null, field)); } decl.ReturnType = ConvertType(field.ReturnType); + if (decl.ReturnType is ComposedType ct && ct.HasRefSpecifier && field.ReturnTypeIsRefReadOnly) + { + ct.HasReadOnlySpecifier = true; + } Expression initializer = null; if (field.IsConst && this.ShowConstantValues) { @@ -2214,6 +2222,8 @@ EntityDeclaration ConvertOperator(IMethod op) OperatorType? opType = OperatorDeclaration.GetOperatorType(op.Name); if (opType == null) return ConvertMethod(op); + if (opType == OperatorType.UnsignedRightShift && !SupportUnsignedRightShift) + return ConvertMethod(op); OperatorDeclaration decl = new OperatorDeclaration(); decl.Modifiers = GetMemberModifiers(op); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 4eda0ba102..b17960e36f 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -281,7 +281,7 @@ private static bool IsValidInStatementExpression(Expression expr) void FindInsertionPoints(AstNode node, int nodeLevel) { BlockContainer scope = node.Annotation(); - if (scope != null && (scope.EntryPoint.IncomingEdgeCount > 1 || scope.Parent is ILFunction)) + if (scope != null && IsRelevantScope(scope)) { // track loops and function bodies as scopes, for comparison with CaptureScope. scopeTracking.Add((new InsertionPoint { level = nodeLevel, nextNode = node }, scope)); @@ -316,9 +316,14 @@ void FindInsertionPointForVariable(ILVariable variable) { InsertionPoint newPoint; int startIndex = scopeTracking.Count - 1; - if (variable.CaptureScope != null && startIndex > 0 && variable.CaptureScope != scopeTracking[startIndex].Scope) + BlockContainer captureScope = variable.CaptureScope; + while (captureScope != null && !IsRelevantScope(captureScope)) { - while (startIndex > 0 && scopeTracking[startIndex].Scope != variable.CaptureScope) + captureScope = BlockContainer.FindClosestContainer(captureScope.Parent); + } + if (captureScope != null && startIndex > 0 && captureScope != scopeTracking[startIndex].Scope) + { + while (startIndex > 0 && scopeTracking[startIndex].Scope != captureScope) startIndex--; newPoint = scopeTracking[startIndex + 1].InsertionPoint; } @@ -366,6 +371,11 @@ void FindInsertionPointForVariable(ILVariable variable) } } + private static bool IsRelevantScope(BlockContainer scope) + { + return scope.EntryPoint.IncomingEdgeCount > 1 || scope.Parent is ILFunction; + } + internal static bool VariableNeedsDeclaration(VariableKind kind) { switch (kind) @@ -466,6 +476,7 @@ void ResolveCollisions() Debug.Assert(point1.level == point2.level); if (point1.nextNode.Parent == point2.nextNode.Parent) { + Debug.Assert(prev.Type.Equals(v.Type)); // We found a collision! v.InvolvedInCollision = true; prev.ReplacementDueToCollision = v; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs index 6ba6b2e8ef..2b1dc7f1d7 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs @@ -16,10 +16,12 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Collections.Generic; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.Semantics; +using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.CSharp.Transforms { @@ -151,4 +153,41 @@ public void Run(AstNode rootNode, TransformContext context) } } } + + /// + /// This transform is used to remove attributes that are embedded + /// + public class RemoveEmbeddedAttributes : DepthFirstAstVisitor, IAstTransform + { + internal static readonly HashSet attributeNames = new HashSet() { + "System.Runtime.CompilerServices.IsReadOnlyAttribute", + "System.Runtime.CompilerServices.IsByRefLikeAttribute", + "System.Runtime.CompilerServices.IsUnmanagedAttribute", + "System.Runtime.CompilerServices.NullableAttribute", + "System.Runtime.CompilerServices.NullableContextAttribute", + "System.Runtime.CompilerServices.NativeIntegerAttribute", + "System.Runtime.CompilerServices.RefSafetyRulesAttribute", + "System.Runtime.CompilerServices.ScopedRefAttribute", + "Microsoft.CodeAnalysis.EmbeddedAttribute", + }; + + public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) + { + var typeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition; + if (typeDefinition == null || !attributeNames.Contains(typeDefinition.FullName)) + return; + if (!typeDefinition.HasAttribute(KnownAttribute.Embedded)) + return; + if (typeDeclaration.Parent is NamespaceDeclaration ns && ns.Members.Count == 1) + ns.Remove(); + else + typeDeclaration.Remove(); + } + + public void Run(AstNode rootNode, TransformContext context) + { + rootNode.AcceptVisitor(this); + } + } + } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index ca35ea9464..f9a6050123 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -763,12 +763,17 @@ Identifier ReplaceEventFieldAnnotation(Identifier identifier) var field = mrr?.Member as IField; if (field == null) return null; - var @event = field.DeclaringType.GetEvents(ev => ev.Name == field.Name, GetMemberOptions.IgnoreInheritedMembers).SingleOrDefault(); - if (@event != null && currentMethod.AccessorOwner != @event) + foreach (var ev in field.DeclaringType.GetEvents(null, GetMemberOptions.IgnoreInheritedMembers)) { - parent.RemoveAnnotations(); - parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, @event)); - return identifier; + if (CSharpDecompiler.IsEventBackingFieldName(field.Name, ev.Name, out int suffixLength) && + currentMethod.AccessorOwner != ev) + { + parent.RemoveAnnotations(); + parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, ev)); + if (suffixLength != 0) + identifier.Name = identifier.Name.Substring(0, identifier.Name.Length - suffixLength); + return identifier; + } } return null; } @@ -911,11 +916,11 @@ bool CheckAutomaticEventMatch(Match m, CustomEventDeclaration ev, bool isAddAcce switch (fieldExpression) { case IdentifierExpression identifier: - if (identifier.Identifier != ev.Name) + if (!CSharpDecompiler.IsEventBackingFieldName(identifier.Identifier, ev.Name, out _)) return false; break; case MemberReferenceExpression memberRef: - if (memberRef.MemberName != ev.Name) + if (!CSharpDecompiler.IsEventBackingFieldName(memberRef.MemberName, ev.Name, out _)) return false; break; default: @@ -1013,7 +1018,7 @@ EventDeclaration TransformAutomaticEvents(CustomEventDeclaration ev) ed.CopyAnnotationsFrom(ev); var fieldDecl = ev.Parent?.Children.OfType() - .FirstOrDefault(fd => fd.Variables.Single().Name == ev.Name); + .FirstOrDefault(fd => CSharpDecompiler.IsEventBackingFieldName(fd.Variables.Single().Name, ev.Name, out _)); if (fieldDecl != null) { fieldDecl.Remove(); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs index d2ae9b2d5a..ae490e1d78 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs @@ -19,6 +19,7 @@ using System; using System.Linq; +using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.TypeSystem; @@ -44,10 +45,19 @@ public override void VisitAssignmentExpression(AssignmentExpression assignment) { base.VisitAssignmentExpression(assignment); // Combine "x = x op y" into "x op= y" - BinaryOperatorExpression binary = assignment.Right as BinaryOperatorExpression; - if (binary != null && assignment.Operator == AssignmentOperatorType.Assign) + // Also supports "x = (T)(x op y)" -> "x op= y", if x.GetType() == T + // and y is implicitly convertible to T. + Expression rhs = assignment.Right; + IType expectedType = null; + if (assignment.Right is CastExpression { Type: var astType } cast) { - if (CanConvertToCompoundAssignment(assignment.Left) && assignment.Left.IsMatch(binary.Left)) + rhs = cast.Expression; + expectedType = astType.GetResolveResult().Type; + } + if (rhs is BinaryOperatorExpression binary && assignment.Operator == AssignmentOperatorType.Assign) + { + if (CanConvertToCompoundAssignment(assignment.Left) && assignment.Left.IsMatch(binary.Left) + && IsImplicitlyConvertible(binary.Right, expectedType)) { assignment.Operator = GetAssignmentOperatorForBinaryOperator(binary.Operator); if (assignment.Operator != AssignmentOperatorType.Assign) @@ -78,6 +88,15 @@ public override void VisitAssignmentExpression(AssignmentExpression assignment) } } } + + bool IsImplicitlyConvertible(Expression rhs, IType expectedType) + { + if (expectedType == null) + return true; + + var conversions = CSharpConversions.Get(context.TypeSystem); + return conversions.ImplicitConversion(rhs.GetResolveResult(), expectedType).IsImplicit; + } } public static AssignmentOperatorType GetAssignmentOperatorForBinaryOperator(BinaryOperatorType bop) @@ -98,6 +117,8 @@ public static AssignmentOperatorType GetAssignmentOperatorForBinaryOperator(Bina return AssignmentOperatorType.ShiftLeft; case BinaryOperatorType.ShiftRight: return AssignmentOperatorType.ShiftRight; + case BinaryOperatorType.UnsignedShiftRight: + return AssignmentOperatorType.UnsignedShiftRight; case BinaryOperatorType.BitwiseAnd: return AssignmentOperatorType.BitwiseAnd; case BinaryOperatorType.BitwiseOr: diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs index f2f7a355f3..738fcd46ab 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs @@ -142,7 +142,7 @@ void ProcessInvocationExpression(InvocationExpression invocationExpression) break; } - BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name); + BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name, context.Settings); if (bop != null && arguments.Length == 2) { invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression @@ -350,7 +350,7 @@ static bool ToStringIsKnownEffectFree(IType type) } } - static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name) + static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name, DecompilerSettings settings) { switch (name) { @@ -374,6 +374,8 @@ static bool ToStringIsKnownEffectFree(IType type) return BinaryOperatorType.ShiftLeft; case "op_RightShift": return BinaryOperatorType.ShiftRight; + case "op_UnsignedRightShift" when settings.UnsignedRightShift: + return BinaryOperatorType.UnsignedShiftRight; case "op_Equality": return BinaryOperatorType.Equality; case "op_Inequality": diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs index 82b4666c2f..bb32e2ac6f 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs @@ -315,9 +315,10 @@ void HandleStaticFieldInitializers(IEnumerable members) SRM.MethodDefinition ctorMethodDef = metadata.GetMethodDefinition((SRM.MethodDefinitionHandle)ctorMethod.MetadataToken); SRM.TypeDefinition declaringType = metadata.GetTypeDefinition(ctorMethodDef.GetDeclaringType()); bool declaringTypeIsBeforeFieldInit = declaringType.HasFlag(TypeAttributes.BeforeFieldInit); - while (true) + int pos = 0; + while (pos < staticCtor.Body.Statements.Count) { - ExpressionStatement es = staticCtor.Body.Statements.FirstOrDefault() as ExpressionStatement; + ExpressionStatement es = staticCtor.Body.Statements.ElementAtOrDefault(pos) as ExpressionStatement; if (es == null) break; AssignmentExpression assignment = es.Expression as AssignmentExpression; @@ -329,7 +330,8 @@ void HandleStaticFieldInitializers(IEnumerable members) // Only move fields that are constants, if the declaring type is not marked beforefieldinit. if (!declaringTypeIsBeforeFieldInit && fieldOrProperty is not IField { IsConst: true }) { - break; + pos++; + continue; } var fieldOrPropertyDecl = members.FirstOrDefault(f => f.GetSymbol() == fieldOrProperty) as EntityDeclaration; if (fieldOrPropertyDecl == null) diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 61e5eaf193..1350d21d3b 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -535,6 +535,17 @@ public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expres .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); } } + else if (type.Kind == TypeKind.Dynamic && targetType.IsReferenceType == true && !targetType.IsKnownType(KnownTypeCode.Object)) + { + // "static" conversion between dynamic and a reference type requires us to add a cast to object, + // otherwise recompilation would produce a dynamic cast. + // (T)dynamicExpression is a "dynamic" cast + // (T)(object)dynamicExpression is a "static" cast + // as "dynamic" casts are handled differently by ExpressionBuilder.VisitDynamicConvertInstruction + // we can always insert the cast to object, if we encounter a conversion from any reference type to dynamic. + return this.ConvertTo(compilation.FindType(KnownTypeCode.Object), expressionBuilder) + .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); + } if (targetType.Kind.IsAnyPointer() && (0.Equals(ResolveResult.ConstantValue) || 0u.Equals(ResolveResult.ConstantValue))) { if (allowImplicitConversion) diff --git a/ICSharpCode.Decompiler/DebugInfo/AsyncDebugInfo.cs b/ICSharpCode.Decompiler/DebugInfo/AsyncDebugInfo.cs index ed26d98f7b..419e4fc46c 100644 --- a/ICSharpCode.Decompiler/DebugInfo/AsyncDebugInfo.cs +++ b/ICSharpCode.Decompiler/DebugInfo/AsyncDebugInfo.cs @@ -36,7 +36,7 @@ public BlobBuilder BuildBlob(MethodDefinitionHandle moveNext) { blob.WriteUInt32((uint)await.YieldOffset); blob.WriteUInt32((uint)await.ResumeOffset); - blob.WriteCompressedInteger(MetadataTokens.GetToken(moveNext)); + blob.WriteCompressedInteger(MetadataTokens.GetRowNumber(moveNext)); } return blob; } diff --git a/ICSharpCode.Decompiler/DebugInfo/KnownGuids.cs b/ICSharpCode.Decompiler/DebugInfo/KnownGuids.cs index 353b49acfe..283818df5d 100644 --- a/ICSharpCode.Decompiler/DebugInfo/KnownGuids.cs +++ b/ICSharpCode.Decompiler/DebugInfo/KnownGuids.cs @@ -10,11 +10,13 @@ public static class KnownGuids public static readonly Guid VBLanguageGuid = new Guid("3a12d0b8-c26c-11d0-b442-00a0244a1dd2"); public static readonly Guid FSharpLanguageGuid = new Guid("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3"); + // https://github.com/dotnet/roslyn/blob/main/src/Dependencies/CodeAnalysis.Debugging/PortableCustomDebugInfoKinds.cs public static readonly Guid StateMachineHoistedLocalScopes = new Guid("6DA9A61E-F8C7-4874-BE62-68BC5630DF71"); public static readonly Guid DynamicLocalVariables = new Guid("83C563C4-B4F3-47D5-B824-BA5441477EA8"); public static readonly Guid DefaultNamespaces = new Guid("58b2eab6-209f-4e4e-a22c-b2d0f910c782"); public static readonly Guid EditAndContinueLocalSlotMap = new Guid("755F52A8-91C5-45BE-B4B8-209571E552BD"); public static readonly Guid EditAndContinueLambdaAndClosureMap = new Guid("A643004C-0240-496F-A783-30D64F4979DE"); + public static readonly Guid EncStateMachineStateMap = new Guid("8B78CD68-2EDE-420B-980B-E15884B8AAA3"); public static readonly Guid EmbeddedSource = new Guid("0e8a571b-6926-466e-b4ad-8ab04611f5fe"); public static readonly Guid SourceLink = new Guid("CC110556-A091-4D38-9FEC-25AB9A351A6A"); public static readonly Guid MethodSteppingInformation = new Guid("54FD2AC5-E925-401A-9C2A-F94F171072F8"); diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs index cc8e342bde..0e0026ea87 100644 --- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs +++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs @@ -26,13 +26,16 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; +using System.Runtime; using System.Security.Cryptography; using System.Text; +using System.Threading; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; @@ -49,7 +52,29 @@ public static bool HasCodeViewDebugDirectoryEntry(PEFile file) return file.Reader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.CodeView); } - public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream, bool noLogo = false, BlobContentId? pdbId = null) + private static bool IncludeTypeWhenGeneratingPdb(PEFile module, TypeDefinitionHandle type, DecompilerSettings settings) + { + var metadata = module.Metadata; + var typeDef = metadata.GetTypeDefinition(type); + string name = metadata.GetString(typeDef.Name); + string ns = metadata.GetString(typeDef.Namespace); + if (name == "" || CSharpDecompiler.MemberIsHidden(module, type, settings)) + return false; + if (ns == "XamlGeneratedNamespace" && name == "GeneratedInternalTypeHelper") + return false; + if (!typeDef.IsNested && RemoveEmbeddedAttributes.attributeNames.Contains(ns + "." + name)) + return false; + return true; + } + + public static void WritePdb( + PEFile file, + CSharpDecompiler decompiler, + DecompilerSettings settings, + Stream targetStream, + bool noLogo = false, + BlobContentId? pdbId = null, + IProgress progress = null) { MetadataBuilder metadata = new MetadataBuilder(); MetadataReader reader = file.Metadata; @@ -72,10 +97,24 @@ string BuildFileNameFromTypeName(TypeDefinitionHandle handle) return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs"); } - foreach (var sourceFile in reader.GetTopLevelTypeDefinitions().GroupBy(BuildFileNameFromTypeName)) + var sourceFiles = reader.GetTopLevelTypeDefinitions().Where(t => IncludeTypeWhenGeneratingPdb(file, t, settings)).GroupBy(BuildFileNameFromTypeName).ToList(); + DecompilationProgress currentProgress = new() { + TotalUnits = sourceFiles.Count, + UnitsCompleted = 0, + Title = "Generating portable PDB..." + }; + + foreach (var sourceFile in sourceFiles) { // Generate syntax tree var syntaxTree = decompiler.DecompileTypes(sourceFile); + + if (progress != null) + { + currentProgress.UnitsCompleted++; + progress.Report(currentProgress); + } + if (!syntaxTree.HasChildren) continue; diff --git a/ICSharpCode.Decompiler/DecompilationProgress.cs b/ICSharpCode.Decompiler/DecompilationProgress.cs new file mode 100644 index 0000000000..3d96349989 --- /dev/null +++ b/ICSharpCode.Decompiler/DecompilationProgress.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +namespace ICSharpCode.Decompiler +{ + /// + /// Information used for (optional) progress reporting by the decompiler. + /// + public struct DecompilationProgress + { + /// + /// The total number of units to process. If set to a value <= 0, an indeterminate progress bar is displayed. + /// + public int TotalUnits; + + /// + /// The number of units currently completed. Should be a positive number. + /// + public int UnitsCompleted; + + /// + /// Optional information displayed alongside the progress bar. + /// + public string? Status; + + /// + /// Optional custom title for the operation. + /// + public string? Title; + } +} diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index d00f198f0b..dac595efac 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -150,13 +150,17 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) if (languageVersion < CSharp.LanguageVersion.CSharp11_0) { parameterNullCheck = false; - lifetimeAnnotations = false; + scopedRef = false; + requiredMembers = false; + numericIntPtr = false; + utf8StringLiterals = false; + unsignedRightShift = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (parameterNullCheck || lifetimeAnnotations) + if (parameterNullCheck || scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift) return CSharp.LanguageVersion.CSharp11_0; if (fileScopedNamespaces || recordStructs) return CSharp.LanguageVersion.CSharp10_0; @@ -210,6 +214,24 @@ public bool NativeIntegers { } } + bool numericIntPtr = true; + + /// + /// Treat IntPtr/UIntPtr as nint/nuint. + /// + [Category("C# 11.0 / VS 2022.4")] + [Description("DecompilerSettings.NumericIntPtr")] + public bool NumericIntPtr { + get { return numericIntPtr; } + set { + if (numericIntPtr != value) + { + numericIntPtr = value; + OnPropertyChanged(); + } + } + } + bool covariantReturns = true; /// @@ -337,20 +359,43 @@ public bool FunctionPointers { } } - bool lifetimeAnnotations = true; + bool scopedRef = true; /// - /// Use C# 9 delegate* unmanaged types. - /// If this option is disabled, function pointers will instead be decompiled with type `IntPtr`. + /// Use C# 11 scoped modifier. /// [Category("C# 11.0 / VS 2022.4")] - [Description("DecompilerSettings.LifetimeAnnotations")] + [Description("DecompilerSettings.ScopedRef")] + public bool ScopedRef { + get { return scopedRef; } + set { + if (scopedRef != value) + { + scopedRef = value; + OnPropertyChanged(); + } + } + } + + [Obsolete("Renamed to ScopedRef. This property will be removed in a future version of the decompiler.")] public bool LifetimeAnnotations { - get { return lifetimeAnnotations; } + get { return ScopedRef; } + set { ScopedRef = value; } + } + + bool requiredMembers = true; + + /// + /// Use C# 11 required modifier. + /// + [Category("C# 11.0 / VS 2022.4")] + [Description("DecompilerSettings.RequiredMembers")] + public bool RequiredMembers { + get { return requiredMembers; } set { - if (lifetimeAnnotations != value) + if (requiredMembers != value) { - lifetimeAnnotations = value; + requiredMembers = value; OnPropertyChanged(); } } @@ -397,9 +442,10 @@ public bool FileScopedNamespaces { /// /// Use C# 11 preview parameter null-checking (string param!!). /// - [Category("C# 11.0 / VS 2022.1")] + [Category("C# 11.0 / VS 2022.4")] [Description("DecompilerSettings.ParameterNullCheck")] [Browsable(false)] + [Obsolete("This feature did not make it into C# 11, and may be removed in a future version of the decompiler.")] public bool ParameterNullCheck { get { return parameterNullCheck; } set { @@ -1136,6 +1182,42 @@ public bool StringInterpolation { } } + bool utf8StringLiterals = true; + + /// + /// Gets/Sets whether to use C# 11.0 UTF-8 string literals + /// + [Category("C# 11.0 / VS 2022.4")] + [Description("DecompilerSettings.Utf8StringLiterals")] + public bool Utf8StringLiterals { + get { return utf8StringLiterals; } + set { + if (utf8StringLiterals != value) + { + utf8StringLiterals = value; + OnPropertyChanged(); + } + } + } + + bool unsignedRightShift = true; + + /// + /// Gets/Sets whether to use C# 11.0 unsigned right shift operator. + /// + [Category("C# 11.0 / VS 2022.4")] + [Description("DecompilerSettings.UnsignedRightShift")] + public bool UnsignedRightShift { + get { return unsignedRightShift; } + set { + if (unsignedRightShift != value) + { + unsignedRightShift = value; + OnPropertyChanged(); + } + } + } + bool showXmlDocumentation = true; /// diff --git a/ICSharpCode.Decompiler/Disassembler/IEntityProcessor.cs b/ICSharpCode.Decompiler/Disassembler/IEntityProcessor.cs new file mode 100644 index 0000000000..5d94500d1d --- /dev/null +++ b/ICSharpCode.Decompiler/Disassembler/IEntityProcessor.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2022 Tom Englert +// +// 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. + +#nullable enable + +using System.Collections.Generic; +using System.Reflection.Metadata; + +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.Decompiler.Disassembler +{ + public interface IEntityProcessor + { + IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items); + + IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items); + + IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items); + + IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items); + + IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items); + + IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items); + + IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items); + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs index 3cd91ae14c..b268ff3014 100644 --- a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs +++ b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs @@ -71,10 +71,12 @@ public IDebugInfoProvider DebugInfo { set => methodBodyDisassembler.DebugInfo = value; } - public bool ExpandMemberDefinitions { get; set; } = false; + public bool ExpandMemberDefinitions { get; set; } public IAssemblyResolver AssemblyResolver { get; set; } + public IEntityProcessor EntityProcessor { get; set; } + public ReflectionDisassembler(ITextOutput output, CancellationToken cancellationToken) : this(output, new MethodBodyDisassembler(output, cancellationToken), cancellationToken) { @@ -1560,7 +1562,7 @@ public void DisassembleType(PEFile module, TypeDefinitionHandle type) DisassembleTypeHeaderInternal(module, type, typeDefinition, genericContext); - var interfaces = typeDefinition.GetInterfaceImplementations(); + var interfaces = Process(module, typeDefinition.GetInterfaceImplementations()); if (interfaces.Count > 0) { output.Indent(); @@ -1575,7 +1577,6 @@ public void DisassembleType(PEFile module, TypeDefinitionHandle type) output.Write(" "); first = false; var iface = module.Metadata.GetInterfaceImplementation(i); - WriteAttributes(module, iface.GetCustomAttributes()); iface.Interface.WriteTo(module, output, genericContext, ILNameSyntax.TypeName); } output.WriteLine(); @@ -1599,8 +1600,21 @@ public void DisassembleType(PEFile module, TypeDefinitionHandle type) output.WriteLine(".size {0}", layout.Size); output.WriteLine(); } - var nestedTypes = typeDefinition.GetNestedTypes(); - if (!nestedTypes.IsEmpty) + foreach (var ifaceHandle in interfaces) + { + var iface = module.Metadata.GetInterfaceImplementation(ifaceHandle); + var customAttributes = iface.GetCustomAttributes(); + if (customAttributes.Count != 0) + { + output.Write(".interfaceimpl type "); + iface.Interface.WriteTo(module, output, genericContext, ILNameSyntax.TypeName); + output.WriteLine(); + WriteAttributes(module, customAttributes); + output.WriteLine(); + } + } + var nestedTypes = Process(module, typeDefinition.GetNestedTypes()); + if (nestedTypes.Any()) { output.WriteLine("// Nested Types"); foreach (var nestedType in nestedTypes) @@ -1611,7 +1625,7 @@ public void DisassembleType(PEFile module, TypeDefinitionHandle type) } output.WriteLine(); } - var fields = typeDefinition.GetFields(); + var fields = Process(module, typeDefinition.GetFields()); if (fields.Any()) { output.WriteLine("// Fields"); @@ -1622,7 +1636,7 @@ public void DisassembleType(PEFile module, TypeDefinitionHandle type) } output.WriteLine(); } - var methods = typeDefinition.GetMethods(); + var methods = Process(module, typeDefinition.GetMethods()); if (methods.Any()) { output.WriteLine("// Methods"); @@ -1633,7 +1647,7 @@ public void DisassembleType(PEFile module, TypeDefinitionHandle type) output.WriteLine(); } } - var events = typeDefinition.GetEvents(); + var events = Process(module, typeDefinition.GetEvents()); if (events.Any()) { output.WriteLine("// Events"); @@ -1645,7 +1659,7 @@ public void DisassembleType(PEFile module, TypeDefinitionHandle type) } output.WriteLine(); } - var properties = typeDefinition.GetProperties(); + var properties = Process(module, typeDefinition.GetProperties()); if (properties.Any()) { output.WriteLine("// Properties"); @@ -1747,11 +1761,51 @@ void WriteTypeParameters(ITextOutput output, PEFile module, MetadataGenericConte } #endregion + #region Processing + + private IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items) + { + return EntityProcessor?.Process(module, items) ?? items; + } + + private IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items) + { + return EntityProcessor?.Process(module, items) ?? items; + } + + private IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items) + { + return EntityProcessor?.Process(module, items) ?? items; + } + + private IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items) + { + return EntityProcessor?.Process(module, items) ?? items; + } + + private IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items) + { + return EntityProcessor?.Process(module, items) ?? items; + } + + private IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items) + { + return EntityProcessor?.Process(module, items) ?? items; + } + + private IReadOnlyCollection Process(PEFile module, IReadOnlyCollection items) + { + return EntityProcessor?.Process(module, items) ?? items; + } + + #endregion + #region Helper methods + void WriteAttributes(PEFile module, CustomAttributeHandleCollection attributes) { var metadata = module.Metadata; - foreach (CustomAttributeHandle a in attributes) + foreach (CustomAttributeHandle a in Process(module, attributes)) { output.Write(".custom "); var attr = metadata.GetCustomAttribute(a); @@ -2037,7 +2091,7 @@ void WriteExportedType(ExportedType exportedType) public void WriteModuleContents(PEFile module) { - foreach (var handle in module.Metadata.GetTopLevelTypeDefinitions()) + foreach (var handle in Process(module, module.Metadata.GetTopLevelTypeDefinitions().ToArray())) { DisassembleType(module, handle); output.WriteLine(); diff --git a/ICSharpCode.Decompiler/Disassembler/SortByNameProcessor.cs b/ICSharpCode.Decompiler/Disassembler/SortByNameProcessor.cs new file mode 100644 index 0000000000..cda178056c --- /dev/null +++ b/ICSharpCode.Decompiler/Disassembler/SortByNameProcessor.cs @@ -0,0 +1,122 @@ +// Copyright (c) 2022 Tom Englert +// +// 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. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; + +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.Decompiler.Disassembler +{ + public class SortByNameProcessor : IEntityProcessor + { + public IReadOnlyCollection Process(PEFile module, + IReadOnlyCollection items) + { + return items.OrderBy(item => GetSortKey(item, module)).ToArray(); + } + + public IReadOnlyCollection Process(PEFile module, + IReadOnlyCollection items) + { + return items.OrderBy(item => GetSortKey(item, module)).ToArray(); + } + + public IReadOnlyCollection Process(PEFile module, + IReadOnlyCollection items) + { + return items.OrderBy(item => GetSortKey(item, module)).ToArray(); + } + + public IReadOnlyCollection Process(PEFile module, + IReadOnlyCollection items) + { + return items.OrderBy(item => GetSortKey(item, module)).ToArray(); + } + + public IReadOnlyCollection Process(PEFile module, + IReadOnlyCollection items) + { + return items.OrderBy(item => GetSortKey(item, module)).ToArray(); + } + + public IReadOnlyCollection Process(PEFile module, + IReadOnlyCollection items) + { + return items.OrderBy(item => GetSortKey(item, module)).ToArray(); + } + + public IReadOnlyCollection Process(PEFile module, + IReadOnlyCollection items) + { + return items.OrderBy(item => GetSortKey(item, module)).ToArray(); + } + + private static string GetSortKey(TypeDefinitionHandle handle, PEFile module) => + handle.GetFullTypeName(module.Metadata).ToILNameString(); + + private static string GetSortKey(MethodDefinitionHandle handle, PEFile module) + { + PlainTextOutput output = new PlainTextOutput(); + MethodDefinition definition = module.Metadata.GetMethodDefinition(handle); + + // Start with the methods name, skip return type + output.Write(module.Metadata.GetString(definition.Name)); + + DisassemblerSignatureTypeProvider signatureProvider = new DisassemblerSignatureTypeProvider(module, output); + MethodSignature> signature = + definition.DecodeSignature(signatureProvider, new MetadataGenericContext(handle, module)); + + if (signature.GenericParameterCount > 0) + { + output.Write($"`{signature.GenericParameterCount}"); + } + + InstructionOutputExtensions.WriteParameterList(output, signature); + + return output.ToString(); + } + + private static string GetSortKey(InterfaceImplementationHandle handle, PEFile module) => + module.Metadata.GetInterfaceImplementation(handle) + .Interface + .GetFullTypeName(module.Metadata) + .ToILNameString(); + + private static string GetSortKey(FieldDefinitionHandle handle, PEFile module) => + module.Metadata.GetString(module.Metadata.GetFieldDefinition(handle).Name); + + private static string GetSortKey(PropertyDefinitionHandle handle, PEFile module) => + module.Metadata.GetString(module.Metadata.GetPropertyDefinition(handle).Name); + + private static string GetSortKey(EventDefinitionHandle handle, PEFile module) => + module.Metadata.GetString(module.Metadata.GetEventDefinition(handle).Name); + + private static string GetSortKey(CustomAttributeHandle handle, PEFile module) => + module.Metadata.GetCustomAttribute(handle) + .Constructor + .GetDeclaringType(module.Metadata) + .GetFullTypeName(module.Metadata) + .ToILNameString(); + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/Documentation/XmlDocLoader.cs b/ICSharpCode.Decompiler/Documentation/XmlDocLoader.cs index b922beeefe..690eca6895 100644 --- a/ICSharpCode.Decompiler/Documentation/XmlDocLoader.cs +++ b/ICSharpCode.Decompiler/Documentation/XmlDocLoader.cs @@ -18,6 +18,7 @@ using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Runtime.CompilerServices; @@ -104,26 +105,37 @@ static string FindXmlDocumentation(string assemblyFileName, TargetRuntime runtim return fileName; } - static string LookupLocalizedXmlDoc(string fileName) + /// + /// Given the assembly file name, looks up the XML documentation file name. + /// Returns null if no XML documentation file is found. + /// + internal static string LookupLocalizedXmlDoc(string fileName) { if (string.IsNullOrEmpty(fileName)) return null; string xmlFileName = Path.ChangeExtension(fileName, ".xml"); - string currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; - string localizedXmlDocFile = GetLocalizedName(xmlFileName, currentCulture); + + CultureInfo currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture; + string localizedXmlDocFile = GetLocalizedName(xmlFileName, currentCulture.Name); + string localizedXmlDocFallbackFile = GetLocalizedName(xmlFileName, currentCulture.TwoLetterISOLanguageName); Debug.WriteLine("Try find XMLDoc @" + localizedXmlDocFile); if (File.Exists(localizedXmlDocFile)) { return localizedXmlDocFile; } + Debug.WriteLine("Try find XMLDoc @" + localizedXmlDocFallbackFile); + if (File.Exists(localizedXmlDocFallbackFile)) + { + return localizedXmlDocFallbackFile; + } Debug.WriteLine("Try find XMLDoc @" + xmlFileName); if (File.Exists(xmlFileName)) { return xmlFileName; } - if (currentCulture != "en") + if (currentCulture.TwoLetterISOLanguageName != "en") { string englishXmlDocFile = GetLocalizedName(xmlFileName, "en"); Debug.WriteLine("Try find XMLDoc @" + englishXmlDocFile); @@ -135,12 +147,9 @@ static string LookupLocalizedXmlDoc(string fileName) return null; } - static string GetLocalizedName(string fileName, string language) + private static string GetLocalizedName(string fileName, string language) { - string localizedXmlDocFile = Path.GetDirectoryName(fileName); - localizedXmlDocFile = Path.Combine(localizedXmlDocFile, language); - localizedXmlDocFile = Path.Combine(localizedXmlDocFile, Path.GetFileName(fileName)); - return localizedXmlDocFile; + return Path.Combine(Path.GetDirectoryName(fileName), language, Path.GetFileName(fileName)); } } } diff --git a/ICSharpCode.Decompiler/Documentation/XmlDocumentationProvider.cs b/ICSharpCode.Decompiler/Documentation/XmlDocumentationProvider.cs index 1f0b2da724..cae69da095 100644 --- a/ICSharpCode.Decompiler/Documentation/XmlDocumentationProvider.cs +++ b/ICSharpCode.Decompiler/Documentation/XmlDocumentationProvider.cs @@ -185,7 +185,7 @@ static string GetRedirectionTarget(string xmlFileName, string target) .Replace("%CORSYSDIR%", corSysDir); if (!Path.IsPathRooted(fileName)) fileName = Path.Combine(Path.GetDirectoryName(xmlFileName), fileName); - return LookupLocalizedXmlDoc(fileName); + return XmlDocLoader.LookupLocalizedXmlDoc(fileName); } static string AppendDirectorySeparator(string dir) @@ -196,45 +196,6 @@ static string AppendDirectorySeparator(string dir) return dir + Path.DirectorySeparatorChar; } - /// - /// Given the assembly file name, looks up the XML documentation file name. - /// Returns null if no XML documentation file is found. - /// - public static string LookupLocalizedXmlDoc(string fileName) - { - string xmlFileName = Path.ChangeExtension(fileName, ".xml"); - string currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; - string localizedXmlDocFile = GetLocalizedName(xmlFileName, currentCulture); - - Debug.WriteLine("Try find XMLDoc @" + localizedXmlDocFile); - if (File.Exists(localizedXmlDocFile)) - { - return localizedXmlDocFile; - } - Debug.WriteLine("Try find XMLDoc @" + xmlFileName); - if (File.Exists(xmlFileName)) - { - return xmlFileName; - } - if (currentCulture != "en") - { - string englishXmlDocFile = GetLocalizedName(xmlFileName, "en"); - Debug.WriteLine("Try find XMLDoc @" + englishXmlDocFile); - if (File.Exists(englishXmlDocFile)) - { - return englishXmlDocFile; - } - } - return null; - } - - static string GetLocalizedName(string fileName, string language) - { - string localizedXmlDocFile = Path.GetDirectoryName(fileName); - localizedXmlDocFile = Path.Combine(localizedXmlDocFile, language); - localizedXmlDocFile = Path.Combine(localizedXmlDocFile, Path.GetFileName(fileName)); - return localizedXmlDocFile; - } #endregion #region Load / Create Index diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 0efd42831c..5ee433d8a1 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -16,7 +16,7 @@ ILSpy git https://github.com/icsharpcode/ILSpy.git - images\DecompilerNuGetPackageIcon.png + DecompilerNuGetPackageIcon.png false Copyright 2011-$([System.DateTime]::Now.Year) AlphaSierraPapa C# Decompiler ILSpy @@ -45,6 +45,7 @@ + @@ -90,6 +91,10 @@ + + + + @@ -133,6 +138,7 @@ + @@ -672,10 +678,10 @@ - + CSharp\Syntax\PatternMatching\Pattern Matching.html - - + + diff --git a/ICSharpCode.Decompiler/IL/BlockBuilder.cs b/ICSharpCode.Decompiler/IL/BlockBuilder.cs index 75a7613bf8..c218816731 100644 --- a/ICSharpCode.Decompiler/IL/BlockBuilder.cs +++ b/ICSharpCode.Decompiler/IL/BlockBuilder.cs @@ -16,8 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -29,6 +29,11 @@ namespace ICSharpCode.Decompiler.IL { + /// + /// Converts the list of basic blocks from ILReader into a BlockContainer structure. + /// This involves creating nested block containers for exception handlers, and creating + /// branches between the blocks. + /// class BlockBuilder { readonly MethodBodyBlock body; @@ -64,7 +69,6 @@ void CreateContainerStructure() var tryRange = new Interval(eh.TryOffset, eh.TryOffset + eh.TryLength); var handlerBlock = new BlockContainer(); handlerBlock.AddILRange(new Interval(eh.HandlerOffset, eh.HandlerOffset + eh.HandlerLength)); - handlerBlock.Blocks.Add(new Block()); handlerContainers.Add(handlerBlock.StartILOffset, handlerBlock); if (eh.Kind == ExceptionRegionKind.Fault || eh.Kind == ExceptionRegionKind.Finally) @@ -94,7 +98,6 @@ void CreateContainerStructure() { var filterBlock = new BlockContainer(expectedResultType: StackType.I4); filterBlock.AddILRange(new Interval(eh.FilterOffset, eh.HandlerOffset)); - filterBlock.Blocks.Add(new Block()); handlerContainers.Add(filterBlock.StartILOffset, filterBlock); filter = filterBlock; } @@ -117,120 +120,52 @@ void CreateContainerStructure() } int currentTryIndex; - TryInstruction nextTry; + TryInstruction? nextTry; - BlockContainer currentContainer; - Block currentBlock; + BlockContainer? currentContainer; readonly Stack containerStack = new Stack(); - public void CreateBlocks(BlockContainer mainContainer, List instructions, BitSet incomingBranches, CancellationToken cancellationToken) + public void CreateBlocks(BlockContainer mainContainer, IEnumerable basicBlocks, CancellationToken cancellationToken) { CreateContainerStructure(); mainContainer.SetILRange(new Interval(0, body.GetCodeSize())); - currentContainer = mainContainer; - if (instructions.Count == 0) - { - currentContainer.Blocks.Add(new Block { - Instructions = { - new InvalidBranch("Empty body found. Decompiled assembly might be a reference assembly.") - } - }); - return; - } - foreach (var inst in instructions) + currentContainer = mainContainer; + foreach (var block in basicBlocks.OrderBy(b => b.StartILOffset)) { cancellationToken.ThrowIfCancellationRequested(); - int start = inst.StartILOffset; - if (currentBlock == null || (incomingBranches[start] && !IsStackAdjustment(inst))) + int start = block.StartILOffset; + // Leave nested containers if necessary + while (start >= currentContainer.EndILOffset) { - // Finish up the previous block - FinalizeCurrentBlock(start, fallthrough: true); - // Leave nested containers if necessary - while (start >= currentContainer.EndILOffset) - { - currentContainer = containerStack.Pop(); - currentBlock = currentContainer.Blocks.Last(); - // this container is skipped (i.e. the loop will execute again) - // set ILRange to the last instruction offset inside the block. - if (start >= currentContainer.EndILOffset) - { - Debug.Assert(currentBlock.ILRangeIsEmpty); - currentBlock.AddILRange(new Interval(currentBlock.StartILOffset, start)); - } - } - // Enter a handler if necessary - if (handlerContainers.TryGetValue(start, out BlockContainer handlerContainer)) - { - containerStack.Push(currentContainer); - currentContainer = handlerContainer; - currentBlock = handlerContainer.EntryPoint; - } - else - { - FinalizeCurrentBlock(start, fallthrough: false); - // Create the new block - currentBlock = new Block(); - currentContainer.Blocks.Add(currentBlock); - } - currentBlock.SetILRange(new Interval(start, start)); + currentContainer = containerStack.Pop(); } + // Enter a handler if necessary + if (handlerContainers.TryGetValue(start, out BlockContainer? handlerContainer)) + { + containerStack.Push(currentContainer); + currentContainer = handlerContainer; + } + // Enter a try block if necessary while (nextTry != null && start == nextTry.TryBlock.StartILOffset) { - currentBlock.Instructions.Add(nextTry); + var blockForTry = new Block(); + blockForTry.SetILRange(nextTry); + blockForTry.Instructions.Add(nextTry); + currentContainer.Blocks.Add(blockForTry); + containerStack.Push(currentContainer); currentContainer = (BlockContainer)nextTry.TryBlock; - currentBlock = new Block(); - currentContainer.Blocks.Add(currentBlock); - currentBlock.SetILRange(new Interval(start, start)); nextTry = tryInstructionList.ElementAtOrDefault(++currentTryIndex); } - currentBlock.Instructions.Add(inst); - if (inst.HasFlag(InstructionFlags.EndPointUnreachable)) - FinalizeCurrentBlock(inst.EndILOffset, fallthrough: false); - else if (!CreateExtendedBlocks && inst.HasFlag(InstructionFlags.MayBranch)) - FinalizeCurrentBlock(inst.EndILOffset, fallthrough: true); - } - FinalizeCurrentBlock(mainContainer.EndILOffset, fallthrough: false); - // Finish up all containers - while (containerStack.Count > 0) - { - currentContainer = containerStack.Pop(); - currentBlock = currentContainer.Blocks.Last(); - FinalizeCurrentBlock(mainContainer.EndILOffset, fallthrough: false); + currentContainer.Blocks.Add(block); } + Debug.Assert(currentTryIndex == tryInstructionList.Count && nextTry == null); ConnectBranches(mainContainer, cancellationToken); CreateOnErrorDispatchers(); } - static bool IsStackAdjustment(ILInstruction inst) - { - return inst is StLoc stloc && stloc.IsStackAdjustment; - } - - private void FinalizeCurrentBlock(int currentILOffset, bool fallthrough) - { - if (currentBlock == null) - return; - Debug.Assert(currentBlock.ILRangeIsEmpty); - currentBlock.SetILRange(new Interval(currentBlock.StartILOffset, currentILOffset)); - if (fallthrough) - { - if (currentBlock.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop()) - { - // Instead of putting the default branch after the switch instruction - switchInst.Sections.Last().Body = new Branch(currentILOffset); - Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable)); - } - else - { - currentBlock.Instructions.Add(new Branch(currentILOffset)); - } - } - currentBlock = null; - } - void ConnectBranches(ILInstruction inst, CancellationToken cancellationToken) { switch (inst) @@ -238,12 +173,16 @@ void ConnectBranches(ILInstruction inst, CancellationToken cancellationToken) case Branch branch: cancellationToken.ThrowIfCancellationRequested(); Debug.Assert(branch.TargetBlock == null); - branch.TargetBlock = FindBranchTarget(branch.TargetILOffset); - if (branch.TargetBlock == null) + var targetBlock = FindBranchTarget(branch.TargetILOffset); + if (targetBlock == null) { branch.ReplaceWith(new InvalidBranch("Could not find block for branch target " + Disassembler.DisassemblerHelpers.OffsetToString(branch.TargetILOffset)).WithILRange(branch)); } + else + { + branch.TargetBlock = targetBlock; + } break; case Leave leave: // ret (in void method) = leave(mainContainer) @@ -279,7 +218,7 @@ void ConnectBranches(ILInstruction inst, CancellationToken cancellationToken) } } - Block FindBranchTarget(int targetILOffset) + Block? FindBranchTarget(int targetILOffset) { foreach (var container in containerStack) { @@ -291,7 +230,7 @@ Block FindBranchTarget(int targetILOffset) if (container.SlotInfo == TryCatchHandler.BodySlot) { // catch handler is allowed to branch back into try block (VB On Error) - TryCatch tryCatch = (TryCatch)container.Parent.Parent; + TryCatch tryCatch = (TryCatch)container.Parent!.Parent!; if (tryCatch.TryBlock.StartILOffset < targetILOffset && targetILOffset < tryCatch.TryBlock.EndILOffset) { return CreateBranchTargetForOnErrorJump(tryCatch, targetILOffset); @@ -345,7 +284,7 @@ void CreateOnErrorDispatchers() { foreach (var (tryCatch, dispatch) in onErrorDispatchers) { - Block block = (Block)tryCatch.Parent; + Block block = (Block)tryCatch.Parent!; // Before the regular entry point of the try-catch, insert an. instruction that resets the dispatcher variable block.Instructions.Insert(tryCatch.ChildIndex, new StLoc(dispatch.Variable, new LdcI4(-1))); // Split the block, so that we can introduce branches that jump directly into the try block @@ -355,7 +294,7 @@ void CreateOnErrorDispatchers() newBlock.Instructions.AddRange(block.Instructions.Skip(splitAt)); block.Instructions.RemoveRange(splitAt, block.Instructions.Count - splitAt); block.Instructions.Add(new Branch(newBlock)); - ((BlockContainer)block.Parent).Blocks.Add(newBlock); + ((BlockContainer)block.Parent!).Blocks.Add(newBlock); // Update the branches that jump directly into the try block foreach (var b in dispatch.Branches) { diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index 67b687362a..377f9a24dd 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Daniel Grunwald +// Copyright (c) 2017 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 @@ -105,6 +105,8 @@ enum AsyncMethodType // across the yield point. Dictionary awaitBlocks = new Dictionary(); + bool isVisualBasicStateMachine; + int catchHandlerOffset; List awaitDebugInfos = new List(); @@ -158,8 +160,11 @@ public void Run(ILFunction function, ILTransformContext context) // and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities. function.RunTransforms(CSharpDecompiler.EarlyILTransforms(), context); - AwaitInCatchTransform.Run(function, context); - AwaitInFinallyTransform.Run(function, context); + if (!isVisualBasicStateMachine) + { + AwaitInCatchTransform.Run(function, context); + AwaitInFinallyTransform.Run(function, context); + } awaitDebugInfos.SortBy(row => row.YieldOffset); function.AsyncDebugInfo = new AsyncDebugInfo(catchHandlerOffset, awaitDebugInfos.ToImmutableArray()); @@ -180,6 +185,23 @@ private void CleanUpBodyOfMoveNext(ILFunction function) EarlyExpressionTransforms.StObjToStLoc(stobj, context); } + if (isVisualBasicStateMachine) + { + // Visual Basic state machines often contain stack slots for ldflda instruction for the builder field. + // This messes up the matching code in AnalyzeAwaitBlock() and causes it to fail. + // Propagating these stack stores makes the matching code work without the need for a lot of + // additional matching code to handle these stack slots. + foreach (var stloc in function.Descendants.OfType().Where(s => s.Variable.Kind == VariableKind.StackSlot && s.Variable.IsSingleDefinition + && s.Value.MatchLdFlda(out var target, out var field) && field.Equals(builderField) && target.MatchLdThis()).ToList()) + { + CopyPropagation.Propagate(stloc, context); + } + + // Remove lone 'ldc.i4', present in older Roslyn VB compiler output + foreach (var block in function.Descendants.OfType()) + block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.LdcI4); + } + // Copy-propagate temporaries holding a copy of 'this'. foreach (var stloc in function.Descendants.OfType().Where(s => s.Variable.IsSingleDefinition && s.Value.MatchLdThis()).ToList()) { @@ -234,14 +256,10 @@ call Start(ldloca V_1, ldloca V_0) taskType = function.Method.ReturnType; builderType = startCall.Method.DeclaringType; FullTypeName builderTypeName; - if (builderType?.GetDefinition() is { } builderTypeDef) + if (builderType?.GetDefinitionOrUnknown() is { } builderTypeDef) { builderTypeName = builderTypeDef.FullTypeName; } - else if (builderType is UnknownType unknownBuilderType) - { - builderTypeName = unknownBuilderType.FullTypeName; - } else { return false; @@ -339,6 +357,19 @@ call Start(ldloca V_1, ldloca V_0) return false; } + if (IsPotentialVisualBasicStateMachineInitialiation(body[0], stateMachineVar)) + { + // Visual Basic compiler uses a different order of field assignements + if (MatchVisualBasicStateMachineFieldAssignements(body, stateMachineVar)) + { + isVisualBasicStateMachine = true; + return true; + } + // If the Visual Basic matching code failed, reset the map in case it + // was partially populated and try matching the C# state machine initialization. + fieldToParameterMap.Clear(); + } + // Check the last field assignment - this should be the state field // stfld <>1__state(ldloca stateField, ldc.i4 -1) if (!MatchStFld(body[pos], stateMachineVar, out stateField, out var initialStateExpr)) @@ -392,6 +423,62 @@ call Start(ldloca V_1, ldloca V_0) return builderFieldIsInitialized; } + bool IsPotentialVisualBasicStateMachineInitialiation(ILInstruction inst, ILVariable stateMachineVar) + { + // stobj VB$StateMachine(ldloca stateMachine, default.value VB$StateMachine) + // Used by VBC (Debug and Release) and Roslyn VB (Release) builds + if (inst.MatchStObj(out var target, out var val, out var type1) && target.MatchLdLoca(stateMachineVar) && + val.MatchDefaultValue(out var type2) && type1.Equals(type2)) + return true; + // stloc stateMachine(newobj VB$StateMachine..ctor()) + // Used by Roslyn VB (Debug) builds + if (inst.MatchStLoc(stateMachineVar, out var init) && init is NewObj newObj && newObj.Arguments.Count == 0 && + stateMachineType.Equals(newObj.Method.DeclaringTypeDefinition)) + return true; + return false; + } + + bool MatchVisualBasicStateMachineFieldAssignements(IList body, ILVariable stateMachineVar) + { + // stfld $State(ldloca stateField, ldc.i4 -1) + if (!MatchStFld(body[body.Count - 4], stateMachineVar, out stateField, out var initialStateExpr)) + return false; + if (!initialStateExpr.MatchLdcI4(out initialState)) + return false; + if (initialState != -1) + return false; + + // stfld $Builder(ldloca stateMachine, call Create()) + if (!MatchStFld(body[body.Count - 3], stateMachineVar, out var builderField2, out var fieldInit)) + return false; + if (!builderField.Equals(builderField2)) + return false; + if (fieldInit is not Call { Method: { Name: "Create" }, Arguments: { Count: 0 } }) + return false; + + for (int i = 1; i < body.Count - 4; i++) + { + if (!MatchStFld(body[i], stateMachineVar, out var field, out fieldInit)) + return false; + // Legacy Visual Basic compiler can emit a call to RuntimeHelpers.GetObjectValue here + if (fieldInit is Call call && call.Arguments.Count == 1 && call.Method.IsStatic && call.Method.Name == "GetObjectValue") + fieldInit = call.Arguments[0]; + if (fieldInit.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter) + { + // OK, copies parameter into state machine + fieldToParameterMap[field] = v; + } + else if (fieldInit is LdObj ldobj && ldobj.Target.MatchLdThis()) + { + // stfld <>4__this(ldloc stateMachine, ldobj AsyncInStruct(ldloc this)) + fieldToParameterMap[field] = ((LdLoc)ldobj.Target).Variable; + } + else + return false; + } + return true; + } + /// /// Matches a (potentially virtual) instance method call. /// @@ -626,6 +713,14 @@ void AnalyzeMoveNext() bool[] blocksAnalyzed = new bool[blockContainer.Blocks.Count]; cachedStateVar = null; int pos = 0; + // Visual Basic state machines initialize doFinallyBodies at the start of MoveNext() + // stloc doFinallyBodies(ldc.i4 1) + if (isVisualBasicStateMachine && blockContainer.EntryPoint.Instructions[pos].MatchStLoc(out var v, out var ldci4) && + ldci4.MatchLdcI4(1)) + { + doFinallyBodies = v; + pos++; + } while (blockContainer.EntryPoint.Instructions[pos] is StLoc stloc) { // stloc V_1(ldfld <>4__this(ldloc this)) @@ -655,6 +750,7 @@ void AnalyzeMoveNext() throw new SymbolicAnalysisFailedException(); // CatchHandler will be validated in ValidateCatchBlock() + // stloc doFinallyBodies(ldc.i4 1) if (((BlockContainer)mainTryCatch.TryBlock).EntryPoint.Instructions[0] is StLoc initDoFinallyBodies && initDoFinallyBodies.Variable.Kind == VariableKind.Local && initDoFinallyBodies.Variable.Type.IsKnownType(KnownTypeCode.Boolean) @@ -719,10 +815,36 @@ private Block CheckSetResultReturnBlock(BlockContainer blockContainer, int setRe } var block = blockContainer.Blocks[setResultReturnBlockIndex]; - // stfld <>1__state(ldloc this, ldc.i4 -2) + int pos = 0; + // [vb-only] stloc S_10(ldloc this) + if (block.Instructions[pos] is StLoc stlocThisCache && stlocThisCache.Value.MatchLdThis() && + stlocThisCache.Variable.Kind == VariableKind.StackSlot) + { + pos++; + } + // [vb-only] stloc S_11(ldc.i4 -2) + ILVariable finalStateSlot = null; + int? finalStateSlotValue = null; + if (block.Instructions[pos] is StLoc stlocFinalState && stlocFinalState.Value is LdcI4 ldcI4 && + stlocFinalState.Variable.Kind == VariableKind.StackSlot) + { + finalStateSlot = stlocFinalState.Variable; + finalStateSlotValue = ldcI4.Value; + pos++; + } + // [vb-only] stloc cachedStateVar(ldloc S_11) + if (block.Instructions[pos] is StLoc stlocCachedState && stlocCachedState.Variable.Kind == VariableKind.Local && + stlocCachedState.Variable.Index == cachedStateVar?.Index && stlocCachedState.Value.MatchLdLoc(finalStateSlot)) + { + pos++; + } + + // stfld <>1__state(ldloc this, ldc.i4 -2) if (!MatchStateAssignment(block.Instructions[pos], out finalState)) throw new SymbolicAnalysisFailedException(); + if (finalStateSlotValue is not null && finalState != finalStateSlotValue.Value) + throw new SymbolicAnalysisFailedException(); finalStateKnown = true; pos++; @@ -878,13 +1000,20 @@ void ValidateCatchBlock() bool[] blocksAnalyzed = new bool[handlerContainer.Blocks.Count]; var catchBlock = handlerContainer.EntryPoint; catchHandlerOffset = catchBlock.StartILOffset; + int pos = 0; + // [vb-only] call SetProjectError(ldloc E_143) + if (isVisualBasicStateMachine && catchBlock.Instructions[pos] is Call call && call.Method.Name == "SetProjectError" && + call.Arguments.Count == 1 && call.Arguments[0].MatchLdLoc(handler.Variable)) + { + pos++; + } // stloc exception(ldloc E_143) - if (!(catchBlock.Instructions[0] is StLoc stloc)) + if (!(catchBlock.Instructions[pos++] is StLoc stloc)) throw new SymbolicAnalysisFailedException(); if (!stloc.Value.MatchLdLoc(handler.Variable)) throw new SymbolicAnalysisFailedException(); // stfld <>1__state(ldloc this, ldc.i4 -2) - if (!MatchStateAssignment(catchBlock.Instructions[1], out int newState)) + if (!MatchStateAssignment(catchBlock.Instructions[pos++], out int newState)) throw new SymbolicAnalysisFailedException(); if (finalStateKnown) { @@ -896,7 +1025,6 @@ void ValidateCatchBlock() finalState = newState; finalStateKnown = true; } - int pos = 2; if (pos + 2 == catchBlock.Instructions.Count && catchBlock.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) { if (MatchDisposeCombinedTokens(handlerContainer, condition, trueInst, falseInst, blocksAnalyzed, out var setResultAndExitBlock)) @@ -928,6 +1056,13 @@ void ValidateCatchBlock() // [optional] call Complete(ldfld <>t__builder(ldloc this)) MatchCompleteCall(catchBlock, ref pos); + // [vb-only] call ClearProjectError() + if (isVisualBasicStateMachine && catchBlock.Instructions[pos] is Call call2 && + call2.Method.Name == "ClearProjectError" && call2.Arguments.Count == 0) + { + pos++; + } + // leave IL_0000 if (!catchBlock.Instructions[pos].MatchLeave((BlockContainer)moveNextFunction.Body)) throw new SymbolicAnalysisFailedException(); @@ -1598,77 +1733,116 @@ bool CheckResumeBlock(Block block, ILVariable awaiterVar, IField awaiterField, B if (!RestoreStack(block, ref pos, stackField)) return false; - // stloc awaiterVar(ldfld awaiterField(ldloc this)) - if (!block.Instructions[pos].MatchStLoc(awaiterVar, out var value)) - return false; - if (value is CastClass cast && cast.Type.Equals(awaiterVar.Type)) + // Visual Basic state machines use a different order of field assignements. + if (isVisualBasicStateMachine) { - // If the awaiter is a reference type, it might get stored in a field of type `object` - // and cast back to the awaiter type in the resume block - value = cast.Argument; - } - if (!value.MatchLdFld(out var target, out var field)) - return false; - if (!target.MatchLdThis()) - return false; - if (!field.Equals(awaiterField)) - return false; - pos++; + // stloc S_28(ldc.i4 -1) + // stloc cachedStateVar(ldloc S_28) + // stfld <>1__state(ldloc this, ldloc S_28) + if (!MatchStateFieldAssignement(block, ref pos)) + return false; + pos++; - // stfld awaiterField(ldloc this, default.value) - if (block.Instructions[pos].MatchStFld(out target, out field, out value) - && target.MatchLdThis() - && field.Equals(awaiterField) - && (value.OpCode == OpCode.DefaultValue || value.OpCode == OpCode.LdNull)) - { + // stloc awaiterVar(ldfld awaiterField(ldloc this)) + if (!MatchStoreToAwaiterVariable(block.Instructions[pos])) + return false; pos++; + + // [optional] stfld awaiterField(ldloc this, default.value) + MatchResetAwaiterField(block, ref pos); } else { - // {stloc V_6(default.value System.Runtime.CompilerServices.TaskAwaiter)} - // {stobj System.Runtime.CompilerServices.TaskAwaiter`1[[System.Int32]](ldflda <>u__$awaiter4(ldloc this), ldloc V_6) at IL_0163} - if (block.Instructions[pos].MatchStLoc(out var variable, out value) && value.OpCode == OpCode.DefaultValue - && block.Instructions[pos + 1].MatchStFld(out target, out field, out value) - && field.Equals(awaiterField) - && value.MatchLdLoc(variable)) - { - pos += 2; - } + // stloc awaiterVar(ldfld awaiterField(ldloc this)) + if (!MatchStoreToAwaiterVariable(block.Instructions[pos])) + return false; + pos++; + + // [optional] stfld awaiterField(ldloc this, default.value) + MatchResetAwaiterField(block, ref pos); + + // stloc S_28(ldc.i4 -1) + // stloc cachedStateVar(ldloc S_28) + // stfld <>1__state(ldloc this, ldloc S_28) + if (!MatchStateFieldAssignement(block, ref pos)) + return false; + pos++; } + return block.Instructions[pos].MatchBranch(completedBlock); - // stloc S_28(ldc.i4 -1) - // stloc cachedStateVar(ldloc S_28) - // stfld <>1__state(ldloc this, ldloc S_28) - ILVariable m1Var = null; - if (block.Instructions[pos] is StLoc stlocM1 && stlocM1.Value.MatchLdcI4(initialState) && stlocM1.Variable.Kind == VariableKind.StackSlot) + bool MatchStoreToAwaiterVariable(ILInstruction instr) { - m1Var = stlocM1.Variable; - pos++; + // stloc awaiterVar(ldfld awaiterField(ldloc this)) + if (!instr.MatchStLoc(awaiterVar, out var value)) + return false; + if (value is CastClass cast && cast.Type.Equals(awaiterVar.Type)) + { + // If the awaiter is a reference type, it might get stored in a field of type `object` + // and cast back to the awaiter type in the resume block + value = cast.Argument; + } + if (!value.MatchLdFld(out var target, out var field)) + return false; + if (!target.MatchLdThis()) + return false; + if (!field.Equals(awaiterField)) + return false; + return true; } - if (block.Instructions[pos] is StLoc stlocCachedState) + + void MatchResetAwaiterField(Block block, ref int pos) { - if (stlocCachedState.Variable.Kind == VariableKind.Local && stlocCachedState.Variable.Index == cachedStateVar?.Index) + // stfld awaiterField(ldloc this, default.value) + if (block.Instructions[pos].MatchStFld(out var target, out var field, out var value) + && target.MatchLdThis() + && field.Equals(awaiterField) + && (value.OpCode == OpCode.DefaultValue || value.OpCode == OpCode.LdNull)) + { + pos++; + } + else { - if (stlocCachedState.Value.MatchLdLoc(m1Var) || stlocCachedState.Value.MatchLdcI4(initialState)) - pos++; + // {stloc V_6(default.value System.Runtime.CompilerServices.TaskAwaiter)} + // {stobj System.Runtime.CompilerServices.TaskAwaiter`1[[System.Int32]](ldflda <>u__$awaiter4(ldloc this), ldloc V_6) at IL_0163} + if (block.Instructions[pos].MatchStLoc(out var variable, out value) && value.OpCode == OpCode.DefaultValue + && block.Instructions[pos + 1].MatchStFld(out target, out field, out value) + && field.Equals(awaiterField) + && value.MatchLdLoc(variable)) + { + pos += 2; + } } } - if (block.Instructions[pos].MatchStFld(out target, out field, out value)) + + bool MatchStateFieldAssignement(Block block, ref int pos) { + // stloc S_28(ldc.i4 -1) + // stloc cachedStateVar(ldloc S_28) + // stfld <>1__state(ldloc this, ldloc S_28) + ILVariable m1Var = null; + if (block.Instructions[pos] is StLoc stlocM1 && stlocM1.Value.MatchLdcI4(initialState) && stlocM1.Variable.Kind == VariableKind.StackSlot) + { + m1Var = stlocM1.Variable; + pos++; + } + if (block.Instructions[pos] is StLoc stlocCachedState) + { + if (stlocCachedState.Variable.Kind == VariableKind.Local && stlocCachedState.Variable.Index == cachedStateVar?.Index) + { + if (stlocCachedState.Value.MatchLdLoc(m1Var) || stlocCachedState.Value.MatchLdcI4(initialState)) + pos++; + } + } + if (!block.Instructions[pos].MatchStFld(out var target, out var field, out var value)) + return false; if (!target.MatchLdThis()) return false; if (!field.MemberDefinition.Equals(stateField.MemberDefinition)) return false; if (!(value.MatchLdcI4(initialState) || value.MatchLdLoc(m1Var))) return false; - pos++; - } - else - { - return false; + return true; } - - return block.Instructions[pos].MatchBranch(completedBlock); } private bool RestoreStack(Block block, ref int pos, IField stackField) diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs index c813d34655..af864827e5 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2014 Daniel Grunwald +// Copyright (c) 2014 Daniel Grunwald // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -137,7 +137,7 @@ void InlineVariableInReturnBlock(Block block, ILTransformContext context) void SimplifyBranchChains(ILFunction function, ILTransformContext context) { - List<(BlockContainer, Block)> blocksToAdd = new List<(BlockContainer, Block)>(); + List<(Block Block, BlockContainer TargetContainer)> blocksToMove = new List<(Block, BlockContainer)>(); HashSet visitedBlocks = new HashSet(); foreach (var branch in function.Descendants.OfType()) { @@ -167,7 +167,7 @@ void SimplifyBranchChains(ILFunction function, ILTransformContext context) context.Step("Replace branch to return with return", branch); branch.ReplaceWith(targetBlock.Instructions[0].Clone()); } - else if (branch.TargetContainer != branch.Ancestors.OfType().First()) + else if (branch.TargetContainer != branch.Ancestors.OfType().First() && targetBlock.IncomingEdgeCount == 1) { // We don't want to always inline the return directly, because this // might force us to place the return within a loop, when it's better @@ -175,11 +175,9 @@ void SimplifyBranchChains(ILFunction function, ILTransformContext context) // But we do want to move the return block into the correct try-finally scope, // so that loop detection at least has the option to put it inside // the loop body. - context.Step("Copy return block into try block", branch); - Block blockCopy = (Block)branch.TargetBlock.Clone(); + context.Step("Move return block into try block", branch); BlockContainer localContainer = branch.Ancestors.OfType().First(); - blocksToAdd.Add((localContainer, blockCopy)); - branch.TargetBlock = blockCopy; + blocksToMove.Add((targetBlock, localContainer)); } } else if (targetBlock.Instructions.Count == 1 && targetBlock.Instructions[0] is Leave leave && leave.Value.MatchNop()) @@ -194,9 +192,10 @@ void SimplifyBranchChains(ILFunction function, ILTransformContext context) if (targetBlock.IncomingEdgeCount == 0) targetBlock.Instructions.Clear(); // mark the block for deletion } - foreach (var (container, block) in blocksToAdd) + foreach ((Block block, BlockContainer targetContainer) in blocksToMove) { - container.Blocks.Add(block); + block.Remove(); + targetContainer.Blocks.Add(block); } } @@ -259,7 +258,7 @@ static bool CombineBlockWithNextBlock(BlockContainer container, Block block, ILT if (targetBlock.StartILOffset < block.StartILOffset && IsDeadTrueStore(block)) { // The C# compiler generates a dead store for the condition of while (true) loops. - block.Instructions.RemoveRange(block.Instructions.Count - 3, 2); + block.Instructions.RemoveAt(block.Instructions.Count - 2); } if (block.ILRangeIsEmpty) @@ -276,15 +275,13 @@ static bool CombineBlockWithNextBlock(BlockContainer container, Block block, ILT /// private static bool IsDeadTrueStore(Block block) { - if (block.Instructions.Count < 3) + if (block.Instructions.Count < 2) return false; - if (!(block.Instructions.SecondToLastOrDefault() is StLoc deadStore && block.Instructions[block.Instructions.Count - 3] is StLoc tempStore)) + if (!(block.Instructions.SecondToLastOrDefault() is StLoc deadStore)) return false; if (!(deadStore.Variable.LoadCount == 0 && deadStore.Variable.AddressCount == 0)) return false; - if (!(deadStore.Value.MatchLdLoc(tempStore.Variable) && tempStore.Variable.IsSingleDefinition && tempStore.Variable.LoadCount == 1)) - return false; - return tempStore.Value.MatchLdcI4(1) && deadStore.Variable.Type.IsKnownType(KnownTypeCode.Boolean); + return deadStore.Value.MatchLdcI4(1) && deadStore.Variable.Type.IsKnownType(KnownTypeCode.Boolean); } } } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs index 5958aa08cb..4d67e9bb49 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2012 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -58,6 +58,7 @@ class StateRangeAnalysis public CancellationToken CancellationToken; readonly StateRangeAnalysisMode mode; readonly IField? stateField; + readonly bool legacyVisualBasic; readonly SymbolicEvaluationContext evalContext; readonly Dictionary ranges = new Dictionary(); @@ -67,10 +68,11 @@ class StateRangeAnalysis internal ILVariable? doFinallyBodies; internal ILVariable? skipFinallyBodies; - public StateRangeAnalysis(StateRangeAnalysisMode mode, IField? stateField, ILVariable? cachedStateVar = null) + public StateRangeAnalysis(StateRangeAnalysisMode mode, IField? stateField, ILVariable? cachedStateVar = null, bool legacyVisualBasic = false) { this.mode = mode; this.stateField = stateField; + this.legacyVisualBasic = legacyVisualBasic; if (mode == StateRangeAnalysisMode.IteratorDispose) { finallyMethodToStateRange = new Dictionary(); @@ -80,7 +82,7 @@ public StateRangeAnalysis(StateRangeAnalysisMode mode, IField? stateField, ILVar rangesForLeave = new Dictionary(); } - evalContext = new SymbolicEvaluationContext(stateField); + evalContext = new SymbolicEvaluationContext(stateField, legacyVisualBasic); if (cachedStateVar != null) evalContext.AddStateVariable(cachedStateVar); } @@ -94,7 +96,7 @@ public StateRangeAnalysis(StateRangeAnalysisMode mode, IField? stateField, ILVar /// internal StateRangeAnalysis CreateNestedAnalysis() { - var sra = new StateRangeAnalysis(mode, stateField); + var sra = new StateRangeAnalysis(mode, stateField, legacyVisualBasic: legacyVisualBasic); sra.doFinallyBodies = this.doFinallyBodies; sra.skipFinallyBodies = this.skipFinallyBodies; foreach (var v in this.evalContext.StateVariables) diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs index 4bd88cadea..07ddcf1ed1 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -99,10 +99,12 @@ public override string ToString() class SymbolicEvaluationContext { readonly IField stateField; + readonly bool legacyVisualBasic; readonly List stateVariables = new List(); - public SymbolicEvaluationContext(IField stateField) + public SymbolicEvaluationContext(IField stateField, bool legacyVisualBasic = false) { + this.legacyVisualBasic = legacyVisualBasic; this.stateField = stateField; } @@ -118,7 +120,7 @@ public void AddStateVariable(ILVariable v) public SymbolicValue Eval(ILInstruction inst) { - if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub && !bni.CheckForOverflow) + if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub && (legacyVisualBasic || !bni.CheckForOverflow)) { var left = Eval(bni.Left); var right = Eval(bni.Right); diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index 099247f61b..8c60690a68 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -59,6 +59,13 @@ public class YieldReturnDecompiler : IILTransform /// Set in MatchEnumeratorCreationPattern() bool isCompiledWithMono; + /// Set in MatchEnumeratorCreationPattern() or ConstructExceptionTable() + bool isCompiledWithVisualBasic; + + /// Set in MatchEnumeratorCreationPattern() + /// If this is true, then isCompiledWithVisualBasic is also true. + bool isCompiledWithLegacyVisualBasic; + /// The dispose method of the compiler-generated enumerator class. /// Set in ConstructExceptionTable() MethodDefinitionHandle disposeMethod; @@ -100,6 +107,11 @@ public class YieldReturnDecompiler : IILTransform /// ILVariable skipFinallyBodies; + /// + /// Local bool variable in MoveNext() that signifies whether to execute finally bodies. + /// + ILVariable doFinallyBodies; + /// /// Set of variables might hold copies of the generated state field. /// @@ -115,6 +127,9 @@ public void Run(ILFunction function, ILTransformContext context) this.currentType = metadata.GetMethodDefinition((MethodDefinitionHandle)context.Function.Method.MetadataToken).GetDeclaringType(); this.enumeratorType = default; this.enumeratorCtor = default; + this.isCompiledWithMono = false; + this.isCompiledWithVisualBasic = false; + this.isCompiledWithLegacyVisualBasic = false; this.stateField = null; this.currentField = null; this.disposingField = null; @@ -123,6 +138,7 @@ public void Run(ILFunction function, ILTransformContext context) this.decompiledFinallyMethods.Clear(); this.returnStores.Clear(); this.skipFinallyBodies = null; + this.doFinallyBodies = null; this.cachedStateVars = null; if (!MatchEnumeratorCreationPattern(function)) return; @@ -144,6 +160,7 @@ public void Run(ILFunction function, ILTransformContext context) context.Step("Replacing body with MoveNext() body", function); function.IsIterator = true; function.StateMachineCompiledWithMono = isCompiledWithMono; + function.StateMachineCompiledWithLegacyVisualBasic = isCompiledWithLegacyVisualBasic; var oldBody = function.Body; function.Body = newBody; // register any locals used in newBody @@ -159,7 +176,7 @@ public void Run(ILFunction function, ILTransformContext context) context.Step("Delete unreachable blocks", function); - if (isCompiledWithMono) + if (isCompiledWithMono || isCompiledWithVisualBasic) { // mono has try-finally inline (like async on MS); we also need to sort nested blocks: foreach (var nestedContainer in newBody.Blocks.SelectMany(c => c.Descendants).OfType()) @@ -180,6 +197,14 @@ public void Run(ILFunction function, ILTransformContext context) { CleanSkipFinallyBodies(function); } + else if (isCompiledWithLegacyVisualBasic) + { + CleanDoFinallyBodies(function); + } + else if (isCompiledWithVisualBasic) + { + CleanFinallyStateChecks(function); + } else { DecompileFinallyBlocks(); @@ -192,6 +217,7 @@ public void Run(ILFunction function, ILTransformContext context) context.Step("Transform failed, roll it back", function); function.IsIterator = false; function.StateMachineCompiledWithMono = false; + function.StateMachineCompiledWithLegacyVisualBasic = false; function.Body = oldBody; function.Variables.RemoveDead(); function.Warnings.Add($"yield-return decompiler failed: {ex.Message}"); @@ -202,7 +228,7 @@ public void Run(ILFunction function, ILTransformContext context) TranslateFieldsToLocalAccess(function, function, fieldToParameterMap, isCompiledWithMono); // On mono, we still need to remove traces of the state variable(s): - if (isCompiledWithMono) + if (isCompiledWithMono || isCompiledWithVisualBasic) { if (fieldToParameterMap.TryGetValue(stateField, out var stateVar)) { @@ -275,18 +301,26 @@ bool MatchEnumeratorCreationPattern(ILFunction function) if (MatchEnumeratorCreationNewObj(newObj)) { pos++; // OK - isCompiledWithMono = false; } else if (MatchMonoEnumeratorCreationNewObj(newObj)) { pos++; - isCompiledWithMono = true; + if (TransformDisplayClassUsage.ValidateConstructor(context, ((NewObj)newObj).Method)) + { + isCompiledWithMono = true; + } + else + { + isCompiledWithVisualBasic = true; + isCompiledWithLegacyVisualBasic = true; + } } else { return false; } + bool stateFieldInitialized = false; for (; pos < body.Instructions.Count; pos++) { // stfld(..., ldloc(var_1), ldloc(parameter)) @@ -306,6 +340,11 @@ bool MatchEnumeratorCreationPattern(ILFunction function) // copy of 'this' in struct fieldToParameterMap[(IField)storedField.MemberDefinition] = ((LdLoc)ldobj.Target).Variable; } + else if ((isCompiledWithMono || isCompiledWithLegacyVisualBasic) && (value.MatchLdcI4(-2) || value.MatchLdcI4(-1) || value.MatchLdcI4(0))) + { + stateField = (IField)storedField.MemberDefinition; + stateFieldInitialized = true; + } else { return false; @@ -319,7 +358,7 @@ bool MatchEnumeratorCreationPattern(ILFunction function) // stloc(var_2, ldloc(var_1)) pos++; } - if (isCompiledWithMono) + if (isCompiledWithMono && !stateFieldInitialized) { // Mono initializes the state field separately: // (but not if it's left at the default value 0) @@ -328,7 +367,6 @@ bool MatchEnumeratorCreationPattern(ILFunction function) && (value.MatchLdcI4(-2) || value.MatchLdcI4(0))) { stateField = (IField)field.MemberDefinition; - isCompiledWithMono = true; pos++; } } @@ -477,8 +515,7 @@ internal static ILFunction CreateILAst(MethodDefinitionHandle method, ILTransfor void AnalyzeCurrentProperty() { MethodDefinitionHandle getCurrentMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault( - m => metadata.GetString(metadata.GetMethodDefinition(m).Name).StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal) - && metadata.GetString(metadata.GetMethodDefinition(m).Name).EndsWith(".get_Current", StringComparison.Ordinal)); + m => IsMethod(m, "get_Current")); Block body = SingleBlock(CreateILAst(getCurrentMethod, context).Body); if (body == null) throw new SymbolicAnalysisFailedException("get_Current has no body"); @@ -516,8 +553,7 @@ void AnalyzeCurrentProperty() void ResolveIEnumerableIEnumeratorFieldMapping() { MethodDefinitionHandle getEnumeratorMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault( - m => metadata.GetString(metadata.GetMethodDefinition(m).Name).StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal) - && metadata.GetString(metadata.GetMethodDefinition(m).Name).EndsWith(".GetEnumerator", StringComparison.Ordinal)); + m => IsMethod(m, "GetEnumerator")); ResolveIEnumerableIEnumeratorFieldMapping(getEnumeratorMethod, context, fieldToParameterMap); } @@ -551,12 +587,26 @@ internal static void ResolveIEnumerableIEnumeratorFieldMapping(MethodDefinitionH void ConstructExceptionTable() { - if (isCompiledWithMono) + disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => IsMethod(m, "Dispose")); + var function = CreateILAst(disposeMethod, context); + + if (!isCompiledWithVisualBasic && !isCompiledWithMono) { - disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => metadata.GetString(metadata.GetMethodDefinition(m).Name) == "Dispose"); - var function = CreateILAst(disposeMethod, context); BlockContainer body = (BlockContainer)function.Body; + foreach (var instr in body.Blocks.SelectMany(block => block.Instructions)) + { + if (instr is CallInstruction call && call.Arguments.Count == 1 && call.Arguments[0].MatchLdThis() && + IsMethod((MethodDefinitionHandle)call.Method.MetadataToken, "MoveNext")) + { + isCompiledWithVisualBasic = true; + break; + } + } + } + if (isCompiledWithMono || isCompiledWithVisualBasic) + { + BlockContainer body = (BlockContainer)function.Body; for (var i = 0; (i < body.EntryPoint.Instructions.Count) && !(body.EntryPoint.Instructions[i] is Branch); i++) { if (body.EntryPoint.Instructions[i] is StObj stobj @@ -570,14 +620,12 @@ void ConstructExceptionTable() } } - // On mono, we don't need to analyse Dispose() to reconstruct the try-finally structure. + // On mono and VB, we don't need to analyse Dispose() to reconstruct the try-finally structure. finallyMethodToStateRange = default; } else { - // Non-Mono: analyze try-finally structure in Dispose() - disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => metadata.GetString(metadata.GetMethodDefinition(m).Name) == "System.IDisposable.Dispose"); - var function = CreateILAst(disposeMethod, context); + // Non-Mono/Non-VB: analyze try-finally structure in Dispose() var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorDispose, stateField); rangeAnalysis.AssignStateRanges(function.Body, LongSet.Universe); finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange; @@ -616,6 +664,17 @@ BlockContainer AnalyzeMoveNext(ILFunction function) CopyPropagation.Propagate(stloc, context); } + // Copy propagate stack slots holding a 32 bit integer. + foreach (var stloc in moveNextFunction.Descendants.OfType().Where(s => s.Variable.Kind == VariableKind.StackSlot && s.Variable.IsSingleDefinition && s.Value.OpCode == OpCode.LdcI4).ToList()) + { + CopyPropagation.Propagate(stloc, context); + } + + foreach (var block in moveNextFunction.Descendants.OfType()) + { + block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.LdcI4); + } + var body = (BlockContainer)moveNextFunction.Body; if (body.Blocks.Count == 1 && body.Blocks[0].Instructions.Count == 1 && body.Blocks[0].Instructions[0] is TryFault tryFault) { @@ -635,6 +694,35 @@ BlockContainer AnalyzeMoveNext(ILFunction function) } } + if (isCompiledWithLegacyVisualBasic && (body.Blocks.Count == 2 || body.Blocks.Count == 1) && + body.Blocks[0].Instructions.Count == 2 && + body.Blocks[0].Instructions[0].MatchStLoc(out var firstVar, out var ldc) && ldc.MatchLdcI4(1)) + { + doFinallyBodies = firstVar; + if (body.Blocks[0].Instructions[1] is TryCatch tryCatch && tryCatch.Handlers.Count == 1) + { + TryCatchHandler catchHandler = tryCatch.Handlers[0]; + var catchBlockContainer = catchHandler.Body as BlockContainer; + if (catchBlockContainer?.Blocks.Count != 1) + throw new SymbolicAnalysisFailedException("Unexpected number of blocks in MoveNext() catch block"); + var catchBlock = catchBlockContainer.Blocks.Single(); + if (!(catchBlock.Instructions.Count == 4 && catchBlock.Instructions[0] is Call call && + call.Method.Name == "SetProjectError" && call.Arguments.Count == 1 && + call.Arguments[0].MatchLdLoc(catchHandler.Variable) && + catchBlock.Instructions[1].MatchStLoc(out _, out var ldloc) && + ldloc.MatchLdLoc(catchHandler.Variable) && + catchBlock.Instructions[2].MatchStFld(out var ldThis, out var fld, out var value) && + ldThis.MatchLdThis() && fld.MemberDefinition.Equals(stateField) && value is LdcI4 && + catchBlock.Instructions[3] is Rethrow)) + throw new SymbolicAnalysisFailedException("Unexpected catch block contents in MoveNext()"); + BlockContainer tryCatchBody = (BlockContainer)tryCatch.TryBlock; + // Move return block + if (body.Blocks.Count == 2) + tryCatchBody.Blocks.Add(body.Blocks[1]); + body = tryCatchBody; + } + } + if (stateField == null) { // With mono-compiled state machines, it's possible that we haven't discovered the state field @@ -688,8 +776,9 @@ BlockContainer AnalyzeMoveNext(ILFunction function) // but those cannot contain any yield statements. // So for reconstructing the control flow, we only consider the blocks directly within body. - var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorMoveNext, stateField); + var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorMoveNext, stateField, legacyVisualBasic: isCompiledWithLegacyVisualBasic); rangeAnalysis.skipFinallyBodies = skipFinallyBodies; + rangeAnalysis.doFinallyBodies = doFinallyBodies; rangeAnalysis.CancellationToken = context.CancellationToken; rangeAnalysis.AssignStateRanges(body, LongSet.Universe); cachedStateVars = rangeAnalysis.CachedStateVars.ToHashSet(); @@ -813,7 +902,7 @@ private BlockContainer ConvertBody(BlockContainer oldBody, StateRangeAnalysis ra // (this allows us to consider each block individually for try-finally reconstruction) newBlock = SplitBlock(newBlock, oldInst); } - else if (oldInst is TryFinally tryFinally && isCompiledWithMono) + else if (oldInst is TryFinally tryFinally && (isCompiledWithMono || isCompiledWithVisualBasic)) { // with mono, we have to recurse into try-finally blocks var oldTryBlock = (BlockContainer)tryFinally.TryBlock; @@ -821,6 +910,19 @@ private BlockContainer ConvertBody(BlockContainer oldBody, StateRangeAnalysis ra sra.AssignStateRanges(oldTryBlock, LongSet.Universe); tryFinally.TryBlock = ConvertBody(oldTryBlock, sra); } + else if (isCompiledWithLegacyVisualBasic && oldInst is IfInstruction ifInstruction && + ifInstruction.FalseInst.MatchNop() && + ifInstruction.Condition.MatchCompEquals(out var left, out var right) && + left.MatchLdFld(out var ldThis, out var fld) && ldThis.MatchLdThis() && + fld.MemberDefinition.Equals(disposingField) && + right.MatchLdcI4(0)) + { + newBlock.Instructions.Add(ifInstruction.TrueInst); + newBlock.AddILRange(ifInstruction.TrueInst); + UpdateBranchTargets(ifInstruction.TrueInst); + break; + } + // copy over the instruction to the new block newBlock.Instructions.Add(oldInst); newBlock.AddILRange(oldInst); @@ -828,12 +930,13 @@ private BlockContainer ConvertBody(BlockContainer oldBody, StateRangeAnalysis ra } } - // Insert new artificial block as entry point, and jump to state 0. + // Insert new artificial block as entry point, and jump to the initial state. // This causes the method to start directly at the first user code, // and the whole compiler-generated state-dispatching logic becomes unreachable code // and gets deleted. + int initialState = isCompiledWithLegacyVisualBasic ? -1 : 0; newBody.Blocks.Insert(0, new Block { - Instructions = { MakeGoTo(0) } + Instructions = { MakeGoTo(initialState) } }); return newBody; @@ -870,10 +973,19 @@ void ConvertBranchAfterYieldReturn(Block newBlock, Block oldBlock, int pos) } } - if (oldBlock.Instructions[pos].MatchStFld(out var target, out var field, out var value) + // Visual Basic Compiler emits additional stores to variables. + int? localNewState = null; + if (oldBlock.Instructions[pos].MatchStLoc(out _, out var value) && value is LdcI4 ldci4) + { + localNewState = ldci4.Value; + pos++; + } + + if (oldBlock.Instructions[pos].MatchStFld(out var target, out var field, out value) && target.MatchLdThis() && field.MemberDefinition == stateField - && value.MatchLdcI4(out int newState)) + && value.MatchLdcI4(out int newState) + && (localNewState is null || localNewState == newState)) { pos++; } @@ -899,6 +1011,19 @@ void ConvertBranchAfterYieldReturn(Block newBlock, Block oldBlock, int pos) } pos++; } + // We can't use MatchStLoc like above since the doFinallyBodies variable is split by SplitVariables. + // This occurs for the Legacy VBC compiler. + if (oldBlock.Instructions[pos].MatchStLoc(out var var, out value) && var.Kind == VariableKind.Local && var.Index == doFinallyBodies.Index) + { + if (!value.MatchLdcI4(0)) + { + newBlock.Instructions.Add(new InvalidExpression { + ExpectedResultType = StackType.Void, + Message = "Unexpected assignment to doFinallyBodies" + }); + } + pos++; + } if (oldBlock.Instructions[pos].MatchReturn(out var retVal) && retVal.MatchLdcI4(1)) { @@ -1251,6 +1376,7 @@ bool IsStateAssignment(ILInstruction inst) } #endregion + #region Cleanup finally blocks /// /// Eliminates usage of doFinallyBodies @@ -1264,7 +1390,7 @@ private void CleanSkipFinallyBodies(ILFunction function) context.StepStartGroup("CleanFinallyBlocks", function); if (skipFinallyBodies.StoreInstructions.Count != 0 || skipFinallyBodies.AddressCount != 0) { - // misdetected another variable as doFinallyBodies? + // misdetected another variable as skipFinallyBodies? // Fortunately removing the initial store of 0 is harmless, as we // default-initialize the variable on uninit uses return; @@ -1318,5 +1444,96 @@ bool IsCallToMonoFinallyMethod(Call call, out IMethod finallyMethod) return !call.Method.MetadataToken.IsNil; } } + + private void CleanDoFinallyBodies(ILFunction function) + { + if (doFinallyBodies == null) + { + return; // only VB code uses doFinallyBodies + } + context.StepStartGroup("CleanDoFinallyBodies", function); + if (doFinallyBodies.StoreInstructions.Count != 0 || doFinallyBodies.AddressCount != 0) + { + // misdetected another variable as skipFinallyBodies? + // Fortunately removing the initial store of 0 is harmless, as we + // default-initialize the variable on uninit uses + return; + } + foreach (var tryFinally in function.Descendants.OfType()) + { + if (!(tryFinally.FinallyBlock is BlockContainer container)) + continue; + Block entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(container); + if (entryPoint?.Instructions[0] is IfInstruction ifInst) + { + if (ifInst.Condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(doFinallyBodies) && right.MatchLdcI4(0)) + { + context.Step("Remove if (doFinallyBodies) from try-finally", tryFinally); + // condition will always be false now that we're using 'yield' instructions + entryPoint.Instructions.RemoveAt(0); + } + } + } + foreach (LdLoc load in doFinallyBodies.LoadInstructions.ToArray()) + { + load.ReplaceWith(new LdcI4(1).WithILRange(load)); + } + context.StepEndGroup(keepIfEmpty: true); + } + + private void CleanFinallyStateChecks(ILFunction function) + { + context.StepStartGroup("CleanFinallyStateChecks", function); + foreach (var tryFinally in function.Descendants.OfType()) + { + if (!(tryFinally.FinallyBlock is BlockContainer container)) + continue; + Block entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(container); + if (entryPoint?.Instructions[0] is IfInstruction ifInst) + { + if (ifInst.Condition is Comp comp && comp.Kind == ComparisonKind.GreaterThanOrEqual && + comp.InputType == StackType.I4 && comp.Sign == Sign.Signed && comp.Left.MatchLdLoc(out var variable) && + cachedStateVars.Contains(variable) && + comp.Right.MatchLdcI4(0)) + { + context.Step("Remove if (stateVar >= 0) from try-finally", tryFinally); + // condition will always be false now that we're using 'yield' instructions + entryPoint.Instructions.RemoveAt(0); + } + } + } + context.StepEndGroup(keepIfEmpty: true); + } + + #endregion + + bool IsMethod(MethodDefinitionHandle method, string name) + { + var methodDefinition = metadata.GetMethodDefinition(method); + if (metadata.GetString(methodDefinition.Name) == name) + return true; + foreach (var implHandle in method.GetMethodImplementations(metadata)) + { + var impl = metadata.GetMethodImplementation(implHandle); + switch (impl.MethodDeclaration.Kind) + { + case HandleKind.MethodDefinition: + var md = metadata.GetMethodDefinition((MethodDefinitionHandle)impl.MethodDeclaration); + if (metadata.GetString(md.Name) != name) + continue; + return true; + case HandleKind.MemberReference: + var mr = metadata.GetMemberReference((MemberReferenceHandle)impl.MethodDeclaration); + if (mr.GetKind() != MemberReferenceKind.Method) + continue; + if (metadata.GetString(mr.Name) != name) + continue; + return true; + default: + continue; + } + } + return false; + } } } diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 49501f5014..901076a22b 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections; using System.Collections.Generic; @@ -44,17 +46,88 @@ namespace ICSharpCode.Decompiler.IL /// public class ILReader { + /// + /// Represents a block of IL instructions. + /// + private sealed class ImportedBlock + { + // These members are immediately available after construction: + public readonly Block Block = new Block(BlockKind.ControlFlow); + public ImmutableStack InputStack; + + public int StartILOffset => Block.StartILOffset; + /// True if the import is in progress or completed. + public bool ImportStarted = false; + + // When the block is imported, Block.Instructions is filled with the imported instructions + // and the following members are initialized: + public List<(ImportedBlock, ImmutableStack)> OutgoingEdges = new(); + + public ImportedBlock(int offset, ImmutableStack inputStack) + { + this.InputStack = inputStack; + this.Block.AddILRange(new Interval(offset, offset)); + } + + /// + /// Compares stack types and update InputStack if necessary. + /// Returns true if InputStack was updated, making a reimport necessary. + /// + public bool MergeStackTypes(ImmutableStack newEdge) + { + var a = this.InputStack; + var b = newEdge; + bool changed = false; + while (!a.IsEmpty && !b.IsEmpty) + { + if (a.Peek().StackType < b.Peek().StackType) + { + changed = true; + } + a = a.Pop(); + b = b.Pop(); + } + if (!changed || !(a.IsEmpty && b.IsEmpty)) + return false; + a = this.InputStack; + b = newEdge; + var output = new List(); + while (!a.IsEmpty && !b.IsEmpty) + { + if (a.Peek().StackType < b.Peek().StackType) + { + output.Add(b.Peek()); + } + else + { + output.Add(a.Peek()); + } + a = a.Pop(); + b = b.Pop(); + } + this.InputStack = ImmutableStack.CreateRange(output); + this.ImportStarted = false; + return true; + } + + internal void ResetForReimport() + { + this.Block.Instructions.Clear(); + this.OutgoingEdges.Clear(); + } + } + readonly ICompilation compilation; readonly MetadataModule module; readonly MetadataReader metadata; public bool UseDebugSymbols { get; set; } - public DebugInfo.IDebugInfoProvider DebugInfo { get; set; } + public DebugInfo.IDebugInfoProvider? DebugInfo { get; set; } public List Warnings { get; } = new List(); // List of candidate locations for sequence points. Includes empty il stack locations, any nop instructions, and the instruction following // a call instruction. - public List SequencePointCandidates { get; private set; } + public List SequencePointCandidates { get; } = new List(); /// /// Creates a new ILReader instance. @@ -69,29 +142,27 @@ public ILReader(MetadataModule module) this.module = module; this.compilation = module.Compilation; this.metadata = module.metadata; - this.SequencePointCandidates = new List(); } + // The members initialized with `null!` are initialized in the Init method. GenericContext genericContext; - IMethod method; - MethodBodyBlock body; + IMethod method = null!; + MethodBodyBlock body = null!; StackType methodReturnStackType; BlobReader reader; - ImmutableStack currentStack; - List expressionStack; - ILVariable[] parameterVariables; - ILVariable[] localVariables; - BitSet isBranchTarget; - BlockContainer mainContainer; - List instructionBuilder; + ImmutableStack currentStack = ImmutableStack.Empty; + ImportedBlock? currentBlock; + List expressionStack = new List(); + ILVariable[] parameterVariables = null!; + ILVariable[] localVariables = null!; + BitSet isBranchTarget = null!; + BlockContainer mainContainer = null!; int currentInstructionStart; - // Dictionary that stores stacks for each IL instruction - Dictionary> stackByOffset; - Dictionary variableByExceptionHandler; - UnionFind unionFind; - List<(ILVariable, ILVariable)> stackMismatchPairs; - IEnumerable stackVariables; + Dictionary blocksByOffset = new Dictionary(); + Queue importQueue = new Queue(); + Dictionary variableByExceptionHandler = new Dictionary(); + IEnumerable? stackVariables; void Init(MethodDefinitionHandle methodDefinitionHandle, MethodBodyBlock body, GenericContext genericContext) { @@ -114,9 +185,7 @@ void Init(MethodDefinitionHandle methodDefinitionHandle, MethodBodyBlock body, G this.body = body; this.reader = body.GetILReader(); this.currentStack = ImmutableStack.Empty; - this.expressionStack = new List(); - this.unionFind = new UnionFind(); - this.stackMismatchPairs = new List<(ILVariable, ILVariable)>(); + this.expressionStack.Clear(); this.methodReturnStackType = method.ReturnType.GetStackType(); InitParameterVariables(); localVariables = InitLocalVariables(); @@ -126,10 +195,10 @@ void Init(MethodDefinitionHandle methodDefinitionHandle, MethodBodyBlock body, G v.UsesInitialValue = true; } this.mainContainer = new BlockContainer(expectedResultType: methodReturnStackType); - this.instructionBuilder = new List(); + this.blocksByOffset.Clear(); + this.importQueue.Clear(); this.isBranchTarget = new BitSet(reader.Length); - this.stackByOffset = new Dictionary>(); - this.variableByExceptionHandler = new Dictionary(); + this.variableByExceptionHandler.Clear(); } EntityHandle ReadAndDecodeMetadataToken() @@ -256,7 +325,7 @@ ILVariable CreateILVariable(int index, IType type) ILVariable CreateILVariable(int index, IType parameterType, string name) { Debug.Assert(!parameterType.IsUnbound()); - ITypeDefinition def = parameterType.GetDefinition(); + ITypeDefinition? def = parameterType.GetDefinition(); if (def != null && index < 0 && def.IsReferenceType == false) { parameterType = new ByReferenceType(parameterType); @@ -282,82 +351,71 @@ void Warn(string message) Warnings.Add(string.Format("IL_{0:x4}: {1}", currentInstructionStart, message)); } - ImmutableStack MergeStacks(ImmutableStack a, ImmutableStack b) + /// + /// Check control flow edges for compatible stacks. + /// Returns union find data structure for unifying the different variables for the same stack slot. + /// Also inserts stack adjustments where necessary. + /// + UnionFind CheckOutgoingEdges() { - if (CheckStackCompatibleWithoutAdjustments(a, b)) + var unionFind = new UnionFind(); + foreach (var block in blocksByOffset.Values) { - // We only need to union the input variables, but can - // otherwise re-use the existing stack. - ImmutableStack output = a; - while (!a.IsEmpty && !b.IsEmpty) - { - Debug.Assert(a.Peek().StackType == b.Peek().StackType); - unionFind.Merge(a.Peek(), b.Peek()); - a = a.Pop(); - b = b.Pop(); - } - return output; - } - else if (a.Count() != b.Count()) - { - // Let's not try to merge mismatched stacks. - Warn("Incompatible stack heights: " + a.Count() + " vs " + b.Count()); - return a; - } - else - { - // The more complex case where the stacks don't match exactly. - var output = new List(); - while (!a.IsEmpty && !b.IsEmpty) + foreach (var (outgoing, stack) in block.OutgoingEdges) { - var varA = a.Peek(); - var varB = b.Peek(); - if (varA.StackType == varB.StackType) + var a = stack; + var b = outgoing.InputStack; + if (a.Count() != b.Count()) { - unionFind.Merge(varA, varB); - output.Add(varA); + // Let's not try to merge mismatched stacks. + Warnings.Add($"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack heights: {a.Count()} vs {b.Count()}"); + continue; } - else + while (!a.IsEmpty && !b.IsEmpty) { - if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType)) - { - Warn("Incompatible stack types: " + varA.StackType + " vs " + varB.StackType); - } - if (varA.StackType > varB.StackType) + var varA = a.Peek(); + var varB = b.Peek(); + if (varA.StackType == varB.StackType) { - output.Add(varA); - // every store to varB should also store to varA - stackMismatchPairs.Add((varB, varA)); + // The stack types match, so we can merge the variables. + unionFind.Merge(varA, varB); } else { - output.Add(varB); - // every store to varA should also store to varB - stackMismatchPairs.Add((varA, varB)); + Debug.Assert(varA.StackType < varB.StackType); + if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType)) + { + Warnings.Add($"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack types: {varA.StackType} vs {varB.StackType}"); + } + InsertStackAdjustment(block.Block, varA, varB); } + a = a.Pop(); + b = b.Pop(); } - a = a.Pop(); - b = b.Pop(); } - // because we built up output by popping from the input stacks, we need to reverse it to get back the original order - output.Reverse(); - return ImmutableStack.CreateRange(output); } + return unionFind; } - static bool CheckStackCompatibleWithoutAdjustments(ImmutableStack a, ImmutableStack b) + /// + /// Inserts a copy from varA to varB (with conversion) at the end of . + /// If the block ends with a branching instruction, the copy is inserted before the branching instruction. + /// + private void InsertStackAdjustment(Block block, ILVariable varA, ILVariable varB) { - while (!a.IsEmpty && !b.IsEmpty) + int insertionPosition = block.Instructions.Count; + while (insertionPosition > 0 && block.Instructions[insertionPosition - 1].HasFlag(InstructionFlags.MayBranch)) { - if (a.Peek().StackType != b.Peek().StackType) - return false; - a = a.Pop(); - b = b.Pop(); + // Branch instruction mustn't be initializing varA. + Debug.Assert(!block.Instructions[insertionPosition - 1].HasFlag(InstructionFlags.MayWriteLocals)); + insertionPosition--; } - return a.IsEmpty && b.IsEmpty; + ILInstruction value = new LdLoc(varA); + value = new Conv(value, varB.StackType.ToPrimitiveType(), false, Sign.Signed); + block.Instructions.Insert(insertionPosition, new StLoc(varB, value) { IsStackAdjustment = true }); } - private bool IsValidTypeStackTypeMerge(StackType stackType1, StackType stackType2) + private static bool IsValidTypeStackTypeMerge(StackType stackType1, StackType stackType2) { if (stackType1 == StackType.I && stackType2 == StackType.I4) return true; @@ -373,42 +431,73 @@ private bool IsValidTypeStackTypeMerge(StackType stackType1, StackType stackType /// /// Stores the given stack for a branch to `offset`. - /// - /// The stack may be modified if stack adjustments are necessary. (e.g. implicit I4->I conversion) /// - void StoreStackForOffset(int offset, ref ImmutableStack stack) + ImportedBlock StoreStackForOffset(int offset, ImmutableStack stack) { - if (stackByOffset.TryGetValue(offset, out var existing)) + if (blocksByOffset.TryGetValue(offset, out var existing)) { - stack = MergeStacks(existing, stack); - if (stack != existing) - stackByOffset[offset] = stack; + bool wasImported = existing.ImportStarted; + if (existing.MergeStackTypes(stack) && wasImported) + { + // If the stack changed, we need to re-import the block. + importQueue.Enqueue(existing); + } + return existing; } else { - stackByOffset.Add(offset, stack); + ImportedBlock newBlock = new ImportedBlock(offset, stack); + blocksByOffset.Add(offset, newBlock); + importQueue.Enqueue(newBlock); + return newBlock; } } void ReadInstructions(CancellationToken cancellationToken) { reader.Reset(); + StoreStackForOffset(0, ImmutableStack.Empty); + if (reader.Length == 0) + { + blocksByOffset[0].Block.Instructions.Add( + new InvalidBranch("Empty body found. Decompiled assembly might be a reference assembly.") + ); + return; + } ILParser.SetBranchTargets(ref reader, isBranchTarget); - reader.Reset(); PrepareBranchTargetsAndStacksForExceptionHandlers(); - bool nextInstructionBeginsNewBlock = false; + // Import of IL byte codes: + while (importQueue.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + ImportedBlock block = importQueue.Dequeue(); + ReadBlock(block, cancellationToken); + } + + // Merge different variables for same stack slot: + var unionFind = CheckOutgoingEdges(); + var visitor = new CollectStackVariablesVisitor(unionFind); + foreach (var block in blocksByOffset.Values) + { + block.Block.AcceptVisitor(visitor); + } + stackVariables = visitor.variables; + } - reader.Reset(); + void ReadBlock(ImportedBlock block, CancellationToken cancellationToken) + { + Debug.Assert(!block.ImportStarted); + block.ResetForReimport(); + block.ImportStarted = true; + reader.Offset = block.StartILOffset; + currentBlock = block; + currentStack = block.InputStack; + // Read instructions until we reach the end of the block. while (reader.RemainingBytes > 0) { cancellationToken.ThrowIfCancellationRequested(); int start = reader.Offset; - if (isBranchTarget[start]) - { - FlushExpressionStack(); - StoreStackForOffset(start, ref currentStack); - } currentInstructionStart = start; bool startedWithEmptyStack = CurrentStackIsEmpty(); DecodedInstruction decodedInstruction; @@ -430,45 +519,50 @@ void ReadInstructions(CancellationToken cancellationToken) { // Flush to avoid re-ordering of side-effects FlushExpressionStack(); - instructionBuilder.Add(inst); + block.Block.Instructions.Add(inst); } - else if (isBranchTarget[start] || nextInstructionBeginsNewBlock) + + if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack) { - // If this instruction is the first in a new block, avoid it being inlined - // into the next instruction. - // This is necessary because the BlockBuilder uses inst.StartILOffset to - // detect block starts, and doesn't search nested instructions. - FlushExpressionStack(); + this.SequencePointCandidates.Add(inst.StartILOffset); } + if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable)) { - FlushExpressionStack(); - if (!stackByOffset.TryGetValue(end, out currentStack)) - { - currentStack = ImmutableStack.Empty; - } - nextInstructionBeginsNewBlock = true; + break; // end of block, don't parse following instructions if they are unreachable } - else + else if (isBranchTarget[end] || inst.HasFlag(InstructionFlags.MayBranch)) { - nextInstructionBeginsNewBlock = inst.HasFlag(InstructionFlags.MayBranch); - } - - if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack) - { - this.SequencePointCandidates.Add(inst.StartILOffset); + break; // end of block (we'll create fall through) } } - + // Finalize block FlushExpressionStack(); - - var visitor = new CollectStackVariablesVisitor(unionFind); - for (int i = 0; i < instructionBuilder.Count; i++) + block.Block.AddILRange(new Interval(block.StartILOffset, reader.Offset)); + if (!block.Block.HasFlag(InstructionFlags.EndPointUnreachable)) { - instructionBuilder[i] = instructionBuilder[i].AcceptVisitor(visitor); + // create fall through branch + ILInstruction branch; + if (reader.RemainingBytes > 0) + { + MarkBranchTarget(reader.Offset, isFallThrough: true); + branch = new Branch(reader.Offset); + } + else + { + branch = new InvalidBranch("End of method reached without returning."); + } + if (block.Block.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop()) + { + // Instead of putting the default branch after the switch instruction + switchInst.Sections.Last().Body = branch; + Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable)); + } + else + { + block.Block.Instructions.Add(branch); + } } - stackVariables = visitor.variables; - InsertStackAdjustments(); } private bool CurrentStackIsEmpty() @@ -482,9 +576,7 @@ private void PrepareBranchTargetsAndStacksForExceptionHandlers() foreach (var eh in body.ExceptionRegions) { // Always mark the start of the try block as a "branch target". - // We don't actually need to store the stack state here, - // but we need to ensure that no ILInstructions are inlined - // into the try-block. + // We need to ensure that we put a block boundary there. isBranchTarget[eh.TryOffset] = true; ImmutableStack ehStack; @@ -514,25 +606,23 @@ private void PrepareBranchTargetsAndStacksForExceptionHandlers() if (eh.FilterOffset != -1) { isBranchTarget[eh.FilterOffset] = true; - StoreStackForOffset(eh.FilterOffset, ref ehStack); + StoreStackForOffset(eh.FilterOffset, ehStack); } if (eh.HandlerOffset != -1) { isBranchTarget[eh.HandlerOffset] = true; - StoreStackForOffset(eh.HandlerOffset, ref ehStack); + StoreStackForOffset(eh.HandlerOffset, ehStack); } } } - private bool IsSequencePointInstruction(ILInstruction instruction) + private static bool IsSequencePointInstruction(ILInstruction instruction) { - if (instruction.OpCode == OpCode.Nop || - (instructionBuilder.Count > 0 - && instructionBuilder.Last().OpCode is OpCode.Call - or OpCode.CallIndirect - or OpCode.CallVirt)) + if (instruction.OpCode is OpCode.Nop + or OpCode.Call + or OpCode.CallIndirect + or OpCode.CallVirt) { - return true; } else @@ -541,39 +631,6 @@ or OpCode.CallIndirect } } - void InsertStackAdjustments() - { - if (stackMismatchPairs.Count == 0) - return; - var dict = new MultiDictionary(); - foreach (var (origA, origB) in stackMismatchPairs) - { - var a = unionFind.Find(origA); - var b = unionFind.Find(origB); - Debug.Assert(a.StackType < b.StackType); - // For every store to a, insert a converting store to b. - if (!dict[a].Contains(b)) - dict.Add(a, b); - } - var newInstructions = new List(); - foreach (var inst in instructionBuilder) - { - newInstructions.Add(inst); - if (inst is StLoc store) - { - foreach (var additionalVar in dict[store.Variable]) - { - ILInstruction value = new LdLoc(store.Variable); - value = new Conv(value, additionalVar.StackType.ToPrimitiveType(), false, Sign.Signed); - newInstructions.Add(new StLoc(additionalVar, value) { - IsStackAdjustment = true, - }.WithILRange(inst)); - } - } - } - instructionBuilder = newInstructions; - } - /// /// Debugging helper: writes the decoded instruction stream interleaved with the inferred evaluation stack layout. /// @@ -582,39 +639,24 @@ public void WriteTypedIL(MethodDefinitionHandle method, MethodBodyBlock body, { Init(method, body, genericContext); ReadInstructions(cancellationToken); - foreach (var inst in instructionBuilder) + foreach (var importBlock in blocksByOffset.Values.OrderBy(b => b.StartILOffset)) { - if (inst is StLoc stloc && stloc.IsStackAdjustment) + output.Write(" ["); + bool isFirstElement = true; + foreach (var element in importBlock.InputStack) { - output.Write(" "); - inst.WriteTo(output, new ILAstWritingOptions()); - output.WriteLine(); - continue; - } - if (stackByOffset.TryGetValue(inst.StartILOffset, out var stack)) - { - output.Write(" ["); - bool isFirstElement = true; - foreach (var element in stack) - { - if (isFirstElement) - isFirstElement = false; - else - output.Write(", "); - output.WriteLocalReference(element.Name, element); - output.Write(":"); - output.Write(element.StackType); - } - output.Write(']'); - output.WriteLine(); + if (isFirstElement) + isFirstElement = false; + else + output.Write(", "); + output.WriteLocalReference(element.Name, element); + output.Write(":"); + output.Write(element.StackType); } - if (isBranchTarget[inst.StartILOffset]) - output.Write('*'); - else - output.Write(' '); - output.WriteLocalReference("IL_" + inst.StartILOffset.ToString("x4"), inst.StartILOffset, isDefinition: true); - output.Write(": "); - inst.WriteTo(output, new ILAstWritingOptions()); + output.Write(']'); + output.WriteLine(); + + importBlock.Block.WriteTo(output, new ILAstWritingOptions()); output.WriteLine(); } new Disassembler.MethodBodyDisassembler(output, cancellationToken) { DetectControlStructure = false } @@ -630,10 +672,11 @@ public ILFunction ReadIL(MethodDefinitionHandle method, MethodBodyBlock body, Ge Init(method, body, genericContext); ReadInstructions(cancellationToken); var blockBuilder = new BlockBuilder(body, variableByExceptionHandler, compilation); - blockBuilder.CreateBlocks(mainContainer, instructionBuilder, isBranchTarget, cancellationToken); + blockBuilder.CreateBlocks(mainContainer, blocksByOffset.Values.Select(ib => ib.Block), cancellationToken); var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind); function.Variables.AddRange(parameterVariables); function.Variables.AddRange(localVariables); + Debug.Assert(stackVariables != null); function.Variables.AddRange(stackVariables); function.Variables.AddRange(variableByExceptionHandler.Values); function.Variables.AddRange(blockBuilder.OnErrorDispatcherVariables); @@ -1180,7 +1223,7 @@ StackType PeekStackType() return currentStack.Peek().StackType; } - class CollectStackVariablesVisitor : ILVisitor + sealed class CollectStackVariablesVisitor : ILVisitor { readonly UnionFind unionFind; internal readonly HashSet variables = new HashSet(); @@ -1209,7 +1252,7 @@ protected internal override ILInstruction VisitLdLoc(LdLoc inst) { var variable = unionFind.Find(inst.Variable); if (variables.Add(variable)) - variable.Name = "S_" + (variables.Count - 1); + variable.Name = $"S_{variables.Count - 1}"; return new LdLoc(variable).WithILRange(inst); } return inst; @@ -1222,7 +1265,7 @@ protected internal override ILInstruction VisitStLoc(StLoc inst) { var variable = unionFind.Find(inst.Variable); if (variables.Add(variable)) - variable.Name = "S_" + (variables.Count - 1); + variable.Name = $"S_{variables.Count - 1}"; return new StLoc(variable, inst.Value).WithILRange(inst); } return inst; @@ -1289,7 +1332,7 @@ ILInstruction Pop(StackType expectedType) return Cast(inst, expectedType, Warnings, reader.Offset); } - internal static ILInstruction Cast(ILInstruction inst, StackType expectedType, List warnings, int ilOffset) + internal static ILInstruction Cast(ILInstruction inst, StackType expectedType, List? warnings, int ilOffset) { if (expectedType != inst.ResultType) { @@ -1444,9 +1487,18 @@ ILInstruction PopLdFldTarget(IField field) private ILInstruction Return() { if (methodReturnStackType == StackType.Void) + { return new IL.Leave(mainContainer); + } + else if (currentInstructionStart == 0) + { + Debug.Assert(expressionStack.Count == 0 && currentStack.IsEmpty); + return new InvalidBranch("Method body consists only of 'ret', but nothing is being returned. Decompiled assembly might be a reference assembly."); + } else + { return new IL.Leave(mainContainer, Pop(methodReturnStackType)); + } } private ILInstruction DecodeLdstr() @@ -1553,7 +1605,7 @@ ILInstruction InitObj(ILInstruction target, IType type) return new StObj(target, value, type); } - IType constrainedPrefix; + IType? constrainedPrefix; private DecodedInstruction DecodeConstrainedCall() { @@ -1906,11 +1958,13 @@ ILInstruction DecodeUnconditionalBranch(ILOpCode opCode, bool isLeave = false) } } - void MarkBranchTarget(int targetILOffset) + void MarkBranchTarget(int targetILOffset, bool isFallThrough = false) { FlushExpressionStack(); - Debug.Assert(isBranchTarget[targetILOffset]); - StoreStackForOffset(targetILOffset, ref currentStack); + Debug.Assert(isFallThrough || isBranchTarget[targetILOffset]); + var targetBlock = StoreStackForOffset(targetILOffset, currentStack); + Debug.Assert(currentBlock != null); + currentBlock.OutgoingEdges.Add((targetBlock, currentStack)); } /// @@ -1921,6 +1975,7 @@ void MarkBranchTarget(int targetILOffset) /// private void FlushExpressionStack() { + Debug.Assert(currentBlock != null); foreach (var inst in expressionStack) { Debug.Assert(inst.ResultType != StackType.Void); @@ -1928,7 +1983,7 @@ private void FlushExpressionStack() var v = new ILVariable(VariableKind.StackSlot, type, inst.ResultType); v.HasGeneratedName = true; currentStack = currentStack.Push(v); - instructionBuilder.Add(new StLoc(v, inst).WithILRange(inst)); + currentBlock.Block.Instructions.Add(new StLoc(v, inst).WithILRange(inst)); } expressionStack.Clear(); } diff --git a/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs b/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs index 5b5767f3b8..e67075aa3f 100644 --- a/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs +++ b/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable // Copyright (c) 2014 Daniel Grunwald // // Permission is hereby granted, free of charge, to any person obtaining a copy of this @@ -78,6 +78,26 @@ public static Sign GetSign(this PrimitiveType primitiveType) } } + public static bool HasOppositeSign(this PrimitiveType primitiveType) + { + switch (primitiveType) + { + case PrimitiveType.I1: + case PrimitiveType.I2: + case PrimitiveType.I4: + case PrimitiveType.I8: + case PrimitiveType.U1: + case PrimitiveType.U2: + case PrimitiveType.U4: + case PrimitiveType.U8: + case PrimitiveType.I: + case PrimitiveType.U: + return true; + default: + return false; + } + } + /// /// Gets the size in bytes of the primitive type. /// diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index bbe6dfaa65..5f2610449c 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -209,7 +209,7 @@ internal void CheckInvariant() /// /// Gets the block container in which this variable is captured. /// For captured variables declared inside the loop, the capture scope is the BlockContainer of the loop. - /// For captured variables declared outside of the loop, the capture scope is the BlockContainer of the parent. + /// For captured variables declared outside of the loop, the capture scope is the BlockContainer of the parent function. /// /// /// This property returns null for variables that are not captured. diff --git a/ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs b/ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs index 75579f41da..be5ebcd2d7 100644 --- a/ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs +++ b/ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs @@ -321,7 +321,7 @@ static void WriteTypeParameterList(ITextOutput output, ILNameSyntax syntax, Syst output.Write('>'); } - static void WriteParameterList(ITextOutput output, MethodSignature> methodSignature) + internal static void WriteParameterList(ITextOutput output, MethodSignature> methodSignature) { output.Write("("); for (int i = 0; i < methodSignature.ParameterTypes.Length; ++i) diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index ab6766660c..c4ce2d8af1 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -19,6 +19,7 @@ #nullable enable using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -121,6 +122,8 @@ public enum OpCode : byte NullableRewrap, /// Loads a constant string. LdStr, + /// Loads a constant byte string (as ReadOnlySpan<byte>). + LdStrUtf8, /// Loads a constant 32-bit integer. LdcI4, /// Loads a constant 64-bit integer. @@ -2838,6 +2841,43 @@ protected internal override bool PerformMatch(ILInstruction? other, ref Patterns } } namespace ICSharpCode.Decompiler.IL +{ + /// Loads a constant byte string (as ReadOnlySpan<byte>). + public sealed partial class LdStrUtf8 : SimpleInstruction + { + public LdStrUtf8(string value) : base(OpCode.LdStrUtf8) + { + this.Value = value; + } + public readonly string Value; + public override StackType ResultType { get { return StackType.O; } } + public override void WriteTo(ITextOutput output, ILAstWritingOptions options) + { + WriteILRange(output, options); + output.Write(OpCode); + output.Write(' '); + Disassembler.DisassemblerHelpers.WriteOperand(output, Value); + } + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitLdStrUtf8(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitLdStrUtf8(this); + } + public override T AcceptVisitor(ILVisitor visitor, C context) + { + return visitor.VisitLdStrUtf8(this, context); + } + protected internal override bool PerformMatch(ILInstruction? other, ref Patterns.Match match) + { + var o = other as LdStrUtf8; + return o != null && this.Value == o.Value; + } + } +} +namespace ICSharpCode.Decompiler.IL { /// Loads a constant 32-bit integer. public sealed partial class LdcI4 : SimpleInstruction @@ -6947,6 +6987,10 @@ protected internal virtual void VisitLdStr(LdStr inst) { Default(inst); } + protected internal virtual void VisitLdStrUtf8(LdStrUtf8 inst) + { + Default(inst); + } protected internal virtual void VisitLdcI4(LdcI4 inst) { Default(inst); @@ -7345,6 +7389,10 @@ protected internal virtual T VisitLdStr(LdStr inst) { return Default(inst); } + protected internal virtual T VisitLdStrUtf8(LdStrUtf8 inst) + { + return Default(inst); + } protected internal virtual T VisitLdcI4(LdcI4 inst) { return Default(inst); @@ -7743,6 +7791,10 @@ protected internal virtual T VisitLdStr(LdStr inst, C context) { return Default(inst, context); } + protected internal virtual T VisitLdStrUtf8(LdStrUtf8 inst, C context) + { + return Default(inst, context); + } protected internal virtual T VisitLdcI4(LdcI4 inst, C context) { return Default(inst, context); @@ -8013,6 +8065,7 @@ partial class InstructionOutputExtensions "nullable.unwrap", "nullable.rewrap", "ldstr", + "ldstr.utf8", "ldc.i4", "ldc.i8", "ldc.f4", @@ -8285,6 +8338,17 @@ public bool MatchLdStr([NotNullWhen(true)] out string? value) value = default(string); return false; } + public bool MatchLdStrUtf8([NotNullWhen(true)] out string? value) + { + var inst = this as LdStrUtf8; + if (inst != null) + { + value = inst.Value; + return true; + } + value = default(string); + return false; + } public bool MatchLdcI4(out int value) { var inst = this as LdcI4; @@ -8751,3 +8815,4 @@ public bool MatchAwait([NotNullWhen(true)] out ILInstruction? value) } } } + diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 18a4cf4c48..72da23a16e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -204,6 +204,8 @@ new OpCode("ldstr", "Loads a constant string.", CustomClassName("LdStr"), LoadConstant("string"), ResultType("O")), + new OpCode("ldstr.utf8", "Loads a constant byte string (as ReadOnlySpan<byte>).", + CustomClassName("LdStrUtf8"), LoadConstant("string"), ResultType("O")), new OpCode("ldc.i4", "Loads a constant 32-bit integer.", LoadConstant("int"), ResultType("I4")), new OpCode("ldc.i8", "Loads a constant 64-bit integer.", diff --git a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs index 0330f079fc..79b79d70ca 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs @@ -88,7 +88,7 @@ public BlockContainer(ContainerKind kind = ContainerKind.Normal, StackType expec public override ILInstruction Clone() { - BlockContainer clone = new BlockContainer(); + BlockContainer clone = new BlockContainer(this.Kind, this.ExpectedResultType); clone.AddILRange(this); clone.Blocks.AddRange(this.Blocks.Select(block => (Block)block.Clone())); // Adjust branch instructions to point to the new container diff --git a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs index c35f986e0d..a25e39a96a 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs @@ -215,8 +215,9 @@ internal static bool IsBinaryCompatibleWithType(BinaryNumericInstruction binary, return false; // operator not supported on pointer types } } - else if (type.IsKnownType(KnownTypeCode.IntPtr) || type.IsKnownType(KnownTypeCode.UIntPtr)) + else if ((type.IsKnownType(KnownTypeCode.IntPtr) || type.IsKnownType(KnownTypeCode.UIntPtr)) && type.Kind is not TypeKind.NInt or TypeKind.NUInt) { + // If the LHS is C# 9 IntPtr (but not nint or C# 11 IntPtr): // "target.intptr *= 2;" is compiler error, but // "target.intptr *= (nint)2;" works if (settings != null && !settings.NativeIntegers) @@ -234,16 +235,17 @@ internal static bool IsBinaryCompatibleWithType(BinaryNumericInstruction binary, } if (binary.Sign != Sign.None) { + bool signMismatchAllowed = (binary.Sign == Sign.Unsigned && binary.Operator == BinaryNumericOperator.ShiftRight && (settings == null || settings.UnsignedRightShift)); if (type.IsCSharpSmallIntegerType()) { // C# will use numeric promotion to int, binary op must be signed - if (binary.Sign != Sign.Signed) + if (binary.Sign != Sign.Signed && !signMismatchAllowed) return false; } else { - // C# will use sign from type - if (type.GetSign() != binary.Sign) + // C# will use sign from type; except for right shift with C# 11 >>> operator. + if (type.GetSign() != binary.Sign && !signMismatchAllowed) return false; } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index 3065392766..bbf0e64419 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable // Copyright (c) 2014 Daniel Grunwald // // Permission is hereby granted, free of charge, to any person obtaining a copy of this @@ -98,6 +98,11 @@ partial class ILFunction /// public bool StateMachineCompiledWithMono; + /// + /// Gets whether the YieldReturnDecompiler determined that the Legacy VB compiler was used to compile this function. + /// + public bool StateMachineCompiledWithLegacyVisualBasic; + /// /// Gets whether this function is async. /// This flag gets set by the AsyncAwaitDecompiler. diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index da87302875..e4331c74be 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -58,6 +58,7 @@ public class AssignVariableNames : IILTransform Dictionary localFunctionMapping; HashSet loopCounters; const char maxLoopVariableName = 'n'; + int numDisplayClassLocals; public void Run(ILFunction function, ILTransformContext context) { @@ -151,6 +152,7 @@ public void Run(ILFunction function, ILTransformContext context) AddExistingName(reservedVariableNames, p.Name); } } + numDisplayClassLocals = 0; foreach (ILFunction f in function.Descendants.OfType().Reverse()) { PerformAssignment(f); @@ -165,11 +167,9 @@ static IEnumerable CollectAllLowerCaseMemberNames(ITypeDefinition type) static IEnumerable CollectAllLowerCaseTypeNames(ITypeDefinition type) { - - foreach (var item in type.ParentModule.TopLevelTypeDefinitions) + var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace); + foreach (var item in ns.Types) { - if (item.Namespace != type.Namespace) - continue; if (IsLowerCase(item.Name)) yield return item.Name; } @@ -197,7 +197,6 @@ void PerformAssignment(ILFunction function) { // remove unused variables before assigning names function.Variables.RemoveDead(); - int numDisplayClassLocals = 0; Dictionary assignedLocalSignatureIndices = new Dictionary(); foreach (var v in function.Variables.OrderBy(v => v.Name)) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs b/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs index b16254a916..59a49c78d1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2016 Siegfried Pammer +// Copyright (c) 2011-2016 Siegfried Pammer // // 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 @@ -55,6 +55,15 @@ public void Run(Block block, BlockTransformContext context) { continue; } + if (CachedDelegateInitializationVBWithReturn(inst)) + { + block.Instructions.RemoveAt(i); + continue; + } + if (CachedDelegateInitializationVBWithClosure(inst)) + { + continue; + } } } } @@ -214,7 +223,7 @@ bool CachedDelegateInitializationVB(IfInstruction inst) { return false; } - if (s.Kind != VariableKind.StackSlot || s.StoreCount != 2 || s.LoadCount != 1) + if (s.Kind != VariableKind.StackSlot || s.StoreCount != 2) return false; if (!(trueInitValue is StObj stobj) || !(falseInitValue is LdObj ldobj)) return false; @@ -236,5 +245,76 @@ bool CachedDelegateInitializationVB(IfInstruction inst) inst.ReplaceWith(new StLoc(s, delegateConstruction)); return true; } + + /// + /// if (comp.o(ldsfld CachedAnonMethodDelegate != ldnull)) { + /// leave IL_0005 (ldsfld CachedAnonMethodDelegate) + /// } + /// leave IL_0005 (stsfld CachedAnonMethodDelegate(DelegateConstruction)) + /// => + /// leave IL_0005 (DelegateConstruction) + /// + bool CachedDelegateInitializationVBWithReturn(IfInstruction inst) + { + if (!inst.Condition.MatchCompNotEqualsNull(out var arg) || !arg.MatchLdsFld(out var field)) + return false; + if (!inst.FalseInst.MatchNop()) + return false; + if (!inst.TrueInst.MatchReturn(out arg) || !arg.MatchLdsFld(field)) + return false; + var leaveAfterIf = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1) as Leave; + if (leaveAfterIf is null || !leaveAfterIf.IsLeavingFunction) + return false; + if (!leaveAfterIf.Value.MatchStsFld(out var field2, out var delegateConstruction) || !field.Equals(field2)) + return false; + if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true)) + return false; + context.Step("CachedDelegateInitializationVBWithReturn", inst); + leaveAfterIf.Value = delegateConstruction; + return true; + } + + /// + /// if (comp.o(ldobj delegateType(ldflda CachedAnonMethodDelegate(ldloc closure)) != ldnull)) Block { + /// stloc s(ldobj delegateType(ldflda CachedAnonMethodDelegate(ldloc closure))) + /// } else Block { + /// stloc s(stobj delegateType(ldflda CachedAnonMethodDelegate(ldloc closure), DelegateConstruction)) + /// } + /// => + /// stloc s(DelegateConstruction) + /// + bool CachedDelegateInitializationVBWithClosure(IfInstruction inst) + { + if (!(inst.TrueInst is Block trueInst && inst.FalseInst is Block falseInst)) + return false; + if (trueInst.Instructions.Count != 1 || falseInst.Instructions.Count != 1) + return false; + if (!(trueInst.Instructions[0].MatchStLoc(out var s, out var trueInitValue) + && falseInst.Instructions[0].MatchStLoc(s, out var falseInitValue))) + { + return false; + } + if (s.Kind != VariableKind.StackSlot || s.StoreCount != 2) + return false; + if (!(falseInitValue is StObj stobj) || !(trueInitValue is LdObj ldobj)) + return false; + if (!(stobj.Value is NewObj delegateConstruction)) + return false; + if (!stobj.Target.MatchLdFlda(out var target1, out var field1) + || !ldobj.Target.MatchLdFlda(out var target2, out var field2) + || !field1.Equals(field2) || !target1.Match(target2).Success) + { + return false; + } + if (!inst.Condition.MatchCompNotEqualsNull(out ILInstruction left)) + return false; + if (!ldobj.Match(left).Success) + return false; + if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true)) + return false; + context.Step("CachedDelegateInitializationVBWithClosure", inst); + inst.ReplaceWith(new StLoc(s, delegateConstruction)); + return true; + } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index f9fa9a7b8c..8a6684c4e0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017 Daniel Grunwald +// Copyright (c) 2014-2017 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 @@ -313,10 +313,10 @@ protected internal override void VisitNewObj(NewObj inst) ILInlining.InlineIfPossible(block, stmt.ChildIndex, context); return; } - if (TransformArrayInitializers.TransformSpanTArrayInitialization(inst, context, out block)) + if (TransformArrayInitializers.TransformSpanTArrayInitialization(inst, context, out var replacement)) { context.Step("TransformSpanTArrayInitialization: single-dim", inst); - inst.ReplaceWith(block); + inst.ReplaceWith(replacement); return; } if (TransformDelegateCtorLdVirtFtnToLdVirtDelegate(inst, out LdVirtDelegate ldVirtDelegate)) @@ -428,11 +428,18 @@ bool TransformDecimalCtorToConstant(NewObj inst, out LdcDecimal result) var args = inst.Arguments; if (args.Count == 1) { - int val; - if (args[0].MatchLdcI4(out val)) + long val; + if (args[0].MatchLdcI(out val)) { - result = new LdcDecimal(val); - return true; + var paramType = inst.Method.Parameters[0].Type.GetDefinition()?.KnownTypeCode; + result = paramType switch { + KnownTypeCode.Int32 => new LdcDecimal(new decimal(unchecked((int)val))), + KnownTypeCode.UInt32 => new LdcDecimal(new decimal(unchecked((uint)val))), + KnownTypeCode.Int64 => new LdcDecimal(new decimal(val)), + KnownTypeCode.UInt64 => new LdcDecimal(new decimal(unchecked((ulong)val))), + _ => null + }; + return result is not null; } } else if (args.Count == 5) @@ -813,7 +820,7 @@ protected internal override void VisitBinaryNumericInstruction(BinaryNumericInst case BinaryNumericOperator.ShiftLeft: case BinaryNumericOperator.ShiftRight: if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs) - && rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31)) + && MatchExpectedShiftSize(rhs)) { // a << (b & 31) => a << b context.Step("Combine bit.and into shift", inst); @@ -831,6 +838,25 @@ protected internal override void VisitBinaryNumericInstruction(BinaryNumericInst } break; } + + bool MatchExpectedShiftSize(ILInstruction rhs) + { + switch (inst.ResultType) + { + case StackType.I4: + return rhs.MatchLdcI4(31); + case StackType.I8: + return rhs.MatchLdcI4(63); + case StackType.I: + // sizeof(IntPtr) * 8 - 1 + return rhs.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub, out var mult, out var one) + && mult.MatchBinaryNumericInstruction(BinaryNumericOperator.Mul, out var size, out var eight) + && size.MatchSizeOf(out var sizeofType) && sizeofType.GetStackType() == StackType.I + && eight.MatchLdcI4(8) && one.MatchLdcI4(1); + default: + return false; + } + } } protected internal override void VisitTryCatchHandler(TryCatchHandler inst) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index b77494f465..e357dfa443 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -494,6 +494,8 @@ internal static bool IsReadonlyReference(ILInstruction addr) // C# doesn't allow mutation of value-type temporaries return true; default: + if (addr.MatchLdFld(out _, out var field)) + return field.ReturnTypeIsRefReadOnly; return false; } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs index 0721134402..c1d73a19b8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using ICSharpCode.Decompiler.IL.Transforms; @@ -29,28 +30,46 @@ public class IntroduceDynamicTypeOnLocals : IILTransform { public void Run(ILFunction function, ILTransformContext context) { - foreach (var variable in function.Variables) + foreach (var nestedFunction in function.Descendants.OfType()) { - if (variable.Kind != VariableKind.Local && - variable.Kind != VariableKind.StackSlot && - variable.Kind != VariableKind.ForeachLocal && - variable.Kind != VariableKind.UsingLocal) + HashSet dynamicVariables = new(); + foreach (var variable in nestedFunction.Variables) { - continue; - } - if (!variable.Type.IsKnownType(KnownTypeCode.Object) || variable.LoadCount == 0) - continue; - foreach (var load in variable.LoadInstructions) - { - if (load.Parent is DynamicInstruction dynamicInstruction) + if (variable.Kind != VariableKind.Local && + variable.Kind != VariableKind.StackSlot && + variable.Kind != VariableKind.ForeachLocal && + variable.Kind != VariableKind.UsingLocal) + { + continue; + } + if (!variable.Type.IsKnownType(KnownTypeCode.Object) || variable.LoadCount == 0) + continue; + foreach (var load in variable.LoadInstructions) { - var argumentInfo = dynamicInstruction.GetArgumentInfoOfChild(load.ChildIndex); - if (!argumentInfo.HasFlag(CSharpArgumentInfoFlags.UseCompileTimeType)) + if (load.Parent is DynamicInstruction dynamicInstruction) { - variable.Type = SpecialType.Dynamic; + var argumentInfo = dynamicInstruction.GetArgumentInfoOfChild(load.ChildIndex); + if (!argumentInfo.HasFlag(CSharpArgumentInfoFlags.UseCompileTimeType)) + { + variable.Type = SpecialType.Dynamic; + if (variable.Index.HasValue && variable.Kind == VariableKind.Local) + { + dynamicVariables.Add(variable.Index.Value); + } + break; + } } } } + foreach (var variable in nestedFunction.Variables) + { + if (variable.Index.HasValue && variable.Kind == VariableKind.Local + && dynamicVariables.Contains(variable.Index.Value)) + { + variable.Type = SpecialType.Dynamic; + continue; + } + } } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs index 910a4726af..a6fee58a7b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs @@ -32,23 +32,37 @@ public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.NativeIntegers) return; - foreach (var variable in function.Variables) + foreach (var nestedFunction in function.Descendants.OfType()) { - if (variable.Kind != VariableKind.Local && - variable.Kind != VariableKind.StackSlot && - variable.Kind != VariableKind.PatternLocal && - variable.Kind != VariableKind.ForeachLocal && - variable.Kind != VariableKind.UsingLocal) + Dictionary variableTypeMapping = new(); + foreach (var variable in nestedFunction.Variables) { - continue; + if (variable.Kind != VariableKind.Local && + variable.Kind != VariableKind.StackSlot && + variable.Kind != VariableKind.PatternLocal && + 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; + if (variable.Kind == VariableKind.Local && variable.Index.HasValue) + variableTypeMapping[variable.Index.Value] = variable.Type; + } } - 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) + foreach (var variable in nestedFunction.Variables) { - variable.Type = variable.Type.GetSign() == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt; + if (variable.Kind == VariableKind.Local && variable.Index.HasValue + && variableTypeMapping.TryGetValue(variable.Index.Value, out var type)) + { + variable.Type = type; + } } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 1a10d2c7a4..ebc9a149a0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -493,7 +493,7 @@ int GetSkipCount(ILFunction rootFunction, IMethod targetMethod) } if (targetMethod.TypeParameters.Count > 0) { - var lastParams = targetMethod.Parameters.Where(p => IsClosureParameter(p, this.resolveContext)).SelectMany(p => UnwrapByRef(p.Type).TypeArguments) + var lastParams = targetMethod.Parameters.Where(p => IsClosureParameter(p, this.resolveContext)).SelectMany(p => p.Type.UnwrapByRef().TypeArguments) .Select(pt => (int?)targetMethod.TypeParameters.IndexOf(pt)).DefaultIfEmpty().Max(); if (lastParams != null && lastParams.GetValueOrDefault() + 1 > skipCount) skipCount = lastParams.GetValueOrDefault() + 1; @@ -564,15 +564,6 @@ internal static bool IsClosureParameter(IParameter parameter, ITypeResolveContex && TransformDisplayClassUsage.IsPotentialClosure(context.CurrentTypeDefinition, type); } - static IType UnwrapByRef(IType type) - { - if (type is ByReferenceType byRef) - { - type = byRef.ElementType; - } - return type; - } - internal static ILInstruction GetStatement(ILInstruction inst) { while (inst.Parent != null) @@ -733,7 +724,7 @@ bool DetermineCaptureAndDeclarationScope(LocalFunctionInfo info, int parameterIn ILInstruction GetClosureInitializer(ILVariable variable) { - var type = UnwrapByRef(variable.Type).GetDefinition(); + var type = variable.Type.UnwrapByRef().GetDefinition(); if (type == null) return null; if (variable.Kind == VariableKind.Parameter) diff --git a/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs index 072d344fc3..5863db1686 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Siegfried Pammer +// Copyright (c) 2017 Siegfried Pammer // // 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 @@ -39,8 +39,9 @@ void IBlockTransform.Run(Block block, BlockTransformContext context) { if (!TransformLockRoslyn(block, i)) if (!TransformLockV4(block, i)) - if (!TransformLockV2(block, i)) - TransformLockMCS(block, i); + if (!TransformLockV4YieldReturn(block, i)) + if (!TransformLockV2(block, i)) + TransformLockMCS(block, i); // This happens in some cases: // Use correct index after transformation. if (i >= block.Instructions.Count) @@ -183,6 +184,52 @@ bool TransformLockV4(Block block, int i) return true; } + /// + /// stloc flag(ldc.i4 0) + /// .try BlockContainer { + /// Block lockBlock (incoming: 1) { + /// stloc obj1(stloc obj2(lockObj)) + /// call Enter(ldloc obj2, ldloca flag) + /// call WriteLine() + /// leave lockBlock (nop) + /// } + /// } finally BlockContainer { + /// Block (incoming: 1) { + /// if (ldloc flag) Block { + /// call Exit(ldloc obj1) + /// } + /// leave lockBlock (nop) + /// } + /// } + /// => + /// .lock (lockObj) BlockContainer { + /// Block lockBlock (incoming: 1) { + /// call WriteLine() + /// leave lockBlock (nop) + /// } + /// } + /// + bool TransformLockV4YieldReturn(Block block, int i) + { + if (i < 1) + return false; + if (!(block.Instructions[i] is TryFinally body) || !(block.Instructions[i - 1] is StLoc flagStore)) + return false; + if (!flagStore.Variable.Type.IsKnownType(KnownTypeCode.Boolean) || !flagStore.Value.MatchLdcI4(0)) + return false; + if (!(body.TryBlock is BlockContainer tryContainer) || !MatchLockEntryPoint(tryContainer.EntryPoint, flagStore.Variable, out ILVariable exitVariable, out var objectStore)) + return false; + if (!(body.FinallyBlock is BlockContainer finallyContainer) || !MatchExitBlock(finallyContainer.EntryPoint, flagStore.Variable, exitVariable)) + return false; + if (objectStore.Variable.LoadCount > 1) + return false; + context.Step("LockTransformV4YieldReturn", block); + block.Instructions.RemoveAt(i - 1); + tryContainer.EntryPoint.Instructions.RemoveRange(0, 2); + body.ReplaceWith(new LockInstruction(objectStore.Value, body.TryBlock).WithILRange(objectStore)); + return true; + } + /// /// stloc obj(lockObj) /// stloc flag(ldc.i4 0) @@ -279,6 +326,23 @@ bool MatchLockEntryPoint(Block entryPoint, ILVariable flag, out StLoc obj) return true; } + bool MatchLockEntryPoint(Block entryPoint, ILVariable flag, out ILVariable exitVairable, out StLoc obj) + { + // This pattern is commonly seen in yield return state machines emitted by the legacy C# compiler. + obj = null; + exitVairable = null; + if (entryPoint.Instructions.Count < 1 || entryPoint.IncomingEdgeCount != 1) + return false; + if (entryPoint.Instructions[0].MatchStLoc(out exitVairable, out var value) && value is StLoc nestedStloc) + { + obj = nestedStloc; + if (!MatchCall(entryPoint.Instructions[1] as Call, "Enter", nestedStloc.Variable, flag)) + return false; + return true; + } + return false; + } + bool MatchCall(Call call, string methodName, ILVariable flag, out StLoc obj) { obj = null; diff --git a/ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs index 8e0b26fb97..7deb3d9eaf 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs @@ -31,8 +31,10 @@ class ParameterNullCheckTransform : IILTransform { void IILTransform.Run(ILFunction function, ILTransformContext context) { +#pragma warning disable 618 // ParameterNullCheck is obsolete if (!context.Settings.ParameterNullCheck) return; +#pragma warning restore 618 // we only need to look at the entry-point as parameter null-checks // do not produce any IL control-flow instructions Block entryPoint = ((BlockContainer)function.Body).EntryPoint; diff --git a/ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs index ec4c1f7dd9..2876f141e7 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Siegfried Pammer +// Copyright (c) 2018 Siegfried Pammer // // 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 @@ -637,10 +637,25 @@ leave IL_003e (nop) // Finally is empty and redundant. But we'll delete the block only if there's a PinnedRegion. if (!(tryFinally.TryBlock is BlockContainer tryContainer)) return; - if (tryContainer.SingleInstruction() is PinnedRegion pinnedRegion) + if (tryContainer.Blocks.Count != 1) + return; + var tryBlock = tryContainer.Blocks[0]; + if (tryBlock.Instructions.Count == 1) { - context.Step("Removing try-finally around PinnedRegion", pinnedRegion); - tryFinally.ReplaceWith(pinnedRegion); + if (tryBlock.Instructions[0] is PinnedRegion pinnedRegion) + { + context.Step("Removing try-finally around PinnedRegion", pinnedRegion); + tryFinally.ReplaceWith(pinnedRegion); + } + } + else if (tryBlock.Instructions.Count == 2) + { + if (tryBlock.Instructions[0] is PinnedRegion pinnedRegion && + tryBlock.Instructions[1].MatchLeave(tryContainer)) + { + context.Step("Removing try-finally around PinnedRegion", pinnedRegion); + tryFinally.ReplaceWith(pinnedRegion); + } } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 3bb5cc7978..1944872a84 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection.Metadata; +using System.Text; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; @@ -113,9 +114,9 @@ bool DoTransform(ILFunction function, Block body, int pos) return false; } - internal static bool TransformSpanTArrayInitialization(NewObj inst, StatementTransformContext context, out Block block) + internal static bool TransformSpanTArrayInitialization(NewObj inst, StatementTransformContext context, out ILInstruction replacement) { - block = null; + replacement = null; if (!context.Settings.ArrayInitializers) return false; if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size)) @@ -124,10 +125,17 @@ internal static bool TransformSpanTArrayInitialization(NewObj inst, StatementTra { var valuesList = new List(); var initialValue = field.GetInitialValue(context.PEFile.Reader, context.TypeSystem); + if (context.Settings.Utf8StringLiterals && + elementType.IsKnownType(KnownTypeCode.Byte) && + DecodeUTF8String(initialValue, size, out string text)) + { + replacement = new LdStrUtf8(text); + return true; + } if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) { var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); - block = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); + replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); return true; } } @@ -135,13 +143,36 @@ internal static bool TransformSpanTArrayInitialization(NewObj inst, StatementTra return false; } + private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text) + { + if (size > blob.RemainingBytes) + { + text = null; + return false; + } + for (int i = 0; i < size; i++) + { + byte val = blob.CurrentPointer[i]; + // If the string has control characters, it's probably binary data and not a string. + if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t')) + { + text = null; + return false; + } + } + text = Encoding.UTF8.GetString(blob.CurrentPointer, size); + // Only use UTF8 string literal if we can perfectly roundtrip the data + byte[] bytes = Encoding.UTF8.GetBytes(text); + return MemoryExtensions.SequenceEqual(bytes, new ReadOnlySpan(blob.CurrentPointer, size)); + } + static bool MatchSpanTCtorWithPointerAndSize(NewObj newObj, StatementTransformContext context, out IType elementType, out FieldDefinition field, out int size) { field = default; size = default; elementType = null; IType type = newObj.Method.DeclaringType; - if (!type.IsKnownType(KnownTypeCode.SpanOfT) && !type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + if (!type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) return false; if (newObj.Arguments.Count != 2 || type.TypeArguments.Count != 1) return false; diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs index ee33433957..849ac88401 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Siegfried Pammer +// Copyright (c) 2015 Siegfried Pammer // // 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 @@ -145,11 +145,17 @@ bool TransformInlineAssignmentStObjOrCall(Block block, int pos) else if (pointerType is PointerType pointer) newType = pointer.ElementType; } - if (IsImplicitTruncation(inst.Value, newType, context.TypeSystem)) + var truncation = CheckImplicitTruncation(inst.Value, newType, context.TypeSystem); + if (truncation == ImplicitTruncationResult.ValueChanged) { // 'stobj' is implicitly truncating the value return false; } + if (truncation == ImplicitTruncationResult.ValueChangedDueToSignMismatch) + { + // Change the sign of the type to skip implicit truncation + newType = SwapSign(newType, context.TypeSystem); + } context.Step("Inline assignment stobj", stobj); stobj.Type = newType; block.Instructions.Remove(localStore); @@ -214,6 +220,23 @@ bool TransformInlineAssignmentStObjOrCall(Block block, int pos) } } + private static IType SwapSign(IType type, ICompilation compilation) + { + return type.ToPrimitiveType() switch { + PrimitiveType.I1 => compilation.FindType(KnownTypeCode.Byte), + PrimitiveType.I2 => compilation.FindType(KnownTypeCode.UInt16), + PrimitiveType.I4 => compilation.FindType(KnownTypeCode.UInt32), + PrimitiveType.I8 => compilation.FindType(KnownTypeCode.UInt64), + PrimitiveType.U1 => compilation.FindType(KnownTypeCode.SByte), + PrimitiveType.U2 => compilation.FindType(KnownTypeCode.Int16), + PrimitiveType.U4 => compilation.FindType(KnownTypeCode.Int32), + PrimitiveType.U8 => compilation.FindType(KnownTypeCode.Int64), + PrimitiveType.I => compilation.FindType(KnownTypeCode.UIntPtr), + PrimitiveType.U => compilation.FindType(KnownTypeCode.IntPtr), + _ => throw new ArgumentException("Type must have an opposing sign: " + type, nameof(type)) + }; + } + static ILInstruction UnwrapSmallIntegerConv(ILInstruction inst, out Conv conv) { conv = inst as Conv; @@ -363,7 +386,7 @@ internal static bool HandleCompoundAssign(ILInstruction compoundStore, Statement ILInstruction rhs; if (operatorCall.Arguments.Count == 2) { - if (CSharp.ExpressionBuilder.GetAssignmentOperatorTypeFromMetadataName(operatorCall.Method.Name) == null) + if (CSharp.ExpressionBuilder.GetAssignmentOperatorTypeFromMetadataName(operatorCall.Method.Name, context.Settings) == null) return false; rhs = operatorCall.Arguments[1]; } @@ -507,46 +530,69 @@ bool TransformInlineAssignmentLocal(Block block, int pos) return true; } + internal static bool IsImplicitTruncation(ILInstruction value, IType type, ICompilation compilation, bool allowNullableValue = false) + { + return CheckImplicitTruncation(value, type, compilation, allowNullableValue) != ImplicitTruncationResult.ValuePreserved; + } + + + internal enum ImplicitTruncationResult : byte + { + /// + /// The value is not implicitly truncated. + /// + ValuePreserved, + /// + /// The value is implicitly truncated. + /// + ValueChanged, + /// + /// The value is implicitly truncated, but the sign of the target type can be changed to remove the truncation. + /// + ValueChangedDueToSignMismatch + } + /// /// Gets whether 'stobj type(..., value)' would evaluate to a different value than 'value' /// due to implicit truncation. /// - static internal bool IsImplicitTruncation(ILInstruction value, IType type, ICompilation compilation, bool allowNullableValue = false) + internal static ImplicitTruncationResult CheckImplicitTruncation(ILInstruction value, IType type, ICompilation compilation, bool allowNullableValue = false) { if (!type.IsSmallIntegerType()) { // Implicit truncation in ILAst only happens for small integer types; // other types of implicit truncation in IL cause the ILReader to insert // conv instructions. - return false; + return ImplicitTruncationResult.ValuePreserved; } // With small integer types, test whether the value might be changed by // truncation (based on type.GetSize()) followed by sign/zero extension (based on type.GetSign()). // (it's OK to have false-positives here if we're unsure) if (value.MatchLdcI4(out int val)) { - switch (type.GetEnumUnderlyingType().GetDefinition()?.KnownTypeCode) - { - case KnownTypeCode.Boolean: - return !(val == 0 || val == 1); - case KnownTypeCode.Byte: - return !(val >= byte.MinValue && val <= byte.MaxValue); - case KnownTypeCode.SByte: - return !(val >= sbyte.MinValue && val <= sbyte.MaxValue); - case KnownTypeCode.Int16: - return !(val >= short.MinValue && val <= short.MaxValue); - case KnownTypeCode.UInt16: - case KnownTypeCode.Char: - return !(val >= ushort.MinValue && val <= ushort.MaxValue); - } + bool valueFits = (type.GetEnumUnderlyingType().GetDefinition()?.KnownTypeCode) switch { + KnownTypeCode.Boolean => val == 0 || val == 1, + KnownTypeCode.Byte => val >= byte.MinValue && val <= byte.MaxValue, + KnownTypeCode.SByte => val >= sbyte.MinValue && val <= sbyte.MaxValue, + KnownTypeCode.Int16 => val >= short.MinValue && val <= short.MaxValue, + KnownTypeCode.UInt16 or KnownTypeCode.Char => val >= ushort.MinValue && val <= ushort.MaxValue, + _ => false + }; + return valueFits ? ImplicitTruncationResult.ValuePreserved : ImplicitTruncationResult.ValueChanged; } else if (value is Conv conv) { - return conv.TargetType != type.ToPrimitiveType(); + PrimitiveType primitiveType = type.ToPrimitiveType(); + PrimitiveType convTargetType = conv.TargetType; + if (convTargetType == primitiveType) + return ImplicitTruncationResult.ValuePreserved; + if (primitiveType.GetSize() == convTargetType.GetSize() && primitiveType.GetSign() != convTargetType.GetSign() && primitiveType.HasOppositeSign()) + return ImplicitTruncationResult.ValueChangedDueToSignMismatch; + return ImplicitTruncationResult.ValueChanged; } else if (value is Comp) { - return false; // comp returns 0 or 1, which always fits + return ImplicitTruncationResult.ValuePreserved; // comp returns 0 or 1, which always fits } else if (value is BinaryNumericInstruction bni) { @@ -557,14 +603,22 @@ static internal bool IsImplicitTruncation(ILInstruction value, IType type, IComp case BinaryNumericOperator.BitXor: // If both input values fit into the type without truncation, // the result of a binary operator will also fit. - return IsImplicitTruncation(bni.Left, type, compilation, allowNullableValue) - || IsImplicitTruncation(bni.Right, type, compilation, allowNullableValue); + var leftTruncation = CheckImplicitTruncation(bni.Left, type, compilation, allowNullableValue); + // If the left side is truncating and a sign change is not possible we do not need to evaluate the right side + if (leftTruncation == ImplicitTruncationResult.ValueChanged) + return ImplicitTruncationResult.ValueChanged; + var rightTruncation = CheckImplicitTruncation(bni.Right, type, compilation, allowNullableValue); + return CommonImplicitTruncation(leftTruncation, rightTruncation); } } else if (value is IfInstruction ifInst) { - return IsImplicitTruncation(ifInst.TrueInst, type, compilation, allowNullableValue) - || IsImplicitTruncation(ifInst.FalseInst, type, compilation, allowNullableValue); + var trueTruncation = CheckImplicitTruncation(ifInst.TrueInst, type, compilation, allowNullableValue); + // If the true branch is truncating and a sign change is not possible we do not need to evaluate the false branch + if (trueTruncation == ImplicitTruncationResult.ValueChanged) + return ImplicitTruncationResult.ValueChanged; + var falseTruncation = CheckImplicitTruncation(ifInst.FalseInst, type, compilation, allowNullableValue); + return CommonImplicitTruncation(trueTruncation, falseTruncation); } else { @@ -575,10 +629,28 @@ static internal bool IsImplicitTruncation(ILInstruction value, IType type, IComp } if (inferredType.Kind != TypeKind.Unknown) { - return !(inferredType.GetSize() <= type.GetSize() && inferredType.GetSign() == type.GetSign()); + var inferredPrimitive = inferredType.ToPrimitiveType(); + var primitiveType = type.ToPrimitiveType(); + + bool sameSign = inferredPrimitive.GetSign() == primitiveType.GetSign(); + if (inferredPrimitive.GetSize() <= primitiveType.GetSize() && sameSign) + return ImplicitTruncationResult.ValuePreserved; + if (inferredPrimitive.GetSize() == primitiveType.GetSize() && !sameSign && primitiveType.HasOppositeSign()) + return ImplicitTruncationResult.ValueChangedDueToSignMismatch; + return ImplicitTruncationResult.ValueChanged; } } - return true; + // In unknown cases, assume that the value might be changed by truncation. + return ImplicitTruncationResult.ValueChanged; + } + + private static ImplicitTruncationResult CommonImplicitTruncation(ImplicitTruncationResult left, ImplicitTruncationResult right) + { + if (left == right) + return left; + // Note: in all cases where left!=right, we return ValueChanged: + // if only one side can be fixed by changing the sign, we don't want to change the sign of the other side. + return ImplicitTruncationResult.ValueChanged; } /// @@ -790,6 +862,14 @@ bool TransformPostIncDecOperatorWithInlineStore(Block block, int pos) { if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub)) return false; + + if (conv is not null) + { + var primitiveType = targetType.ToPrimitiveType(); + if (primitiveType.GetSize() == conv.TargetType.GetSize() && primitiveType.GetSign() != conv.TargetType.GetSign()) + targetType = SwapSign(targetType, context.TypeSystem); + } + if (!ValidateCompoundAssign(binary, conv, targetType, context.Settings)) return false; stloc = binary.Left as StLoc; @@ -858,11 +938,20 @@ bool TransformPostIncDecOperator(Block block, int i) var tmpVar = inst.Variable; if (!IsCompoundStore(store, out var targetType, out var value, context.TypeSystem)) return false; - if (IsImplicitTruncation(inst.Value, targetType, context.TypeSystem)) + var truncation = CheckImplicitTruncation(inst.Value, targetType, context.TypeSystem); + if (truncation == ImplicitTruncationResult.ValueChanged) { // 'stloc tmp' is implicitly truncating the value return false; } + if (truncation == ImplicitTruncationResult.ValueChangedDueToSignMismatch) + { + if (!(store is StObj stObj && stObj.Type.Equals(targetType))) + { + // We cannot apply the sign change, so we can't fix the truncation + return false; + } + } if (!IsMatchingCompoundLoad(inst.Value, store, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: inst.Variable, previousInstruction: block.Instructions.ElementAtOrDefault(i - 1))) @@ -871,10 +960,23 @@ bool TransformPostIncDecOperator(Block block, int i) } if (UnwrapSmallIntegerConv(value, out var conv) is BinaryNumericInstruction binary) { - if (!binary.Left.MatchLdLoc(tmpVar) || !(binary.Right.MatchLdcI(1) || binary.Right.MatchLdcF4(1) || binary.Right.MatchLdcF8(1))) - return false; if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub)) return false; + if (!binary.Left.MatchLdLoc(tmpVar)) + return false; + if (targetType is PointerType ptrType) + { + var right = PointerArithmeticOffset.Detect(binary.Right, ptrType.ElementType, binary.CheckForOverflow); + if (right is null || !right.MatchLdcI(1)) + return false; + } + else if (!(binary.Right.MatchLdcI(1) || binary.Right.MatchLdcF4(1) || binary.Right.MatchLdcF8(1))) + return false; + if (truncation == ImplicitTruncationResult.ValueChangedDueToSignMismatch && store is StObj stObj) + { + // Change the sign of the type to skip implicit truncation + stObj.Type = targetType = SwapSign(targetType, context.TypeSystem); + } if (!ValidateCompoundAssign(binary, conv, targetType, context.Settings)) return false; context.Step("TransformPostIncDecOperator (builtin)", inst); @@ -891,6 +993,7 @@ bool TransformPostIncDecOperator(Block block, int i) if (operatorCall.IsLifted) return false; // TODO: add tests and think about whether nullables need special considerations context.Step("TransformPostIncDecOperator (user-defined)", inst); + Debug.Assert(truncation == ImplicitTruncationResult.ValuePreserved); finalizeMatch?.Invoke(context); inst.Value = new UserDefinedCompoundAssign(operatorCall.Method, CompoundEvalMode.EvaluatesToOldValue, target, targetKind, new LdcI4(1)); diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs index 75d3006da8..1f8845d930 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -44,13 +46,14 @@ void IStatementTransform.Run(Block block, int pos, StatementTransformContext con IType instType; var blockKind = BlockKind.CollectionInitializer; var insertionPos = initInst.ChildIndex; - var siblings = initInst.Parent.Children; + var siblings = initInst.Parent!.Children; + IMethod currentMethod = context.Function.Method!; switch (initInst) { case NewObj newObjInst: if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local - && !context.Function.Method.IsConstructor - && !context.Function.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) + && !currentMethod.IsConstructor + && !currentMethod.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) { // on statement level (no other expressions on IL stack), // prefer to keep local variables (but not stack slots), @@ -70,7 +73,7 @@ void IStatementTransform.Run(Block block, int pos, StatementTransformContext con instType = newObjInst.Method.DeclaringType; break; case DefaultValue defaultVal: - if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor) + if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !currentMethod.IsConstructor) { // on statement level (no other expressions on IL stack), // prefer to keep local variables (but not stack slots), @@ -100,10 +103,10 @@ void IStatementTransform.Run(Block block, int pos, StatementTransformContext con return; } int initializerItemsCount = 0; - possibleIndexVariables = new Dictionary(); - currentPath = new List(); + possibleIndexVariables.Clear(); + currentPath.Clear(); isCollection = false; - pathStack = new Stack>(); + pathStack.Clear(); pathStack.Push(new HashSet()); // Detect initializer type by scanning the following statements // each must be a callvirt with ldloc v as first argument @@ -192,10 +195,10 @@ bool IsMethodCallOnVariable(ILInstruction inst, ILVariable variable) return false; } - Dictionary possibleIndexVariables; - List currentPath; + readonly Dictionary possibleIndexVariables = new Dictionary(); + readonly List currentPath = new List(); bool isCollection; - Stack> pathStack; + readonly Stack> pathStack = new Stack>(); bool IsPartOfInitializer(InstructionCollection instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, StatementTransformContext context) { @@ -248,7 +251,7 @@ bool IsPartOfInitializer(InstructionCollection instructions, int case AccessPathKind.Setter: if (isCollection || !pathStack.Peek().Add(lastElement)) return false; - if (values.Count != 1 || !IsValidObjectInitializerTarget(currentPath)) + if (values?.Count != 1 || !IsValidObjectInitializerTarget(currentPath)) return false; if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer) blockKind = BlockKind.ObjectInitializer; @@ -264,9 +267,16 @@ bool IsValidObjectInitializerTarget(List path) return true; var element = path.Last(); var previous = path.SkipLast(1).LastOrDefault(); - if (!(element.Member is IProperty p)) + if (element.Member is not IProperty p) + return true; + if (!p.IsIndexer) return true; - return !p.IsIndexer || NormalizeTypeVisitor.IgnoreNullabilityAndTuples.EquivalentTypes(previous.Member?.ReturnType, element.Member.DeclaringType); + if (previous != default) + { + return NormalizeTypeVisitor.IgnoreNullabilityAndTuples + .EquivalentTypes(previous.Member.ReturnType, element.Member.DeclaringType); + } + return false; } } @@ -279,7 +289,7 @@ public enum AccessPathKind public struct AccessPathElement : IEquatable { - public AccessPathElement(OpCode opCode, IMember member, ILInstruction[] indices = null) + public AccessPathElement(OpCode opCode, IMember member, ILInstruction[]? indices = null) { this.OpCode = opCode; this.Member = member; @@ -288,24 +298,24 @@ public AccessPathElement(OpCode opCode, IMember member, ILInstruction[] indices public readonly OpCode OpCode; public readonly IMember Member; - public readonly ILInstruction[] Indices; + public readonly ILInstruction[]? Indices; public override string ToString() => $"[{Member}, {Indices}]"; - public static (AccessPathKind Kind, List Path, List Values, ILVariable Target) GetAccessPath( - ILInstruction instruction, IType rootType, DecompilerSettings settings = null, - CSharpTypeResolveContext resolveContext = null, - Dictionary possibleIndexVariables = null) + public static (AccessPathKind Kind, List Path, List? Values, ILVariable? Target) GetAccessPath( + ILInstruction instruction, IType rootType, DecompilerSettings? settings = null, + CSharpTypeResolveContext? resolveContext = null, + Dictionary? possibleIndexVariables = null) { List path = new List(); - ILVariable target = null; + ILVariable? target = null; AccessPathKind kind = AccessPathKind.Invalid; - List values = null; + List? values = null; IMethod method; - var inst = instruction; - while (instruction != null) + ILInstruction? inst = instruction; + while (inst != null) { - switch (instruction) + switch (inst) { case CallInstruction call: if (!(call is CallVirt || call is Call)) @@ -313,12 +323,15 @@ public static (AccessPathKind Kind, List Path, List 0 && settings?.DictionaryInitializers == false) @@ -359,7 +372,7 @@ public static (AccessPathKind Kind, List Path, List Path, List(new[] { stobj.Value }); @@ -381,35 +394,35 @@ public static (AccessPathKind Kind, List Path, List v.Descendants).OfType().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca))) + if (kind != AccessPathKind.Invalid && values != null && values.SelectMany(v => v.Descendants).OfType().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca))) kind = AccessPathKind.Invalid; return (kind, path, values, target); } - private static bool CanBeUsedInInitializer(IProperty property, CSharpTypeResolveContext resolveContext, AccessPathKind kind, List path) + private static bool CanBeUsedInInitializer(IProperty property, CSharpTypeResolveContext? resolveContext, AccessPathKind kind) { if (property.CanSet && (property.Accessibility == property.Setter.Accessibility || IsAccessorAccessible(property.Setter, resolveContext))) return true; return kind != AccessPathKind.Setter; } - private static bool IsAccessorAccessible(IMethod setter, CSharpTypeResolveContext resolveContext) + private static bool IsAccessorAccessible(IMethod setter, CSharpTypeResolveContext? resolveContext) { if (resolveContext == null) return true; @@ -417,7 +430,7 @@ private static bool IsAccessorAccessible(IMethod setter, CSharpTypeResolveContex return lookup.IsAccessible(setter, allowProtectedAccess: setter.DeclaringTypeDefinition == resolveContext.CurrentTypeDefinition); } - static bool IsMethodApplicable(IMethod method, IReadOnlyList arguments, IType rootType, CSharpTypeResolveContext resolveContext, DecompilerSettings settings) + static bool IsMethodApplicable(IMethod method, IReadOnlyList arguments, IType rootType, CSharpTypeResolveContext resolveContext, DecompilerSettings? settings) { if (method.IsStatic && !method.IsExtensionMethod) return false; @@ -453,7 +466,7 @@ out bool success } } - static IType GetReturnTypeFromInstruction(ILInstruction instruction) + static IType? GetReturnTypeFromInstruction(ILInstruction instruction) { switch (instruction) { @@ -474,7 +487,7 @@ static IType GetReturnTypeFromInstruction(ILInstruction instruction) } } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is AccessPathElement) return Equals((AccessPathElement)obj); @@ -494,8 +507,10 @@ public override int GetHashCode() public bool Equals(AccessPathElement other) { - return other.Member.Equals(this.Member) - && (other.Indices == this.Indices || other.Indices.SequenceEqual(this.Indices, ILInstructionMatchComparer.Instance)); + return (other.Member == this.Member + || this.Member.Equals(other.Member)) + && (other.Indices == this.Indices + || (other.Indices != null && this.Indices != null && this.Indices.SequenceEqual(other.Indices, ILInstructionMatchComparer.Instance))); } public static bool operator ==(AccessPathElement lhs, AccessPathElement rhs) @@ -513,7 +528,7 @@ class ILInstructionMatchComparer : IEqualityComparer { public static readonly ILInstructionMatchComparer Instance = new ILInstructionMatchComparer(); - public bool Equals(ILInstruction x, ILInstruction y) + public bool Equals(ILInstruction? x, ILInstruction? y) { if (x == y) return true; diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index 5f69000d1a..a62e16eb29 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2019 Siegfried Pammer +// Copyright (c) 2019 Siegfried Pammer // // 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 @@ -289,7 +289,7 @@ DisplayClass DetectDisplayClass(ILVariable v) return null; if (!(v.StoreInstructions.SingleOrDefault() is StLoc stloc)) return null; - if (stloc.Value is NewObj newObj && ValidateConstructor(newObj.Method)) + if (stloc.Value is NewObj newObj && ValidateConstructor(context, newObj.Method)) { result = new DisplayClass(v, definition) { CaptureScope = v.CaptureScope, @@ -393,7 +393,7 @@ DisplayClass DetectDisplayClassInitializer(ILVariable v) var definition = newObj.Method.DeclaringType.GetDefinition(); if (!ValidateDisplayClassDefinition(definition)) return null; - if (!ValidateConstructor(newObj.Method)) + if (!ValidateConstructor(context, newObj.Method)) return null; if (!initializerBlock.Parent.MatchStLoc(out var referenceVariable)) return null; @@ -432,7 +432,7 @@ private bool ValidateDisplayClassDefinition(ITypeDefinition definition) return true; } - private bool ValidateConstructor(IMethod method) + internal static bool ValidateConstructor(ILTransformContext context, IMethod method) { try { @@ -491,7 +491,7 @@ private bool ValidateConstructor(IMethod method) } } - ILOpCode DecodeOpCodeSkipNop(ref BlobReader reader) + static ILOpCode DecodeOpCodeSkipNop(ref BlobReader reader) { ILOpCode code; do diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index eef43cb7d2..518c097607 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection; using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.Semantics; @@ -544,6 +545,15 @@ ILInstruction Convert() return (null, SpecialType.UnknownType); if (MatchGetMethodFromHandle(invocation.Arguments[0], out var member)) { + var method = (IMethod)member; + // It is possible to use Expression.Bind with a get-accessor, + // however, it would be an invalid expression tree if the property is readonly. + // As this is an assignment, the ILAst expects a set-accessor. To avoid any problems + // constructing property assignments, we explicitly use the set-accessor instead. + if (method.AccessorOwner is IProperty { CanSet: true } property && method != property.Setter) + { + member = property.Setter; + } } else if (MatchGetFieldFromHandle(invocation.Arguments[0], out member)) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index 140bd11adc..2025d390b1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Siegfried Pammer +// Copyright (c) 2017 Siegfried Pammer // // 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 @@ -316,7 +316,7 @@ bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isRefere // reference types have a null check. if (!checkInst.MatchIfInstruction(out var condition, out var disposeInst)) return false; - if (!MatchNullCheckOrTypeCheck(condition, ref objVar, disposeTypeCode)) + if (!MatchNullCheckOrTypeCheck(condition, ref objVar, disposeTypeCode, out var isInlinedIsInst)) return false; if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) return false; @@ -333,6 +333,8 @@ bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isRefere return false; if (target.MatchBox(out var newTarget, out var type) && type.Equals(objVar.Type)) target = newTarget; + else if (isInlinedIsInst && target.MatchIsInst(out newTarget, out type) && type.IsKnownType(disposeTypeCode)) + target = newTarget; disposeCall = cv; } else if (objVar.Type.Kind == TypeKind.Struct && objVar.Type.IsByRefLike) @@ -381,10 +383,31 @@ bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isRefere } } - bool MatchNullCheckOrTypeCheck(ILInstruction condition, ref ILVariable objVar, KnownTypeCode disposeType) + bool MatchNullCheckOrTypeCheck(ILInstruction condition, ref ILVariable objVar, KnownTypeCode disposeType, out bool isInlinedIsInst) { + isInlinedIsInst = false; if (condition.MatchCompNotEquals(out var left, out var right)) { + if (left.MatchStLoc(out var inlineAssignVar, out var inlineAssignVal)) + { + if (!inlineAssignVal.MatchIsInst(out var arg, out var type) || !type.IsKnownType(disposeType)) + return false; + if (!inlineAssignVar.IsSingleDefinition || inlineAssignVar.LoadCount != 1) + return false; + if (!inlineAssignVar.Type.IsKnownType(disposeType)) + return false; + isInlinedIsInst = true; + left = arg; + if (!left.MatchLdLoc(objVar) || !right.MatchLdNull()) + return false; + objVar = inlineAssignVar; + return true; + } + else if (left.MatchIsInst(out var arg, out var type) && type.IsKnownType(disposeType)) + { + isInlinedIsInst = true; + left = arg; + } if (!left.MatchLdLoc(objVar) || !right.MatchLdNull()) return false; return true; diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs index 9de8f29980..8b8575619e 100644 --- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs +++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs @@ -723,16 +723,22 @@ public static IEnumerable EnumerateGac() continue; foreach (var item in new DirectoryInfo(rootPath).EnumerateFiles("*.dll", SearchOption.AllDirectories)) { - string[]? name = Path.GetDirectoryName(item.FullName)?.Substring(rootPath.Length + 1).Split(new[] { "\\" }, StringSplitOptions.RemoveEmptyEntries); - if (name?.Length != 2) - continue; - var match = Regex.Match(name[1], $"(v4.0_)?(?[^_]+)_(?[^_]+)?_(?[^_]+)"); - if (!match.Success) - continue; - string culture = match.Groups["culture"].Value; - if (string.IsNullOrEmpty(culture)) - culture = "neutral"; - yield return AssemblyNameReference.Parse(name[0] + ", Version=" + match.Groups["version"].Value + ", Culture=" + culture + ", PublicKeyToken=" + match.Groups["publicKey"].Value); + // The root of the GAC should only contain folders, but make sure we handle the case where it does NOT in case + // someone has a non-standard layout (e.g. due to a broken installer). + string? assemblyParentPath = Path.GetDirectoryName(item.FullName); + if (assemblyParentPath?.Length > rootPath.Length) + { + string[]? name = assemblyParentPath.Substring(rootPath.Length + 1).Split(new[] { "\\" }, StringSplitOptions.RemoveEmptyEntries); + if (name?.Length != 2) + continue; + var match = Regex.Match(name[1], $"(v4.0_)?(?[^_]+)_(?[^_]+)?_(?[^_]+)"); + if (!match.Success) + continue; + string culture = match.Groups["culture"].Value; + if (string.IsNullOrEmpty(culture)) + culture = "neutral"; + yield return AssemblyNameReference.Parse(name[0] + ", Version=" + match.Groups["version"].Value + ", Culture=" + culture + ", PublicKeyToken=" + match.Groups["publicKey"].Value); + } } } } diff --git a/ICSharpCode.Decompiler/NRTAttributes.cs b/ICSharpCode.Decompiler/NRTAttributes.cs new file mode 100644 index 0000000000..bdcaab0266 --- /dev/null +++ b/ICSharpCode.Decompiler/NRTAttributes.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullWhenAttribute : Attribute + { + public bool ReturnValue { get; } + + public string[] Members { get; } + + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new string[1] { member }; + } + + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + } +} diff --git a/ICSharpCode.Decompiler/Output/IAmbience.cs b/ICSharpCode.Decompiler/Output/IAmbience.cs index d496204b7a..74bbcf0725 100644 --- a/ICSharpCode.Decompiler/Output/IAmbience.cs +++ b/ICSharpCode.Decompiler/Output/IAmbience.cs @@ -109,6 +109,10 @@ public enum ConversionFlags /// Support record structs. /// SupportRecordStructs = 0x40000, + /// + /// Support >>> as unsigned right shift operator. + /// + SupportUnsignedRightShift = 0x80000, StandardConversionFlags = ShowParameterNames | ShowAccessibility | diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs index 0562bcedd7..d225feeaa3 100644 --- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs +++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs @@ -457,7 +457,7 @@ public static bool IsDefinition(ref AstNode node) { if (node is EntityDeclaration && !(node.Parent is LocalFunctionDeclarationStatement)) return true; - if (node is VariableInitializer && node.Parent is FieldDeclaration) + if (node is VariableInitializer && node.Parent is FieldDeclaration or EventDeclaration) { node = node.Parent; return true; diff --git a/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs b/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs index 06341735f0..a119f82d5b 100644 --- a/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs +++ b/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs @@ -4,7 +4,7 @@ public const string Minor = "0"; public const string Build = "0"; public const string Revision = "$INSERTREVISION$"; - public const string VersionName = "preview2"; + public const string VersionName = null; public const string FullVersion = Major + "." + Minor + "." + Build + ".$INSERTREVISION$$INSERTBRANCHPOSTFIX$$INSERTVERSIONNAMEPOSTFIX$"; public const string FullVersionWithShortCommitHash = FullVersion + "-$INSERTSHORTCOMMITHASH$"; diff --git a/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs b/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs index 214c994b5b..d77c160963 100644 --- a/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs +++ b/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs @@ -26,8 +26,11 @@ namespace ICSharpCode.Decompiler.Semantics /// class OutVarResolveResult : ResolveResult { - public static readonly OutVarResolveResult Instance = new OutVarResolveResult(); + /// + /// Type of the variable originally used in IL. It will be used, if "out var" cannot be used. + /// + public readonly IType OriginalVariableType; - public OutVarResolveResult() : base(SpecialType.NoType) { } + public OutVarResolveResult(IType originalVariableType) : base(SpecialType.NoType) { OriginalVariableType = originalVariableType; } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs index 459ce0fdc9..a6ba7ef15d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Daniel Grunwald +// Copyright (c) 2018 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 @@ -40,12 +40,11 @@ public static IType ApplyAttributesToType( SRM.MetadataReader metadata, TypeSystemOptions options, Nullability nullableContext, - bool typeChildrenOnly = false, - bool isSignatureReturnType = false) + bool typeChildrenOnly = false) { bool hasDynamicAttribute = false; bool[] dynamicAttributeData = null; - bool hasNativeIntegersAttribute = false; + bool hasNativeIntegersAttribute = (options & TypeSystemOptions.NativeIntegersWithoutAttribute) != 0; bool[] nativeIntegersAttributeData = null; string[] tupleElementNames = null; Nullability nullability; @@ -134,14 +133,6 @@ public static IType ApplyAttributesToType( options, tupleElementNames, nullability, nullableAttributeData ); - if (isSignatureReturnType && hasDynamicAttribute - && inputType.SkipModifiers().Kind == TypeKind.ByReference - && attributes.Value.HasKnownAttribute(metadata, KnownAttribute.IsReadOnly)) - { - // crazy special case: `ref readonly` return takes one dynamic index more than - // a non-readonly `ref` return. - visitor.dynamicTypeIndex++; - } if (typeChildrenOnly) { return inputType.VisitChildren(visitor); @@ -190,6 +181,7 @@ private ApplyAttributeTypeVisitor(ICompilation compilation, public override IType VisitModOpt(ModifiedType type) { + dynamicTypeIndex++; if ((options & TypeSystemOptions.KeepModifiers) != 0) return base.VisitModOpt(type); else @@ -198,6 +190,7 @@ public override IType VisitModOpt(ModifiedType type) public override IType VisitModReq(ModifiedType type) { + dynamicTypeIndex++; if ((options & TypeSystemOptions.KeepModifiers) != 0) return base.VisitModReq(type); else diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index b20d1bb0ee..5a46088f68 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -120,16 +120,24 @@ public enum TypeSystemOptions /// FunctionPointers = 0x2000, /// - /// Allow C# 11 scoped annotation. If this option is not enabled, LifetimeAnnotationAttribute + /// Allow C# 11 scoped annotation. If this option is not enabled, ScopedRefAttribute /// will be reported as custom attribute. /// - LifetimeAnnotations = 0x4000, + ScopedRef = 0x4000, + [Obsolete("Use ScopedRef instead")] + LifetimeAnnotations = ScopedRef, + /// + /// Replace 'IntPtr' types with the 'nint' type even in absence of [NativeIntegerAttribute]. + /// Note: DecompilerTypeSystem constructor removes this setting from the options if + /// not targeting .NET 7 or later. + /// + NativeIntegersWithoutAttribute = 0x8000, /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods - | NativeIntegers | FunctionPointers | LifetimeAnnotations + | NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute } /// @@ -165,8 +173,10 @@ public static TypeSystemOptions GetOptions(DecompilerSettings settings) typeSystemOptions |= TypeSystemOptions.NativeIntegers; if (settings.FunctionPointers) typeSystemOptions |= TypeSystemOptions.FunctionPointers; - if (settings.LifetimeAnnotations) - typeSystemOptions |= TypeSystemOptions.LifetimeAnnotations; + if (settings.ScopedRef) + typeSystemOptions |= TypeSystemOptions.ScopedRef; + if (settings.NumericIntPtr) + typeSystemOptions |= TypeSystemOptions.NativeIntegersWithoutAttribute; return typeSystemOptions; } @@ -304,6 +314,10 @@ private async Task InitializeAsync(PEFile mainModule, IAssemblyResolver assembly } } + if (!(identifier == TargetFrameworkIdentifier.NET && version >= new Version(7, 0))) + { + typeSystemOptions &= ~TypeSystemOptions.NativeIntegersWithoutAttribute; + } var mainModuleWithOptions = mainModule.WithOptions(typeSystemOptions); var referencedAssembliesWithOptions = referencedAssemblies.Select(file => file.WithOptions(typeSystemOptions)); // Primitive types are necessary to avoid assertions in ILReader. diff --git a/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs b/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs index d730daa4e2..c2589db4df 100644 --- a/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs @@ -132,6 +132,11 @@ public override ITypeDefinition GetDefinition() } } + public override ITypeDefinitionOrUnknown GetDefinitionOrUnknown() + { + return GetDefinition(); + } + public override IType AcceptVisitor(TypeVisitor visitor) { return visitor.VisitFunctionPointerType(this); diff --git a/ICSharpCode.Decompiler/TypeSystem/IEvent.cs b/ICSharpCode.Decompiler/TypeSystem/IEvent.cs index 732840c629..635b031dac 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IEvent.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IEvent.cs @@ -18,12 +18,17 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; + namespace ICSharpCode.Decompiler.TypeSystem { public interface IEvent : IMember { + [MemberNotNullWhen(true, nameof(AddAccessor))] bool CanAdd { get; } + [MemberNotNullWhen(true, nameof(RemoveAccessor))] bool CanRemove { get; } + [MemberNotNullWhen(true, nameof(InvokeAccessor))] bool CanInvoke { get; } IMethod? AddAccessor { get; } diff --git a/ICSharpCode.Decompiler/TypeSystem/IField.cs b/ICSharpCode.Decompiler/TypeSystem/IField.cs index 49a92fd264..42e13c40ed 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IField.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IField.cs @@ -35,6 +35,11 @@ public interface IField : IMember, IVariable /// bool IsReadOnly { get; } + /// + /// Gets whether the field type is 'ref readonly'. + /// + bool ReturnTypeIsRefReadOnly { get; } + /// /// Gets whether this field is volatile. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs index 26e4626c88..6438a13153 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs @@ -19,6 +19,7 @@ #nullable enable using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace ICSharpCode.Decompiler.TypeSystem @@ -81,6 +82,7 @@ public interface IMethod : IParameterizedMember /// /// Gets whether the method is a property/event accessor. /// + [MemberNotNullWhen(true, nameof(AccessorOwner))] bool IsAccessor { get; } /// diff --git a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs index 59025af0fc..aee0078a93 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs @@ -18,6 +18,7 @@ #nullable enable +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -36,7 +37,20 @@ public enum ReferenceKind : byte public struct LifetimeAnnotation { + /// + /// C# 11 scoped annotation: "scoped ref" (ScopedRefAttribute) + /// + public bool ScopedRef { +#pragma warning disable 618 + get { return RefScoped; } + set { RefScoped = value; } +#pragma warning restore 618 + } + + [Obsolete("Use ScopedRef property instead of directly accessing this field")] public bool RefScoped; + + [Obsolete("C# 11 preview: \"ref scoped\" no longer supported")] public bool ValueScoped; } diff --git a/ICSharpCode.Decompiler/TypeSystem/IProperty.cs b/ICSharpCode.Decompiler/TypeSystem/IProperty.cs index f8b6203d12..cad688dbe3 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IProperty.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IProperty.cs @@ -18,6 +18,8 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; + namespace ICSharpCode.Decompiler.TypeSystem { /// @@ -25,7 +27,9 @@ namespace ICSharpCode.Decompiler.TypeSystem /// public interface IProperty : IParameterizedMember { + [MemberNotNullWhen(true, nameof(Getter))] bool CanGet { get; } + [MemberNotNullWhen(true, nameof(Setter))] bool CanSet { get; } IMethod? Getter { get; } diff --git a/ICSharpCode.Decompiler/TypeSystem/IType.cs b/ICSharpCode.Decompiler/TypeSystem/IType.cs index a5e38fc6a7..6226e13159 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IType.cs @@ -87,6 +87,12 @@ public interface IType : INamedElement, IEquatable /// ITypeDefinition? GetDefinition(); + /// + /// Gets the underlying type definition or UnkownType, if unknown. + /// Can return null for types which do not have a type definition (for example arrays, pointers, type parameters). + /// + ITypeDefinitionOrUnknown? GetDefinitionOrUnknown(); + /// /// Gets the parent type, if this is a nested type. /// Returns null for top-level types. diff --git a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs index 2b06723070..f7d07a2c69 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs @@ -23,10 +23,10 @@ namespace ICSharpCode.Decompiler.TypeSystem { /// - /// Represents a class, enum, interface, struct, delegate or VB module. + /// Represents a class, enum, interface, struct, delegate, record or VB module. /// For partial classes, this represents the whole class. /// - public interface ITypeDefinition : IType, IEntity + public interface ITypeDefinition : ITypeDefinitionOrUnknown, IType, IEntity { IReadOnlyList NestedTypes { get; } IReadOnlyList Members { get; } @@ -53,11 +53,6 @@ public interface ITypeDefinition : IType, IEntity /// bool IsReadOnly { get; } - /// - /// Gets the full name of this type. - /// - FullTypeName FullTypeName { get; } - /// /// Gets the short type name as stored in metadata. /// That is, the short type name including the generic arity (`N) appended. diff --git a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinitionOrUnknown.cs b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinitionOrUnknown.cs new file mode 100644 index 0000000000..87de6f5d45 --- /dev/null +++ b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinitionOrUnknown.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2022 Siegfried Pammer +// +// 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. + +#nullable enable + +namespace ICSharpCode.Decompiler.TypeSystem +{ + /// + /// Represents a class, enum, interface, struct, delegate, record or unknown type. + /// For partial classes, this represents the whole class. + /// + public interface ITypeDefinitionOrUnknown : IType + { + /// + /// Gets the full name of this type. + /// + FullTypeName FullTypeName { get; } + } +} diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractType.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractType.cs index 44e63b9636..b6b3bef3ad 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractType.cs @@ -64,7 +64,8 @@ public virtual string ReflectionName { public virtual IType ChangeNullability(Nullability nullability) { - Debug.Assert(nullability == Nullability.Oblivious); + // Only some types support nullability, in the default implementation + // we just ignore the nullability change. return this; } @@ -91,6 +92,11 @@ public virtual ITypeDefinition GetDefinition() return null; } + public virtual ITypeDefinitionOrUnknown GetDefinitionOrUnknown() + { + return null; + } + public virtual IEnumerable DirectBaseTypes { get { return EmptyList.Instance; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs index c034d1d2a8..0660573f0b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs @@ -270,6 +270,11 @@ ITypeDefinition IType.GetDefinition() return null; } + ITypeDefinitionOrUnknown IType.GetDefinitionOrUnknown() + { + return null; + } + public IType AcceptVisitor(TypeVisitor visitor) { return visitor.VisitTypeParameter(this); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index d6b7862012..cea8132506 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -238,6 +238,7 @@ internal bool IgnoreAttribute(TopLevelTypeName attributeType, SymbolKind target) case SymbolKind.ReturnType: case SymbolKind.Property: case SymbolKind.Indexer: + case SymbolKind.Field: return true; // "ref readonly" is currently always active default: return false; @@ -251,8 +252,8 @@ internal bool IgnoreAttribute(TopLevelTypeName attributeType, SymbolKind target) case "NullableContextAttribute": return (options & TypeSystemOptions.NullabilityAnnotations) != 0 && (target == SymbolKind.TypeDefinition || IsMethodLike(target)); - case "LifetimeAnnotationAttribute": - return (options & TypeSystemOptions.LifetimeAnnotations) != 0 + case "ScopedRefAttribute": + return (options & TypeSystemOptions.ScopedRef) != 0 && (target == SymbolKind.Parameter); default: return false; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DecoratedType.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DecoratedType.cs index 2f949874d6..5107044f2a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DecoratedType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DecoratedType.cs @@ -59,6 +59,11 @@ ITypeDefinition IType.GetDefinition() return baseType.GetDefinition(); } + ITypeDefinitionOrUnknown IType.GetDefinitionOrUnknown() + { + return baseType.GetDefinitionOrUnknown(); + } + IEnumerable IType.GetEvents(Predicate filter, GetMemberOptions options) { return baseType.GetEvents(filter, options); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs index 43549b6c6d..ca9b556d7d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs @@ -109,6 +109,7 @@ public FakeField(ICompilation compilation) : base(compilation) } bool IField.IsReadOnly => false; + bool IField.ReturnTypeIsRefReadOnly => false; bool IField.IsVolatile => false; bool IVariable.IsConst => false; @@ -199,6 +200,16 @@ public override IMember Specialize(TypeParameterSubstitution substitution) public bool IsIndexer { get; set; } public bool ReturnTypeIsRefReadOnly => false; public IReadOnlyList Parameters { get; set; } + + public override string ToString() => + "FakeProperty " + ReturnType + " " + DeclaringType.Name + "." + Name + + (Parameters.Count == 0 + ? "" + : "[" + string.Join(", ", Parameters) + "]") + + " { " + + (CanGet ? "get; " : "") + + (CanSet ? "set; " : "") + + "}"; } sealed class FakeEvent : FakeMember, IEvent diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index 6f6dcacaf2..59dfc5863a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -33,6 +33,7 @@ public enum KnownAttribute None, CompilerGenerated, + CompilerFeatureRequired, /// /// Marks a method as extension method; or a class as containing extension methods. /// @@ -44,6 +45,7 @@ public enum KnownAttribute NullablePublicOnly, Conditional, Obsolete, + Embedded, IsReadOnly, SpecialName, DebuggerHidden, @@ -88,10 +90,11 @@ public enum KnownAttribute In, Out, Optional, + DefaultParameterValue, CallerMemberName, CallerFilePath, CallerLineNumber, - LifetimeAnnotation, + ScopedRef, // Type parameter attributes: IsUnmanaged, @@ -105,15 +108,19 @@ public enum KnownAttribute // C# 9 attributes: NativeInteger, PreserveBaseOverrides, + + // C# 11 attributes: + RequiredAttribute, } public static class KnownAttributes { - internal const int Count = (int)KnownAttribute.PreserveBaseOverrides + 1; + internal const int Count = (int)KnownAttribute.RequiredAttribute + 1; static readonly TopLevelTypeName[] typeNames = new TopLevelTypeName[Count]{ default, new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CompilerGeneratedAttribute)), + new TopLevelTypeName("System.Runtime.CompilerServices", "CompilerFeatureRequiredAttribute"), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(ExtensionAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(DynamicAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(TupleElementNamesAttribute)), @@ -122,6 +129,7 @@ public static class KnownAttributes new TopLevelTypeName("System.Runtime.CompilerServices", "NullablePublicOnlyAttribute"), new TopLevelTypeName("System.Diagnostics", nameof(ConditionalAttribute)), new TopLevelTypeName("System", nameof(ObsoleteAttribute)), + new TopLevelTypeName("Microsoft.CodeAnalysis", "EmbeddedAttribute"), new TopLevelTypeName("System.Runtime.CompilerServices", "IsReadOnlyAttribute"), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(SpecialNameAttribute)), new TopLevelTypeName("System.Diagnostics", nameof(DebuggerHiddenAttribute)), @@ -160,10 +168,11 @@ public static class KnownAttributes new TopLevelTypeName("System.Runtime.InteropServices", nameof(InAttribute)), new TopLevelTypeName("System.Runtime.InteropServices", nameof(OutAttribute)), new TopLevelTypeName("System.Runtime.InteropServices", nameof(OptionalAttribute)), + new TopLevelTypeName("System.Runtime.InteropServices", nameof(DefaultParameterValueAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerMemberNameAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerFilePathAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerLineNumberAttribute)), - new TopLevelTypeName("System.Runtime.CompilerServices", "LifetimeAnnotationAttribute"), + new TopLevelTypeName("System.Runtime.CompilerServices", "ScopedRefAttribute"), // Type parameter attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "IsUnmanagedAttribute"), // Marshalling attributes: @@ -173,6 +182,8 @@ public static class KnownAttributes // C# 9 attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "NativeIntegerAttribute"), new TopLevelTypeName("System.Runtime.CompilerServices", "PreserveBaseOverridesAttribute"), + // C# 11 attributes: + new TopLevelTypeName("System.Runtime.CompilerServices", "RequiredMemberAttribute"), }; public static ref readonly TopLevelTypeName GetTypeName(this KnownAttribute attr) @@ -213,6 +224,7 @@ public static bool IsCustomAttribute(this KnownAttribute knownAttribute) case KnownAttribute.MarshalAs: case KnownAttribute.PermissionSet: case KnownAttribute.Optional: + case KnownAttribute.DefaultParameterValue: case KnownAttribute.In: case KnownAttribute.Out: case KnownAttribute.IndexerName: diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs index f6a23d073e..d561e3be21 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Daniel Grunwald +// Copyright (c) 2018 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 @@ -189,6 +189,13 @@ public IAttribute GetAttribute(KnownAttribute attribute) return b.GetAttribute(metadata, def.GetCustomAttributes(), attribute, SymbolKind.Field); } + public bool ReturnTypeIsRefReadOnly { + get { + var def = module.metadata.GetFieldDefinition(handle); + return def.GetCustomAttributes().HasKnownAttribute(module.metadata, KnownAttribute.IsReadOnly); + } + } + public string FullName => $"{DeclaringType?.FullName}.{Name}"; public string ReflectionName => $"{DeclaringType?.ReflectionName}.{Name}"; public string Namespace => DeclaringType?.Namespace ?? string.Empty; @@ -225,7 +232,6 @@ private IType DecodeTypeAndVolatileFlag() if (ty is ModifiedType mod && mod.Modifier.Name == "IsVolatile" && mod.Modifier.Namespace == "System.Runtime.CompilerServices") { Volatile.Write(ref this.isVolatile, true); - ty = mod.ElementType; } ty = ApplyAttributeTypeVisitor.ApplyAttributesToType(ty, Compilation, fieldDef.GetCustomAttributes(), metadata, module.OptionsForEntity(this), diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index abc4252ea6..3ebd754d74 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Daniel Grunwald +// Copyright (c) 2018 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 @@ -67,7 +67,8 @@ internal MetadataMethod(MetadataModule module, MethodDefinitionHandle handle) var (accessorOwner, semanticsAttribute) = module.PEFile.MethodSemanticsLookup.GetSemantics(handle); const MethodAttributes finalizerAttributes = (MethodAttributes.Virtual | MethodAttributes.Family | MethodAttributes.HideBySig); this.typeParameters = MetadataTypeParameter.Create(module, this, def.GetGenericParameters()); - if (semanticsAttribute != 0) + if (semanticsAttribute != 0 && !accessorOwner.IsNil + && accessorOwner.Kind is HandleKind.PropertyDefinition or HandleKind.EventDefinition) { this.symbolKind = SymbolKind.Accessor; this.accessorOwner = accessorOwner; @@ -129,7 +130,6 @@ public string Name { public bool HasBody => module.metadata.GetMethodDefinition(handle).HasBody(); - public IMember AccessorOwner { get { if (accessorOwner.IsNil) @@ -271,8 +271,7 @@ internal static (IType returnType, IParameter[] parameters, ModifiedType returnT } Debug.Assert(i == parameters.Length); var returnType = ApplyAttributeTypeVisitor.ApplyAttributesToType(signature.ReturnType, - module.Compilation, returnTypeAttributes, metadata, typeSystemOptions, nullableContext, - isSignatureReturnType: true); + module.Compilation, returnTypeAttributes, metadata, typeSystemOptions, nullableContext); return (returnType, parameters, signature.ReturnType as ModifiedType); } #endregion diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index adcf825401..c2cba9824e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -64,8 +64,17 @@ public IEnumerable GetAttributes() var metadata = module.metadata; var parameter = metadata.GetParameter(handle); - if (IsOptional && !HasConstantValueInSignature) + bool defaultValueAssignmentAllowed = ReferenceKind is ReferenceKind.None or ReferenceKind.In; + + if (IsOptional && (!defaultValueAssignmentAllowed || !HasConstantValueInSignature)) + { b.Add(KnownAttribute.Optional); + } + + if (!(IsDecimalConstant || !HasConstantValueInSignature) && (!defaultValueAssignmentAllowed || !IsOptional)) + { + b.Add(KnownAttribute.DefaultParameterValue, KnownTypeCode.Object, GetConstantValue(throwOnInvalidMetadata: false)); + } if (!IsOut && !IsIn) { @@ -108,32 +117,17 @@ ReferenceKind DetectRefKind() public LifetimeAnnotation Lifetime { get { - if ((module.TypeSystemOptions & TypeSystemOptions.LifetimeAnnotations) == 0) + if ((module.TypeSystemOptions & TypeSystemOptions.ScopedRef) == 0) { return default; } var metadata = module.metadata; var parameterDef = metadata.GetParameter(handle); - foreach (var h in parameterDef.GetCustomAttributes()) + if (parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ScopedRef)) { - var custom = metadata.GetCustomAttribute(h); - if (!custom.IsKnownAttribute(metadata, KnownAttribute.LifetimeAnnotation)) - continue; - - var value = custom.DecodeValue(module.TypeProvider); - if (value.FixedArguments.Length != 2) - continue; - if (value.FixedArguments[0].Value is bool refScoped - && value.FixedArguments[1].Value is bool valueScoped) - { - return new LifetimeAnnotation { - RefScoped = refScoped, - ValueScoped = valueScoped - }; - } + return new LifetimeAnnotation { ScopedRef = true }; } - return default; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs index 1f58efc4c3..3eff3c1423 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs @@ -58,6 +58,7 @@ sealed class MetadataTypeDefinition : ITypeDefinition IEvent[] events; IMethod[] methods; List directBaseTypes; + bool defaultMemberNameInitialized; string defaultMemberName; internal MetadataTypeDefinition(MetadataModule module, TypeDefinitionHandle handle) @@ -258,17 +259,23 @@ public IEnumerable Methods { var methodsCollection = metadata.GetTypeDefinition(handle).GetMethods(); var methodsList = new List(methodsCollection.Count); var methodSemantics = module.PEFile.MethodSemanticsLookup; + bool hasDefaultCtor = false; foreach (MethodDefinitionHandle h in methodsCollection) { var md = metadata.GetMethodDefinition(h); if (methodSemantics.GetSemantics(h).Item2 == 0 && module.IsVisible(md.Attributes)) { - methodsList.Add(module.GetDefinition(h)); + IMethod method = module.GetDefinition(h); + if (method.SymbolKind == SymbolKind.Constructor && !method.IsStatic && method.Parameters.Count == 0) + { + hasDefaultCtor = true; + } + methodsList.Add(method); } } - if (this.Kind == TypeKind.Struct || this.Kind == TypeKind.Enum) + if (!hasDefaultCtor && (this.Kind == TypeKind.Struct || this.Kind == TypeKind.Enum)) { - methodsList.Add(FakeMethod.CreateDummyConstructor(Compilation, this, IsAbstract ? Accessibility.Protected : Accessibility.Public)); + methodsList.Add(FakeMethod.CreateDummyConstructor(Compilation, this, Accessibility.Public)); } if ((module.TypeSystemOptions & TypeSystemOptions.Uncached) != 0) return methodsList; @@ -301,7 +308,7 @@ public bool? IsReferenceType { public IType ChangeNullability(Nullability nullability) { - if (nullability == Nullability.Oblivious) + if (nullability == Nullability.Oblivious || IsReferenceType == false) return this; else return new NullabilityAnnotatedType(this, nullability); @@ -323,7 +330,7 @@ public IEnumerable DirectBaseTypes { EntityHandle baseTypeHandle = td.BaseType; if (!baseTypeHandle.IsNil) { - baseType = module.ResolveType(baseTypeHandle, context); + baseType = module.ResolveType(baseTypeHandle, context, metadata.GetCustomAttributes(this.handle), Nullability.Oblivious); } } catch (BadImageFormatException) @@ -459,7 +466,7 @@ public IAttribute GetAttribute(KnownAttribute attribute) public string DefaultMemberName { get { string defaultMemberName = LazyInit.VolatileRead(ref this.defaultMemberName); - if (defaultMemberName != null) + if (defaultMemberName != null || defaultMemberNameInitialized) return defaultMemberName; var metadata = module.metadata; var typeDefinition = metadata.GetTypeDefinition(handle); @@ -475,7 +482,9 @@ public string DefaultMemberName { break; } } - return LazyInit.GetOrSet(ref this.defaultMemberName, defaultMemberName ?? "Item"); + defaultMemberName = LazyInit.GetOrSet(ref this.defaultMemberName, defaultMemberName); + defaultMemberNameInitialized = true; + return defaultMemberName; } } #endregion @@ -527,6 +536,7 @@ public string FullName { public string Namespace => fullTypeName.TopLevelTypeName.Namespace; ITypeDefinition IType.GetDefinition() => this; + ITypeDefinitionOrUnknown IType.GetDefinitionOrUnknown() => this; TypeParameterSubstitution IType.GetSubstitution() => TypeParameterSubstitution.Identity; public IType AcceptVisitor(TypeVisitor visitor) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs index 348d051694..28737d6700 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs @@ -310,6 +310,7 @@ IEnumerable IType.GetProperties(Predicate filter, GetMembe bool ITypeDefinition.IsRecord => false; ITypeDefinition IType.GetDefinition() => this; + ITypeDefinitionOrUnknown IType.GetDefinitionOrUnknown() => this; TypeParameterSubstitution IType.GetSubstitution() => TypeParameterSubstitution.Identity; IType IType.AcceptVisitor(TypeVisitor visitor) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs index 7a41c50c06..abfd5b184b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs @@ -18,7 +18,7 @@ internal NullabilityAnnotatedType(IType type, Nullability nullability) Debug.Assert(nullability != Nullability.Oblivious); // Due to IType -> concrete type casts all over the type system, we can insert // the NullabilityAnnotatedType wrapper only in some limited places. - Debug.Assert(type is ITypeDefinition + Debug.Assert(type is ITypeDefinition { IsReferenceType: not false } || type.Kind == TypeKind.Dynamic || type.Kind == TypeKind.Unknown || (type is ITypeParameter && this is ITypeParameter)); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedField.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedField.cs index ac0dc9c402..fe4dc9ccc9 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedField.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedField.cs @@ -45,13 +45,9 @@ public SpecializedField(IField fieldDefinition, TypeParameterSubstitution substi AddSubstitution(substitution); } - public bool IsReadOnly { - get { return fieldDefinition.IsReadOnly; } - } - - public bool IsVolatile { - get { return fieldDefinition.IsVolatile; } - } + public bool IsReadOnly => fieldDefinition.IsReadOnly; + public bool ReturnTypeIsRefReadOnly => fieldDefinition.ReturnTypeIsRefReadOnly; + public bool IsVolatile => fieldDefinition.IsVolatile; IType IVariable.Type { get { return this.ReturnType; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs index 51abb1a48a..28a0f33b7e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs @@ -26,7 +26,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation /// An unknown type where (part) of the name is known. /// [Serializable] - public class UnknownType : AbstractType, ITypeReference + public class UnknownType : AbstractType, ITypeDefinitionOrUnknown, ITypeReference { readonly bool namespaceKnown; readonly FullTypeName fullTypeName; @@ -78,6 +78,11 @@ IType ITypeReference.Resolve(ITypeResolveContext context) return this; } + public override ITypeDefinitionOrUnknown GetDefinitionOrUnknown() + { + return this; + } + public override string Name { get { return fullTypeName.Name; } } @@ -102,7 +107,7 @@ public override bool? IsReferenceType { public override IType ChangeNullability(Nullability nullability) { - if (nullability == Nullability.Oblivious) + if (nullability == Nullability.Oblivious || isReferenceType == false) return this; else return new NullabilityAnnotatedType(this, nullability); diff --git a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs index 818e1ecd95..3481ac1c82 100644 --- a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs +++ b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs @@ -402,8 +402,9 @@ public IType ResolveType(EntityHandle typeRefDefSpec, GenericContext context, Ty IType ResolveDeclaringType(EntityHandle declaringTypeReference, GenericContext context) { // resolve without substituting dynamic/tuple types - var ty = ResolveType(declaringTypeReference, context, - options & ~(TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple | TypeSystemOptions.NullabilityAnnotations)); + const TypeSystemOptions removedOptions = TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple + | TypeSystemOptions.NullabilityAnnotations | TypeSystemOptions.NativeIntegers | TypeSystemOptions.NativeIntegersWithoutAttribute; + var ty = ResolveType(declaringTypeReference, context, options & ~removedOptions); // but substitute tuple types in type arguments: ty = ApplyAttributeTypeVisitor.ApplyAttributesToType(ty, Compilation, null, metadata, options, Nullability.Oblivious, typeChildrenOnly: true); return ty; @@ -670,7 +671,7 @@ private void GuessFakeMethodAccessor(IType declaringType, string name, MethodSig m.AccessorKind = MethodSemanticsAttributes.Setter; m.AccessorOwner = fakeProperty; - fakeProperty.Getter = m; + fakeProperty.Setter = m; fakeProperty.ReturnType = parameters.Last().Type; fakeProperty.IsIndexer = parameters.Count > 1; fakeProperty.Parameters = parameters.SkipLast(1).ToArray(); diff --git a/ICSharpCode.Decompiler/TypeSystem/ModifiedType.cs b/ICSharpCode.Decompiler/TypeSystem/ModifiedType.cs index 31c677e33b..d87668c353 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ModifiedType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ModifiedType.cs @@ -58,6 +58,11 @@ public override ITypeDefinition GetDefinition() return elementType.GetDefinition(); } + public override ITypeDefinitionOrUnknown GetDefinitionOrUnknown() + { + return elementType.GetDefinitionOrUnknown(); + } + public override IEnumerable GetAccessors(Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return elementType.GetAccessors(filter, options); diff --git a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs index 505a063595..8f097f6832 100644 --- a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs @@ -1,6 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (c) 2019 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. + +#nullable enable using ICSharpCode.Decompiler.TypeSystem.Implementation; diff --git a/ICSharpCode.Decompiler/TypeSystem/Nullability.cs b/ICSharpCode.Decompiler/TypeSystem/Nullability.cs index 17ce52520c..fc934ebeaa 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Nullability.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Nullability.cs @@ -1,6 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (c) 2019 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. namespace ICSharpCode.Decompiler.TypeSystem { diff --git a/ICSharpCode.Decompiler/TypeSystem/ParameterizedType.cs b/ICSharpCode.Decompiler/TypeSystem/ParameterizedType.cs index b20eba7de0..3acc578604 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ParameterizedType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ParameterizedType.cs @@ -180,6 +180,11 @@ public ITypeDefinition GetDefinition() return genericType.GetDefinition(); } + public ITypeDefinitionOrUnknown GetDefinitionOrUnknown() + { + return genericType.GetDefinitionOrUnknown(); + } + /// /// Gets a type visitor that performs the substitution of class type parameters with the type arguments /// of this parameterized type. diff --git a/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs b/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs index 12601f9a4f..c7cf024284 100644 --- a/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs @@ -115,7 +115,7 @@ public override int GetHashCode() public override IType ChangeNullability(Nullability nullability) { - if (nullability == base.Nullability) + if (nullability == base.Nullability || Kind is not TypeKind.Dynamic) return this; else return new NullabilityAnnotatedType(this, nullability); diff --git a/ICSharpCode.Decompiler/TypeSystem/TupleType.cs b/ICSharpCode.Decompiler/TypeSystem/TupleType.cs index 26c61cbbf6..d77387f4f3 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TupleType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TupleType.cs @@ -312,6 +312,11 @@ public override ITypeDefinition GetDefinition() return UnderlyingType.GetDefinition(); } + public override ITypeDefinitionOrUnknown GetDefinitionOrUnknown() + { + return UnderlyingType.GetDefinitionOrUnknown(); + } + public override IEnumerable GetEvents(Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return UnderlyingType.GetEvents(filter, options); diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs b/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs index df2cb64020..39c2bcad3e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs @@ -72,6 +72,13 @@ public IType GetFunctionPointerType(SRM.MethodSignature signature) public IType GetGenericInstantiation(IType genericType, ImmutableArray typeArguments) { + int tpc = genericType.TypeParameterCount; + if (tpc == 0 || tpc != typeArguments.Length) + { + // This can occur when the genericType is from another assembly, + // doesn't have the typical `1 suffix, and that other assembly is not loaded. + return genericType; + } return new ParameterizedType(genericType, typeArguments); } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index 7115696d1b..3ce5d01805 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -239,7 +239,7 @@ public static bool IsUnmanagedType(this IType type, bool allowGenerics) bool IsUnmanagedTypeInternal(IType type) { - if (type.Kind is TypeKind.Enum or TypeKind.Pointer or TypeKind.FunctionPointer) + if (type.Kind is TypeKind.Enum or TypeKind.Pointer or TypeKind.FunctionPointer or TypeKind.NInt or TypeKind.NUInt) { return true; } @@ -286,7 +286,7 @@ bool IsUnmanagedTypeInternal(IType type) types = new HashSet(); } types.Add(type); - foreach (var f in type.GetFields()) + foreach (var f in type.GetFields(f => !f.IsStatic)) { if (types.Contains(f.Type)) { @@ -374,6 +374,15 @@ public static IType SkipModifiers(this IType ty) return ty; } + public static IType UnwrapByRef(this IType type) + { + if (type is ByReferenceType byRef) + { + type = byRef.ElementType; + } + return type; + } + public static bool HasReadonlyModifier(this IMethod accessor) { return accessor.ThisIsRefReadOnly && accessor.DeclaringTypeDefinition?.IsReadOnly == false; @@ -739,5 +748,21 @@ public static IType AsParameterizedType(this ITypeDefinition td) return new ParameterizedType(td, td.TypeArguments); } } + + public static INamespace GetNamespaceByFullName(this ICompilation compilation, string name) + { + if (string.IsNullOrEmpty(name)) + return compilation.RootNamespace; + var parts = name.Split('.'); + var ns = compilation.RootNamespace; + foreach (var part in parts) + { + var child = ns.GetChildNamespace(part); + if (child == null) + return null; + ns = child; + } + return ns; + } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs index da76087c72..d5636fe244 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using ICSharpCode.Decompiler.TypeSystem.Implementation; namespace ICSharpCode.Decompiler.TypeSystem diff --git a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs index fd86f97910..7d18ef5517 100644 --- a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs +++ b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Daniel Grunwald +// Copyright (c) 2018 Daniel Grunwald // This file is based on the Mono implementation of ResXResourceWriter. // It is modified to add support for "ResourceSerializedObject" values. // @@ -31,9 +31,8 @@ // includes code by Mike Krüger and Lluis Sanchez using System; -using System.ComponentModel; +using System.Globalization; using System.IO; -using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Xml; @@ -46,25 +45,20 @@ namespace ICSharpCode.Decompiler.Util #endif class ResXResourceWriter : IDisposable { - #region Local Variables private string filename; private Stream stream; private TextWriter textwriter; private XmlTextWriter writer; private bool written; private string base_path; - #endregion // Local Variables - #region Static Fields public static readonly string BinSerializedObjectMimeType = "application/x-microsoft.net.object.binary.base64"; public static readonly string ByteArraySerializedObjectMimeType = "application/x-microsoft.net.object.bytearray.base64"; public static readonly string DefaultSerializedObjectMimeType = BinSerializedObjectMimeType; public static readonly string ResMimeType = "text/microsoft-resx"; public static readonly string SoapSerializedObjectMimeType = "application/x-microsoft.net.object.soap.base64"; public static readonly string Version = "2.0"; - #endregion // Static Fields - #region Constructors & Destructor public ResXResourceWriter(Stream stream) { if (stream == null) @@ -96,9 +90,9 @@ public ResXResourceWriter(string fileName) { Dispose(false); } - #endregion // Constructors & Destructor const string WinFormsAssemblyName = ", System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; + const string MSCorLibAssemblyName = ", mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; const string ResXNullRefTypeName = "System.Resources.ResXNullRef" + WinFormsAssemblyName; void InitWriter() @@ -131,39 +125,23 @@ void WriteHeader(string name, string value) void WriteNiceBase64(byte[] value, int offset, int length) { - string b64; - StringBuilder sb; - int pos; - int inc; - string ins; - - b64 = Convert.ToBase64String(value, offset, length); - - // Wild guess; two extra newlines, and one newline/tab pair for every 80 chars - sb = new StringBuilder(b64, b64.Length + ((b64.Length + 160) / 80) * 3); - pos = 0; - inc = 80 + Environment.NewLine.Length + 1; - ins = Environment.NewLine + "\t"; - while (pos < sb.Length) - { - sb.Insert(pos, ins); - pos += inc; - } - sb.Insert(sb.Length, Environment.NewLine); - writer.WriteString(sb.ToString()); + string base64 = Convert.ToBase64String( + value, offset, length, + Base64FormattingOptions.InsertLineBreaks); + writer.WriteString(base64); } - void WriteBytes(string name, Type type, byte[] value, int offset, int length, string comment) + void WriteBytes(string name, string type, byte[] value, int offset, int length, string comment) { writer.WriteStartElement("data"); writer.WriteAttributeString("name", name); if (type != null) { - writer.WriteAttributeString("type", type.AssemblyQualifiedName); + writer.WriteAttributeString("type", type); // byte[] should never get a mimetype, otherwise MS.NET won't be able // to parse the data. - if (type != typeof(byte[])) + if (type != "System.Byte[]" + MSCorLibAssemblyName) writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType); writer.WriteStartElement("value"); WriteNiceBase64(value, offset, length); @@ -177,7 +155,7 @@ void WriteBytes(string name, Type type, byte[] value, int offset, int length, st writer.WriteEndElement(); - if (!(comment == null || comment.Equals(String.Empty))) + if (!string.IsNullOrEmpty(comment)) { writer.WriteStartElement("comment"); writer.WriteString(comment); @@ -187,29 +165,22 @@ void WriteBytes(string name, Type type, byte[] value, int offset, int length, st writer.WriteEndElement(); } - void WriteBytes(string name, Type type, byte[] value, string comment) - { - WriteBytes(name, type, value, 0, value.Length, comment); - } - - void WriteString(string name, string value) - { - WriteString(name, value, null); - } - void WriteString(string name, string value, string type) - { - WriteString(name, value, type, String.Empty); - } void WriteString(string name, string value, string type, string comment) { writer.WriteStartElement("data"); writer.WriteAttributeString("name", name); if (type != null) + { writer.WriteAttributeString("type", type); + } + else + { + writer.WriteAttributeString("xml:space", "preserve"); + } writer.WriteStartElement("value"); writer.WriteString(value); writer.WriteEndElement(); - if (!(comment == null || comment.Equals(String.Empty))) + if (!string.IsNullOrEmpty(comment)) { writer.WriteStartElement("comment"); writer.WriteString(comment); @@ -221,34 +192,16 @@ void WriteString(string name, string value, string type, string comment) public void AddResource(string name, byte[] value) { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (written) - throw new InvalidOperationException("The resource is already generated."); - - if (writer == null) - InitWriter(); - - WriteBytes(name, value.GetType(), value, null); + AddResource(name, value, string.Empty); } public void AddResource(string name, object value) { - AddResource(name, value, String.Empty); + AddResource(name, value, string.Empty); } private void AddResource(string name, object value, string comment) { - if (value is string) - { - AddResource(name, (string)value, comment); - return; - } - if (name == null) throw new ArgumentNullException(nameof(name)); @@ -258,59 +211,74 @@ private void AddResource(string name, object value, string comment) if (writer == null) InitWriter(); - if (value is byte[]) - { - WriteBytes(name, value.GetType(), (byte[])value, comment); - return; - } - if (value is ResourceSerializedObject rso) - { - var bytes = rso.GetBytes(); - WriteBytes(name, null, bytes, 0, bytes.Length, comment); - return; - } - - if (value == null) - { - // nulls written as ResXNullRef - WriteString(name, "", ResXNullRefTypeName, comment); - return; - } - - if (value != null && !value.GetType().IsSerializable) - throw new InvalidOperationException(String.Format("The element '{0}' of type '{1}' is not serializable.", name, value.GetType().Name)); - - TypeConverter converter = TypeDescriptor.GetConverter(value); - - if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string))) + switch (value) { - string str = (string)converter.ConvertToInvariantString(value); - WriteString(name, str, value.GetType().AssemblyQualifiedName, comment); - return; + case null: + // nulls written as ResXNullRef + WriteString(name, "", ResXNullRefTypeName, comment); + break; + case string s: + WriteString(name, s, null, comment); + break; + case bool bo: + WriteString(name, bo.ToString(CultureInfo.InvariantCulture), "System.Boolean" + MSCorLibAssemblyName, comment); + break; + case char ch: + WriteString(name, ch.ToString(CultureInfo.InvariantCulture), "System.Char" + MSCorLibAssemblyName, comment); + break; + case sbyte sb: + WriteString(name, sb.ToString(CultureInfo.InvariantCulture), "System.SByte" + MSCorLibAssemblyName, comment); + break; + case byte b: + WriteString(name, b.ToString(CultureInfo.InvariantCulture), "System.Byte" + MSCorLibAssemblyName, comment); + break; + case short sh: + WriteString(name, sh.ToString(CultureInfo.InvariantCulture), "System.Int16" + MSCorLibAssemblyName, comment); + break; + case ushort ush: + WriteString(name, ush.ToString(CultureInfo.InvariantCulture), "System.UInt16" + MSCorLibAssemblyName, comment); + break; + case int i: + WriteString(name, i.ToString(CultureInfo.InvariantCulture), "System.Int32" + MSCorLibAssemblyName, comment); + break; + case uint u: + WriteString(name, u.ToString(CultureInfo.InvariantCulture), "System.UInt32" + MSCorLibAssemblyName, comment); + break; + case long l: + WriteString(name, l.ToString(CultureInfo.InvariantCulture), "System.Int64" + MSCorLibAssemblyName, comment); + break; + case ulong ul: + WriteString(name, ul.ToString(CultureInfo.InvariantCulture), "System.UInt64" + MSCorLibAssemblyName, comment); + break; + case float f: + WriteString(name, f.ToString(CultureInfo.InvariantCulture), "System.Single" + MSCorLibAssemblyName, comment); + break; + case double d: + WriteString(name, d.ToString(CultureInfo.InvariantCulture), "System.Double" + MSCorLibAssemblyName, comment); + break; + case decimal m: + WriteString(name, m.ToString(CultureInfo.InvariantCulture), "System.Decimal" + MSCorLibAssemblyName, comment); + break; + case DateTime dt: + WriteString(name, dt.ToString(CultureInfo.InvariantCulture), "System.DateTime" + MSCorLibAssemblyName, comment); + break; + case TimeSpan sp: + WriteString(name, sp.ToString(), "System.TimeSpan" + MSCorLibAssemblyName, comment); + break; + case byte[] array: + WriteBytes(name, "System.Byte[]" + MSCorLibAssemblyName, array, 0, array.Length, comment); + break; + case MemoryStream memoryStream: + var arr = memoryStream.ToArray(); + WriteBytes(name, null, arr, 0, arr.Length, comment); + break; + case ResourceSerializedObject rso: + var bytes = rso.GetBytes(); + WriteBytes(name, null, bytes, 0, bytes.Length, comment); + break; + default: + throw new NotSupportedException($"Value '{value}' of type {value.GetType().FullName} is not supported by this version of ResXResourceWriter. Use byte arrays or streams instead."); } - - if (converter != null && converter.CanConvertTo(typeof(byte[])) && converter.CanConvertFrom(typeof(byte[]))) - { - byte[] b = (byte[])converter.ConvertTo(value, typeof(byte[])); - WriteBytes(name, value.GetType(), b, comment); - return; - } - - MemoryStream ms = new MemoryStream(); - BinaryFormatter fmt = new BinaryFormatter(); - try - { - fmt.Serialize(ms, value); - } - catch (Exception e) - { - throw new InvalidOperationException("Cannot add a " + value.GetType() + - "because it cannot be serialized: " + - e.Message); - } - - WriteBytes(name, null, ms.GetBuffer(), 0, (int)ms.Length, comment); - ms.Close(); } public void AddResource(string name, string value) @@ -318,179 +286,6 @@ public void AddResource(string name, string value) AddResource(name, value, string.Empty); } - private void AddResource(string name, string value, string comment) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (written) - throw new InvalidOperationException("The resource is already generated."); - - if (writer == null) - InitWriter(); - - WriteString(name, value, null, comment); - } - - public void AddMetadata(string name, string value) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (written) - throw new InvalidOperationException("The resource is already generated."); - - if (writer == null) - InitWriter(); - - writer.WriteStartElement("metadata"); - writer.WriteAttributeString("name", name); - writer.WriteAttributeString("xml:space", "preserve"); - - writer.WriteElementString("value", value); - - writer.WriteEndElement(); - } - - public void AddMetadata(string name, byte[] value) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (written) - throw new InvalidOperationException("The resource is already generated."); - - if (writer == null) - InitWriter(); - - writer.WriteStartElement("metadata"); - writer.WriteAttributeString("name", name); - - writer.WriteAttributeString("type", value.GetType().AssemblyQualifiedName); - - writer.WriteStartElement("value"); - WriteNiceBase64(value, 0, value.Length); - writer.WriteEndElement(); - - writer.WriteEndElement(); - } - - public void AddMetadata(string name, object value) - { - if (value is string) - { - AddMetadata(name, (string)value); - return; - } - - if (value is byte[]) - { - AddMetadata(name, (byte[])value); - return; - } - - if (name == null) - throw new ArgumentNullException(nameof(name)); - - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (!value.GetType().IsSerializable) - throw new InvalidOperationException(String.Format("The element '{0}' of type '{1}' is not serializable.", name, value.GetType().Name)); - - if (written) - throw new InvalidOperationException("The resource is already generated."); - - if (writer == null) - InitWriter(); - - Type type = value.GetType(); - - TypeConverter converter = TypeDescriptor.GetConverter(value); - if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string))) - { - string str = (string)converter.ConvertToInvariantString(value); - writer.WriteStartElement("metadata"); - writer.WriteAttributeString("name", name); - if (type != null) - writer.WriteAttributeString("type", type.AssemblyQualifiedName); - writer.WriteStartElement("value"); - writer.WriteString(str); - writer.WriteEndElement(); - writer.WriteEndElement(); - writer.WriteWhitespace("\n "); - return; - } - - if (converter != null && converter.CanConvertTo(typeof(byte[])) && converter.CanConvertFrom(typeof(byte[]))) - { - byte[] b = (byte[])converter.ConvertTo(value, typeof(byte[])); - writer.WriteStartElement("metadata"); - writer.WriteAttributeString("name", name); - - if (type != null) - { - writer.WriteAttributeString("type", type.AssemblyQualifiedName); - writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType); - writer.WriteStartElement("value"); - WriteNiceBase64(b, 0, b.Length); - } - else - { - writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType); - writer.WriteStartElement("value"); - writer.WriteBase64(b, 0, b.Length); - } - - writer.WriteEndElement(); - writer.WriteEndElement(); - return; - } - - MemoryStream ms = new MemoryStream(); - BinaryFormatter fmt = new BinaryFormatter(); - try - { - fmt.Serialize(ms, value); - } - catch (Exception e) - { - throw new InvalidOperationException("Cannot add a " + value.GetType() + - "because it cannot be serialized: " + - e.Message); - } - - writer.WriteStartElement("metadata"); - writer.WriteAttributeString("name", name); - - if (type != null) - { - writer.WriteAttributeString("type", type.AssemblyQualifiedName); - writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType); - writer.WriteStartElement("value"); - WriteNiceBase64(ms.GetBuffer(), 0, ms.GetBuffer().Length); - } - else - { - writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType); - writer.WriteStartElement("value"); - writer.WriteBase64(ms.GetBuffer(), 0, ms.GetBuffer().Length); - } - - writer.WriteEndElement(); - writer.WriteEndElement(); - ms.Close(); - } - public void Close() { if (writer != null) @@ -529,7 +324,7 @@ protected virtual void Dispose(bool disposing) Close(); } - static string schema = @" + static readonly string schema = @" @@ -559,11 +354,9 @@ protected virtual void Dispose(bool disposing) ".Replace("'", "\"").Replace("\t", " "); - #region Public Properties public string BasePath { get { return base_path; } set { base_path = value; } } - #endregion } } diff --git a/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj b/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj index 23c06798ed..27a0ad480e 100644 --- a/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj +++ b/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj @@ -54,7 +54,6 @@ - diff --git a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs index 681397143c..1e46531cfc 100644 --- a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs +++ b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs @@ -342,6 +342,12 @@ int DumpPackageAssemblies(string packageFileName, string outputDirectory, Comman { Stream contents; + if (entry.RelativePath.Replace('\\', '/').Contains("../", StringComparison.Ordinal) || Path.IsPathRooted(entry.RelativePath)) + { + app.Error.WriteLine($"Skipping single-file entry '{entry.RelativePath}' because it might refer to a location outside of the bundle output directory."); + continue; + } + if (entry.CompressedSize == 0) { contents = new UnmanagedMemoryStream(packageView.SafeMemoryMappedViewHandle, entry.Offset, entry.Size); @@ -357,7 +363,7 @@ int DumpPackageAssemblies(string packageFileName, string outputDirectory, Comman if (decompressedStream.Length != entry.Size) { - app.Error.WriteLine($"Corrupted single-file entry '${entry.RelativePath}'. Declared decompressed size '${entry.Size}' is not the same as actual decompressed size '${decompressedStream.Length}'."); + app.Error.WriteLine($"Corrupted single-file entry '{entry.RelativePath}'. Declared decompressed size '{entry.Size}' is not the same as actual decompressed size '{decompressedStream.Length}'."); return ProgramExitCodes.EX_DATAERR; } @@ -365,7 +371,9 @@ int DumpPackageAssemblies(string packageFileName, string outputDirectory, Comman contents = decompressedStream; } - using (var fileStream = File.Create(Path.Combine(outputDirectory, entry.RelativePath))) + string target = Path.Combine(outputDirectory, entry.RelativePath); + Directory.CreateDirectory(Path.GetDirectoryName(target)); + using (var fileStream = File.Create(target)) { contents.CopyTo(fileStream); } diff --git a/ICSharpCode.ILSpyX/AssemblyListManager.cs b/ICSharpCode.ILSpyX/AssemblyListManager.cs index 8d28773528..5791ee8d08 100644 --- a/ICSharpCode.ILSpyX/AssemblyListManager.cs +++ b/ICSharpCode.ILSpyX/AssemblyListManager.cs @@ -23,17 +23,10 @@ using System.Xml.Linq; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpyX { - public interface ISettingsProvider - { - XElement this[XName section] { get; } - - void Update(Action action); - ISettingsProvider Load(); - } - /// /// Manages the available assembly lists. /// diff --git a/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj b/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj index a1490aeb3e..86b9a2503b 100644 --- a/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj +++ b/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj @@ -4,6 +4,7 @@ net6.0 enable true + nullable True ..\ICSharpCode.Decompiler\ICSharpCode.Decompiler.snk @@ -63,14 +64,11 @@ - - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ICSharpCode.ILSpyX/LoadedPackage.cs b/ICSharpCode.ILSpyX/LoadedPackage.cs index f99b78b9b0..8d0d99f905 100644 --- a/ICSharpCode.ILSpyX/LoadedPackage.cs +++ b/ICSharpCode.ILSpyX/LoadedPackage.cs @@ -221,7 +221,7 @@ public override Stream TryOpenStream() deflateStream.CopyTo(decompressedStream); if (decompressedStream.Length != entry.Size) { - throw new InvalidDataException($"Corrupted single-file entry '${entry.RelativePath}'. Declared decompressed size '${entry.Size}' is not the same as actual decompressed size '${decompressedStream.Length}'."); + throw new InvalidDataException($"Corrupted single-file entry '{entry.RelativePath}'. Declared decompressed size '{entry.Size}' is not the same as actual decompressed size '{decompressedStream.Length}'."); } decompressedStream.Seek(0, SeekOrigin.Begin); diff --git a/ICSharpCode.ILSpyX/Search/AbstractEntitySearchStrategy.cs b/ICSharpCode.ILSpyX/Search/AbstractEntitySearchStrategy.cs index a64a58b19f..036004b1d4 100644 --- a/ICSharpCode.ILSpyX/Search/AbstractEntitySearchStrategy.cs +++ b/ICSharpCode.ILSpyX/Search/AbstractEntitySearchStrategy.cs @@ -37,12 +37,12 @@ protected AbstractEntitySearchStrategy(ILanguage language, ApiVisibility apiVisi this.apiVisibility = apiVisibility; } - protected bool CheckVisibility(IEntity entity) + protected bool CheckVisibility(IEntity? entity) { if (apiVisibility == ApiVisibility.All) return true; - do + while (entity != null) { if (apiVisibility == ApiVisibility.PublicOnly) { @@ -58,7 +58,6 @@ protected bool CheckVisibility(IEntity entity) } entity = entity.DeclaringTypeDefinition; } - while (entity != null); return true; } @@ -67,7 +66,7 @@ protected bool IsInNamespaceOrAssembly(IEntity entity) { if (searchRequest.InAssembly != null) { - if (!entity.ParentModule.FullAssemblyName.Contains(searchRequest.InAssembly)) + if (entity.ParentModule == null || !entity.ParentModule.FullAssemblyName.Contains(searchRequest.InAssembly, StringComparison.OrdinalIgnoreCase)) { return false; } @@ -79,7 +78,7 @@ protected bool IsInNamespaceOrAssembly(IEntity entity) { return entity.Namespace.Length == 0; } - else if (!entity.Namespace.Contains(searchRequest.InNamespace)) + else if (!entity.Namespace.Contains(searchRequest.InNamespace, StringComparison.OrdinalIgnoreCase)) { return false; } diff --git a/ICSharpCode.ILSpyX/Search/AssemblySearchStrategy.cs b/ICSharpCode.ILSpyX/Search/AssemblySearchStrategy.cs index 5f32db836c..3d0b1fb876 100644 --- a/ICSharpCode.ILSpyX/Search/AssemblySearchStrategy.cs +++ b/ICSharpCode.ILSpyX/Search/AssemblySearchStrategy.cs @@ -41,19 +41,27 @@ public override void Search(PEFile module, CancellationToken cancellationToken) if (searchKind == AssemblySearchKind.NameOrFileName) { - string localName = GetNameToMatch(module, AssemblySearchKind.Name); - string fileName = Path.GetFileName(GetNameToMatch(module, AssemblySearchKind.FilePath)); - if (IsMatch(localName) || IsMatch(fileName)) + string? localName = GetNameToMatch(module, AssemblySearchKind.Name); + string? filePath = GetNameToMatch(module, AssemblySearchKind.FilePath); + if (localName != null && IsMatch(localName)) + { OnFoundResult(module); + } + else if (filePath != null) + { + string fileName = Path.GetFileName(filePath); + if (IsMatch(fileName)) + OnFoundResult(module); + } return; } - string name = GetNameToMatch(module, searchKind); - if (IsMatch(name)) + string? name = GetNameToMatch(module, searchKind); + if (name != null && IsMatch(name)) OnFoundResult(module); } - string GetNameToMatch(PEFile module, AssemblySearchKind kind) + string? GetNameToMatch(PEFile module, AssemblySearchKind kind) { switch (kind) { diff --git a/ICSharpCode.ILSpyX/Search/CSharpLexer.cs b/ICSharpCode.ILSpyX/Search/CSharpLexer.cs index ab689b95af..05a6edb975 100644 --- a/ICSharpCode.ILSpyX/Search/CSharpLexer.cs +++ b/ICSharpCode.ILSpyX/Search/CSharpLexer.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; diff --git a/ICSharpCode.ILSpyX/Search/LiteralSearchStrategy.cs b/ICSharpCode.ILSpyX/Search/LiteralSearchStrategy.cs index 81d0487dd0..a5b813820c 100644 --- a/ICSharpCode.ILSpyX/Search/LiteralSearchStrategy.cs +++ b/ICSharpCode.ILSpyX/Search/LiteralSearchStrategy.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Concurrent; +using System.Diagnostics; using System.Reflection.Metadata; using System.Threading; @@ -35,8 +36,8 @@ namespace ICSharpCode.ILSpyX.Search { public class LiteralSearchStrategy : AbstractEntitySearchStrategy { - readonly TypeCode searchTermLiteralType; - readonly object searchTermLiteralValue; + readonly TypeCode searchTermLiteralType = TypeCode.Empty; + readonly object? searchTermLiteralValue; public LiteralSearchStrategy(ILanguage language, ApiVisibility apiVisibility, SearchRequest request, IProducerConsumerCollection resultQueue) @@ -90,9 +91,10 @@ public override void Search(PEFile module, CancellationToken cancellationToken) if (!md.HasBody() || !MethodIsLiteralMatch(module, md)) continue; var method = ((MetadataModule)typeSystem.MainModule).GetDefinition(handle); - if (!CheckVisibility(method) || !IsInNamespaceOrAssembly(method)) + var result = method.AccessorOwner ?? method; + if (!CheckVisibility(result) || !IsInNamespaceOrAssembly(result)) continue; - OnFoundResult(method); + OnFoundResult(result); } foreach (var handle in metadata.FieldDefinitions) @@ -130,10 +132,12 @@ bool IsLiteralMatch(MetadataReader metadata, object? val) case TypeCode.Single: case TypeCode.Double: case TypeCode.String: + Debug.Assert(searchTermLiteralValue != null); return searchTermLiteralValue.Equals(val); default: // substring search with searchTerm - return IsMatch(val.ToString()); + string? valAsString = val.ToString(); + return valAsString != null && IsMatch(valAsString); } } @@ -142,6 +146,7 @@ bool MethodIsLiteralMatch(PEFile module, MethodDefinition methodDefinition) var blob = module.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress).GetILReader(); if (searchTermLiteralType == TypeCode.Int64) { + Debug.Assert(searchTermLiteralValue != null); long val = (long)searchTermLiteralValue; while (blob.RemainingBytes > 0) { @@ -208,6 +213,7 @@ bool MethodIsLiteralMatch(PEFile module, MethodDefinition methodDefinition) } else if (searchTermLiteralType != TypeCode.Empty) { + Debug.Assert(searchTermLiteralValue != null); ILOpCode expectedCode; switch (searchTermLiteralType) { diff --git a/ICSharpCode.ILSpyX/Search/SearchResult.cs b/ICSharpCode.ILSpyX/Search/SearchResult.cs index d0e94bf402..abe18ae0d6 100644 --- a/ICSharpCode.ILSpyX/Search/SearchResult.cs +++ b/ICSharpCode.ILSpyX/Search/SearchResult.cs @@ -42,14 +42,18 @@ public class SearchResult public float Fitness { get; set; } +#nullable disable public string Name { get; set; } public string Location { get; set; } public string Assembly { get; set; } +#nullable enable public object? ToolTip { get; set; } +#nullable disable public object Image { get; set; } public object LocationImage { get; set; } public object AssemblyImage { get; set; } +#nullable enable public override string ToString() { @@ -76,25 +80,33 @@ public int Compare(SearchResult? x, SearchResult? y) public class MemberSearchResult : SearchResult { +#nullable disable public IEntity Member { get; set; } public override object Reference => Member; +#nullable enable } public class ResourceSearchResult : SearchResult { +#nullable disable public Resource Resource { get; set; } +#nullable enable public override object Reference => ValueTuple.Create(Resource, Name); } public class AssemblySearchResult : SearchResult { +#nullable disable public PEFile Module { get; set; } public override object Reference => Module; +#nullable enable } public class NamespaceSearchResult : SearchResult { +#nullable disable public INamespace Namespace { get; set; } public override object Reference => Namespace; +#nullable enable } } \ No newline at end of file diff --git a/ICSharpCode.ILSpyX/Settings/DefaultSettingsFilePathProvider.cs b/ICSharpCode.ILSpyX/Settings/DefaultSettingsFilePathProvider.cs new file mode 100644 index 0000000000..7ab15f62ab --- /dev/null +++ b/ICSharpCode.ILSpyX/Settings/DefaultSettingsFilePathProvider.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace ICSharpCode.ILSpyX.Settings +{ + /// + /// Used in scenarios where the user passes a path and that is to be used, eg ilspycmd parameter + /// + public class DefaultSettingsFilePathProvider : ISettingsFilePathProvider + { + private readonly string _providedPath; + + public DefaultSettingsFilePathProvider(string providedPath) + { + _providedPath = providedPath; + } + + public string GetSettingsFilePath() + { + return _providedPath; + } + } +} diff --git a/ILSpy/ILSpySettings.cs b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs similarity index 83% rename from ILSpy/ILSpySettings.cs rename to ICSharpCode.ILSpyX/Settings/ILSpySettings.cs index 77eb58bc94..63e94299b5 100644 --- a/ILSpy/ILSpySettings.cs +++ b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs @@ -22,15 +22,18 @@ using System.Xml; using System.Xml.Linq; -using ICSharpCode.ILSpyX; - -namespace ICSharpCode.ILSpy +namespace ICSharpCode.ILSpyX.Settings { /// /// Manages IL Spy settings. /// public class ILSpySettings : ISettingsProvider { + /// + /// This settings file path provider determines where to load settings file from, includes filename + /// + public static ISettingsFilePathProvider? SettingsFilePathProvider { get; set; } + readonly XElement root; ILSpySettings() @@ -62,6 +65,11 @@ public static ILSpySettings Load() try { XDocument doc = LoadWithoutCheckingCharacters(GetConfigFile()); + if (null == doc.Root) + { + return new ILSpySettings(); + } + return new ILSpySettings(doc.Root); } catch (IOException) @@ -87,7 +95,7 @@ public static void SaveSettings(XElement section) { Update( delegate (XElement root) { - XElement existingElement = root.Element(section.Name); + XElement? existingElement = root.Element(section.Name); if (existingElement != null) existingElement.ReplaceWith(section); else @@ -113,14 +121,14 @@ public static void Update(Action action) catch (IOException) { // ensure the directory exists - Directory.CreateDirectory(Path.GetDirectoryName(config)); + Directory.CreateDirectory(Path.GetDirectoryName(config)!); doc = new XDocument(new XElement("ILSpy")); } catch (XmlException) { doc = new XDocument(new XElement("ILSpy")); } - doc.Root.SetAttributeValue("version", DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision); + doc.Root!.SetAttributeValue("version", DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision); action(doc.Root); doc.Save(config, SaveOptions.None); } @@ -133,12 +141,11 @@ void ISettingsProvider.Update(Action action) static string GetConfigFile() { - if (App.CommandLineArguments.ConfigFile != null) - return App.CommandLineArguments.ConfigFile; - string localPath = Path.Combine(Path.GetDirectoryName(typeof(MainWindow).Assembly.Location), "ILSpy.xml"); - if (File.Exists(localPath)) - return localPath; - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ICSharpCode\\ILSpy.xml"); + if (null != SettingsFilePathProvider) + return SettingsFilePathProvider.GetSettingsFilePath(); + + throw new ArgumentNullException(nameof(SettingsFilePathProvider)); + // return "ILSpy.xml"; } ISettingsProvider ISettingsProvider.Load() diff --git a/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs b/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs new file mode 100644 index 0000000000..363dba66be --- /dev/null +++ b/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Xml.Linq; + +namespace ICSharpCode.ILSpyX.Settings +{ + public interface IMiscSettings + { + public bool AllowMultipleInstances { get; set; } + public bool LoadPreviousAssemblies { get; set; } + + public static void Save(XElement root, IMiscSettings miscSettings) + { + var section = new XElement("MiscSettings"); + section.SetAttributeValue(nameof(miscSettings.AllowMultipleInstances), miscSettings.AllowMultipleInstances); + section.SetAttributeValue(nameof(miscSettings.LoadPreviousAssemblies), miscSettings.LoadPreviousAssemblies); + + XElement? existingElement = root.Element("MiscSettings"); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + root.Add(section); + } + } +} diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs b/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs new file mode 100644 index 0000000000..15b16a09b0 --- /dev/null +++ b/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace ICSharpCode.ILSpyX.Settings +{ + public interface ISettingsFilePathProvider + { + string GetSettingsFilePath(); + } +} diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs new file mode 100644 index 0000000000..3f62dba764 --- /dev/null +++ b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +using static System.Collections.Specialized.BitVector32; + +namespace ICSharpCode.ILSpyX.Settings +{ + public interface ISettingsProvider + { + XElement this[XName section] { get; } + + void Update(Action action); + ISettingsProvider Load(); + + public static ICSharpCode.Decompiler.DecompilerSettings LoadDecompilerSettings(ISettingsProvider settingsProvider) + { + XElement e = settingsProvider["DecompilerSettings"]; + var newSettings = new Decompiler.DecompilerSettings(); + var properties = typeof(Decompiler.DecompilerSettings).GetProperties() + .Where(p => p.GetCustomAttribute()?.Browsable != false); + foreach (var p in properties) + { + var value = (bool?)e.Attribute(p.Name); + if (value.HasValue) + p.SetValue(newSettings, value.Value); + } + return newSettings; + } + + public static void SaveDecompilerSettings(XElement root, ICSharpCode.Decompiler.DecompilerSettings newSettings) + { + var properties = typeof(Decompiler.DecompilerSettings).GetProperties() + .Where(p => p.GetCustomAttribute()?.Browsable != false); + + XElement section = new XElement("DecompilerSettings"); + foreach (var p in properties) + { + section.SetAttributeValue(p.Name, p.GetValue(newSettings)); + } + + XElement? existingElement = root.Element("DecompilerSettings"); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + root.Add(section); + } + } +} diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs b/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs new file mode 100644 index 0000000000..1735289efa --- /dev/null +++ b/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace ICSharpCode.ILSpyX.Settings +{ + public interface ISettingsSection + { + // This should be abstract, but that needs C# 11.0 (see IParseable) + // Keep it to be enabled in the future + public static TSelf Load(ISettingsProvider settingsProvider) + { + throw new NotImplementedException(); + } + } +} diff --git a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs b/ICSharpCode.ILSpyX/Settings/MiscSettings.cs new file mode 100644 index 0000000000..d349764d47 --- /dev/null +++ b/ICSharpCode.ILSpyX/Settings/MiscSettings.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Xml.Linq; + +namespace ICSharpCode.ILSpyX.Settings +{ + public class MiscSettings : IMiscSettings, ISettingsSection + { + private MiscSettings() + { + } + + public bool AllowMultipleInstances { get; set; } + public bool LoadPreviousAssemblies { get; set; } + + public static MiscSettings Load(ISettingsProvider settingsProvider) + { + XElement e = settingsProvider["MiscSettings"]; + var s = new MiscSettings(); + s.AllowMultipleInstances = (bool?)e.Attribute(nameof(s.AllowMultipleInstances)) ?? false; + s.LoadPreviousAssemblies = (bool?)e.Attribute(nameof(s.LoadPreviousAssemblies)) ?? true; + + return s; + } + } +} diff --git a/ILSpy.AddIn.Shared/Commands/OpenILSpyCommand.cs b/ILSpy.AddIn.Shared/Commands/OpenILSpyCommand.cs index 6759a54c12..13caafd9ce 100644 --- a/ILSpy.AddIn.Shared/Commands/OpenILSpyCommand.cs +++ b/ILSpy.AddIn.Shared/Commands/OpenILSpyCommand.cs @@ -47,12 +47,6 @@ protected virtual void OnBeforeQueryStatus(object sender, EventArgs e) protected abstract void OnExecute(object sender, EventArgs e); - protected string GetILSpyPath() - { - var basePath = Path.GetDirectoryName(typeof(ILSpyAddInPackage).Assembly.Location); - return Path.Combine(basePath, "ILSpy", "ILSpy.exe"); - } - protected void OpenAssembliesInILSpy(ILSpyParameters parameters) { ThreadHelper.ThrowIfNotOnUIThread(); diff --git a/ILSpy.AddIn.Shared/ILSpyInstance.cs b/ILSpy.AddIn.Shared/ILSpyInstance.cs index 8144eb1402..dc90e39a20 100644 --- a/ILSpy.AddIn.Shared/ILSpyInstance.cs +++ b/ILSpy.AddIn.Shared/ILSpyInstance.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -31,8 +32,15 @@ public ILSpyInstance(ILSpyParameters parameters = null) static string GetILSpyPath() { + // Only VS2022 supports arm64, so we can gloss over 2017-2019 support + string archPathSegment = "x64"; + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + archPathSegment = "arm64"; + } + var basePath = Path.GetDirectoryName(typeof(ILSpyAddInPackage).Assembly.Location); - return Path.Combine(basePath, "ILSpy", "ILSpy.exe"); + return Path.Combine(basePath, archPathSegment, "ILSpy", "ILSpy.exe"); } public void Start() diff --git a/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj b/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj index 88f9c52942..5d09db6572 100644 --- a/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj +++ b/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj @@ -41,9 +41,9 @@ - + all - runtime; build; native; contentfiles; analyzers + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -55,21 +55,6 @@ - - - false - true - - - false - true - - - false - true - - - @@ -89,16 +74,23 @@ - ..\ILSpy\bin\$(Configuration)\net6.0-windows\ + ..\ILSpy\bin\$(Configuration)\net6.0-windows\win-x64\publish\fwdependent\ + ..\ILSpy\bin\$(Configuration)\net6.0-windows\win-arm64\publish\fwdependent\ - - \ILSpy\zh-Hans\ + + \x64\ILSpy\zh-Hans\ + + + \x64\ILSpy + + + \arm64\ILSpy\zh-Hans\ - - \ILSpy + + \arm64\ILSpy diff --git a/ILSpy.AddIn.VS2022/source.extension.vsixmanifest.template b/ILSpy.AddIn.VS2022/source.extension.vsixmanifest.template index db13ce77ec..be14883354 100644 --- a/ILSpy.AddIn.VS2022/source.extension.vsixmanifest.template +++ b/ILSpy.AddIn.VS2022/source.extension.vsixmanifest.template @@ -13,6 +13,9 @@ amd64 + + arm64 + diff --git a/ILSpy.AddIn.slnf b/ILSpy.AddIn.slnf deleted file mode 100644 index 3dbf7547fb..0000000000 --- a/ILSpy.AddIn.slnf +++ /dev/null @@ -1,21 +0,0 @@ -{ - "solution": { - "path": "ILSpy.sln", - "projects": [ - "ICSharpCode.Decompiler.TestRunner\\ICSharpCode.Decompiler.TestRunner.csproj", - "ICSharpCode.Decompiler.Tests\\ICSharpCode.Decompiler.Tests.csproj", - "ICSharpCode.Decompiler\\ICSharpCode.Decompiler.csproj", - "ICSharpCode.ILSpyX\\ICSharpCode.ILSpyX.csproj", - "ILSpy.AddIn.Shared\\ILSpy.AddIn.Shared.shproj", - "ILSpy.AddIn.VS2022\\ILSpy.AddIn.VS2022.csproj", - "ILSpy.AddIn\\ILSpy.AddIn.csproj", - "ILSpy.BamlDecompiler.Tests\\ILSpy.BamlDecompiler.Tests.csproj", - "ILSpy.BamlDecompiler\\ILSpy.BamlDecompiler.csproj", - "ILSpy.ReadyToRun\\ILSpy.ReadyToRun.csproj", - "ILSpy.Tests\\ILSpy.Tests.csproj", - "ILSpy\\ILSpy.csproj", - "SharpTreeView\\ICSharpCode.TreeView.csproj", - "TestPlugin\\TestPlugin.csproj" - ] - } -} \ No newline at end of file diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj index 1754260579..021cfed03e 100644 --- a/ILSpy.AddIn/ILSpy.AddIn.csproj +++ b/ILSpy.AddIn/ILSpy.AddIn.csproj @@ -47,7 +47,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -61,21 +61,6 @@ - - - false - true - - - false - true - - - false - true - - - @@ -95,7 +80,7 @@ - ..\ILSpy\bin\$(Configuration)\net6.0-windows\ + ..\ILSpy\bin\$(Configuration)\net6.0-windows\win-x64\publish\fwdependent\ diff --git a/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs b/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs index 5c68179f14..27acff4ca8 100644 --- a/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs +++ b/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs @@ -154,6 +154,12 @@ public void Issue2116() RunTest("cases/issue2116"); } + [Test] + public void ReadonlyProperty() + { + RunTest("cases/readonlyproperty"); + } + #region RunTest void RunTest(string name) { diff --git a/ILSpy.BamlDecompiler.Tests/Cases/MyControl.xaml.cs b/ILSpy.BamlDecompiler.Tests/Cases/MyControl.xaml.cs index c837274600..53f50b8490 100644 --- a/ILSpy.BamlDecompiler.Tests/Cases/MyControl.xaml.cs +++ b/ILSpy.BamlDecompiler.Tests/Cases/MyControl.xaml.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Collections; using System.Windows.Controls; namespace ILSpy.BamlDecompiler.Tests.Cases @@ -25,6 +26,8 @@ namespace ILSpy.BamlDecompiler.Tests.Cases /// public partial class MyControl : UserControl { + public IList DataTypes { get; } = new ArrayList(); + public MyControl() { InitializeComponent(); diff --git a/ILSpy.BamlDecompiler.Tests/Cases/ReadonlyProperty.xaml b/ILSpy.BamlDecompiler.Tests/Cases/ReadonlyProperty.xaml new file mode 100644 index 0000000000..9f9e39f3db --- /dev/null +++ b/ILSpy.BamlDecompiler.Tests/Cases/ReadonlyProperty.xaml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ILSpy.BamlDecompiler.Tests/Cases/ReadonlyProperty.xaml.cs b/ILSpy.BamlDecompiler.Tests/Cases/ReadonlyProperty.xaml.cs new file mode 100644 index 0000000000..6e4d81f8e0 --- /dev/null +++ b/ILSpy.BamlDecompiler.Tests/Cases/ReadonlyProperty.xaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace ILSpy.BamlDecompiler.Tests.Cases +{ + /// + /// Interaction logic for ReadonlyProperty.xaml + /// + public partial class ReadonlyProperty : Window + { + public ReadonlyProperty() + { + InitializeComponent(); + } + } +} diff --git a/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj b/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj index f3159cecf2..dc979f1526 100644 --- a/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj +++ b/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj @@ -58,6 +58,7 @@ MyControl.xaml + Resources.xaml @@ -102,6 +103,9 @@ + + MSBuild:Compile + Always diff --git a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs index be0a6ae1cc..4ac408de5e 100644 --- a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs +++ b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs @@ -72,6 +72,8 @@ public string WriteResourceToFile(LoadedAssembly assembly, string fileName, Stre { fileName = Path.ChangeExtension(fileName, ".xaml"); } + context.AdditionalProperties.Add("Generator", "MSBuild:Compile"); + context.AdditionalProperties.Add("SubType", "Designer"); string saveFileName = Path.Combine(context.DecompilationOptions.SaveAsProjectDirectory, fileName); Directory.CreateDirectory(Path.GetDirectoryName(saveFileName)); result.Xaml.Save(saveFileName); diff --git a/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj b/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj index 55a813f4fe..e222ba25a0 100644 --- a/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj +++ b/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj @@ -32,11 +32,6 @@ - - - - - diff --git a/ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs b/ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs index 492646eb25..46af966472 100644 --- a/ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs +++ b/ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs @@ -21,9 +21,13 @@ THE SOFTWARE. */ using System.Collections.Generic; +using System.DirectoryServices.ActiveDirectory; using System.Linq; +using System.Windows.Forms; using System.Xml.Linq; +using ICSharpCode.Decompiler.TypeSystem; + using ILSpy.BamlDecompiler.Xaml; namespace ILSpy.BamlDecompiler.Rewrite @@ -64,8 +68,15 @@ bool RewriteElement(XamlContext ctx, XElement parent, XElement elem) { var type = parent.Annotation(); var property = elem.Annotation(); - if ((property == null || type == null) && elem.Name != key) - return false; + + if (elem.Name != key) + { + if (property == null || type == null) + return false; + + if (property.ResolvedMember is IProperty { CanSet: false }) + return false; + } if (elem.Elements().Count() != 1 || elem.Attributes().Any(t => t.Name.Namespace != XNamespace.Xmlns)) return false; diff --git a/ILSpy.Installer.sln b/ILSpy.Installer.sln new file mode 100644 index 0000000000..64b205024c --- /dev/null +++ b/ILSpy.Installer.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33723.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ILSpy.Installer", "ILSpy.Installer\ILSpy.Installer.csproj", "{D27793B2-C3F9-4410-AAD0-E117BEDCCEB0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.Decompiler", "ICSharpCode.Decompiler\ICSharpCode.Decompiler.csproj", "{3FE7AE02-D69D-4C76-9BC0-CF700DFD09FE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D27793B2-C3F9-4410-AAD0-E117BEDCCEB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D27793B2-C3F9-4410-AAD0-E117BEDCCEB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D27793B2-C3F9-4410-AAD0-E117BEDCCEB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D27793B2-C3F9-4410-AAD0-E117BEDCCEB0}.Release|Any CPU.Build.0 = Release|Any CPU + {3FE7AE02-D69D-4C76-9BC0-CF700DFD09FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FE7AE02-D69D-4C76-9BC0-CF700DFD09FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FE7AE02-D69D-4C76-9BC0-CF700DFD09FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FE7AE02-D69D-4C76-9BC0-CF700DFD09FE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A343F649-2CFB-4022-9133-1BA55FBCE3D1} + EndGlobalSection +EndGlobal diff --git a/ILSpy.Installer.slnf b/ILSpy.Installer.slnf deleted file mode 100644 index cec81502ac..0000000000 --- a/ILSpy.Installer.slnf +++ /dev/null @@ -1,20 +0,0 @@ -{ - "solution": { - "path": "ILSpy.sln", - "projects": [ - "ICSharpCode.Decompiler.PdbProvider.Cecil\\ICSharpCode.Decompiler.PdbProvider.Cecil.csproj", - "ICSharpCode.Decompiler.TestRunner\\ICSharpCode.Decompiler.TestRunner.csproj", - "ICSharpCode.Decompiler.Tests\\ICSharpCode.Decompiler.Tests.csproj", - "ICSharpCode.Decompiler\\ICSharpCode.Decompiler.csproj", - "ICSharpCode.ILSpyX\\ICSharpCode.ILSpyX.csproj", - "ILSpy.BamlDecompiler.Tests\\ILSpy.BamlDecompiler.Tests.csproj", - "ILSpy.BamlDecompiler\\ILSpy.BamlDecompiler.csproj", - "ILSpy.ReadyToRun\\ILSpy.ReadyToRun.csproj", - "ILSpy.Tests\\ILSpy.Tests.csproj", - "ILSpy\\ILSpy.csproj", - "ILSpy.Installer\\ILSpy.Installer.csproj", - "SharpTreeView\\ICSharpCode.TreeView.csproj", - "TestPlugin\\TestPlugin.csproj" - ] - } -} \ No newline at end of file diff --git a/ILSpy.Installer/ILSpy.Installer.csproj b/ILSpy.Installer/ILSpy.Installer.csproj index 8104ccf412..bf8fa6fe55 100644 --- a/ILSpy.Installer/ILSpy.Installer.csproj +++ b/ILSpy.Installer/ILSpy.Installer.csproj @@ -7,24 +7,12 @@ - - + + - - false - true - - - false - true - - - false - true - diff --git a/ILSpy.Installer/README.md b/ILSpy.Installer/README.md new file mode 100644 index 0000000000..f83b37d2cb --- /dev/null +++ b/ILSpy.Installer/README.md @@ -0,0 +1,8 @@ +# Building the Installer + +It is mandatory to first publish(.ps1) the respective target platforms, then setup can be built, eg + +``` +msbuild ILSpy.Installer.sln /p:Configuration="Release" /p:Platform="Any CPU" +msbuild ILSpy.Installer.sln /p:Configuration="Release" /p:Platform="Any CPU" /p:DefineConstants="ARM64" +``` \ No newline at end of file diff --git a/ILSpy.Installer/setup.cs b/ILSpy.Installer/setup.cs index 7376db07ca..ce15c30891 100644 --- a/ILSpy.Installer/setup.cs +++ b/ILSpy.Installer/setup.cs @@ -19,7 +19,13 @@ static public void Main() #else var buildConfiguration = "Release"; #endif - var buildOutputDir = $@"ILSpy\bin\{buildConfiguration}\net6.0-windows"; + +#if ARM64 + var buildPlatform = "arm64"; +#else + var buildPlatform = "x64"; +#endif + var buildOutputDir = $@"ILSpy\bin\{buildConfiguration}\net6.0-windows\win-{buildPlatform}\publish\fwdependent"; var project = new Project("ILSpy", new InstallDir(@"%LocalAppData%\Programs\ILSpy", @@ -30,6 +36,12 @@ static public void Main() new Files(Path.Combine(buildOutputDir, "ILSpy.resources.dll")), new Files(Path.Combine(buildOutputDir, "ILSpy.ReadyToRun.Plugin.resources.dll")))); +#if ARM64 + project.Platform = Platform.arm64; +#else + project.Platform = Platform.x64; +#endif + project.GUID = new Guid("a12fdab1-731b-4a98-9749-d481ce8692ab"); project.Version = AppPackage.Version; project.SourceBaseDir = Path.GetDirectoryName(Environment.CurrentDirectory); @@ -59,7 +71,41 @@ static public void Main() new FileShortcut("ILSpy", @"%ProgramMenu%") }; - Compiler.BuildMsi(project, Path.Combine(Environment.CurrentDirectory, "wix", $"ILSpy-{AppPackage.Version}.msi")); + Compiler.WixLocation = GetWixBinLocationForPackage(); + Compiler.BuildMsi(project, Path.Combine(Environment.CurrentDirectory, "wix", $"ILSpy-{AppPackage.Version}-{buildPlatform}.msi")); + } + + // Copied from https://github.com/oleg-shilo/wixsharp/blob/c4f8615ce8e47c7162edb30656669d0d326f79ff/Source/src/WixSharp/Utilities/WixBinLocator.cs#L117 + private static string GetWixBinLocationForPackage() + { + //The global packages may be redirected with environment variable + //https://docs.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders + + string wixBinPackageDir; + var nugetPackagesEnvironmentVariable = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + if (nugetPackagesEnvironmentVariable.IsNotEmpty() && Directory.Exists(nugetPackagesEnvironmentVariable)) + { + wixBinPackageDir = Path.Combine(nugetPackagesEnvironmentVariable, "wixsharp.wix.bin"); + } + else + { + wixBinPackageDir = @"%userprofile%\.nuget\packages\wixsharp.wix.bin".ExpandEnvVars(); + } + + if (Directory.Exists(wixBinPackageDir)) + { + Version greatestWixBinVersion = System.IO.Directory.GetDirectories(wixBinPackageDir) + .Select(dirPath => new Version(dirPath.PathGetFileName())) + .OrderDescending() + .FirstOrDefault(); + + if (greatestWixBinVersion != null) + { + return wixBinPackageDir.PathJoin(greatestWixBinVersion.ToString(), @"tools\bin"); + } + } + + return ""; } } } diff --git a/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj b/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj index 865fc3609a..853436cc7a 100644 --- a/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj +++ b/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj @@ -37,8 +37,8 @@ - - + + $(MSBuildToolsPath)\..\..\..\..\VC\ $(VCBasePath)Auxiliary\Build\$(VCToolsVersionPropsFileNameDefault) + True + + + + 1701;1702;CA1001;CA2213 + + + + 1701;1702;CA1001;CA2213 diff --git a/ILSpy/ILSpySettingsFilePathProvider.cs b/ILSpy/ILSpySettingsFilePathProvider.cs new file mode 100644 index 0000000000..386b9b2baa --- /dev/null +++ b/ILSpy/ILSpySettingsFilePathProvider.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; + +using ICSharpCode.ILSpyX.Settings; + +namespace ICSharpCode.ILSpy +{ + internal class ILSpySettingsFilePathProvider : ISettingsFilePathProvider + { + public string GetSettingsFilePath() + { + if (App.CommandLineArguments.ConfigFile != null) + return App.CommandLineArguments.ConfigFile; + string localPath = Path.Combine(Path.GetDirectoryName(typeof(MainWindow).Assembly.Location), "ILSpy.xml"); + if (File.Exists(localPath)) + return localPath; + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ICSharpCode\\ILSpy.xml"); + } + } +} diff --git a/ILSpy/Images/DarkMode.png b/ILSpy/Images/DarkMode.png deleted file mode 100644 index 843dc5363c..0000000000 Binary files a/ILSpy/Images/DarkMode.png and /dev/null differ diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs index e4ce9eba69..fe229607a4 100644 --- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs +++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs @@ -23,6 +23,7 @@ using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpyX.Extensions; @@ -55,9 +56,15 @@ class CSharpHighlightingTokenWriter : DecoratingTokenWriter HighlightingColor methodCallColor; HighlightingColor methodDeclarationColor; - HighlightingColor fieldDeclarationColor; HighlightingColor fieldAccessColor; + HighlightingColor propertyDeclarationColor; + HighlightingColor propertyAccessColor; + HighlightingColor eventDeclarationColor; + HighlightingColor eventAccessColor; + + HighlightingColor variableColor; + HighlightingColor parameterColor; HighlightingColor valueKeywordColor; HighlightingColor thisKeywordColor; @@ -95,12 +102,16 @@ public CSharpHighlightingTokenWriter(TokenWriter decoratedWriter, ISmartTextOutp this.enumerationTypeColor = highlighting.GetNamedColor("EnumTypes"); this.typeParameterTypeColor = highlighting.GetNamedColor("TypeParameters"); this.delegateTypeColor = highlighting.GetNamedColor("DelegateTypes"); - this.methodDeclarationColor = this.methodCallColor = highlighting.GetNamedColor("MethodCall"); - //this.eventDeclarationColor = this.eventAccessColor = defaultTextColor; - //this.propertyDeclarationColor = this.propertyAccessColor = defaultTextColor; - this.fieldDeclarationColor = this.fieldAccessColor = highlighting.GetNamedColor("FieldAccess"); - //this.variableDeclarationColor = this.variableAccessColor = defaultTextColor; - //this.parameterDeclarationColor = this.parameterAccessColor = defaultTextColor; + this.methodDeclarationColor = highlighting.GetNamedColor("MethodDeclaration"); + this.methodCallColor = highlighting.GetNamedColor("MethodCall"); + this.fieldDeclarationColor = highlighting.GetNamedColor("FieldDeclaration"); + this.fieldAccessColor = highlighting.GetNamedColor("FieldAccess"); + this.propertyDeclarationColor = highlighting.GetNamedColor("PropertyDeclaration"); + this.propertyAccessColor = highlighting.GetNamedColor("PropertyAccess"); + this.eventDeclarationColor = highlighting.GetNamedColor("EventDeclaration"); + this.eventAccessColor = highlighting.GetNamedColor("EventAccess"); + this.variableColor = highlighting.GetNamedColor("Variable"); + this.parameterColor = highlighting.GetNamedColor("Parameter"); this.valueKeywordColor = highlighting.GetNamedColor("NullOrValueKeywords"); this.thisKeywordColor = highlighting.GetNamedColor("ThisOrBaseReference"); this.trueKeywordColor = highlighting.GetNamedColor("TrueFalse"); @@ -338,13 +349,25 @@ public override void WritePrimitiveType(string type) public override void WriteIdentifier(Identifier identifier) { HighlightingColor color = null; - if (identifier.Name == "value" - && identifier.Parent?.GetResolveResult() is ILVariableResolveResult rr - && rr.Variable.Kind == Decompiler.IL.VariableKind.Parameter - && identifier.Ancestors.OfType().FirstOrDefault() is Accessor accessor - && accessor.Role != PropertyDeclaration.GetterRole) + if (identifier.Parent?.GetResolveResult() is ILVariableResolveResult rr) { - color = valueKeywordColor; + if (rr.Variable.Kind == VariableKind.Parameter) + { + if (identifier.Name == "value" + && identifier.Ancestors.OfType().FirstOrDefault() is { } accessor + && accessor.Role != PropertyDeclaration.GetterRole) + { + color = valueKeywordColor; + } + else + { + color = parameterColor; + } + } + else + { + color = variableColor; + } } if (identifier.Parent is AstType) { @@ -361,60 +384,40 @@ public override void WriteIdentifier(Identifier identifier) switch (GetCurrentDefinition()) { case ITypeDefinition t: - switch (t.Kind) - { - case TypeKind.Delegate: - color = delegateTypeColor; - break; - case TypeKind.Class: - color = referenceTypeColor; - break; - case TypeKind.Interface: - color = interfaceTypeColor; - break; - case TypeKind.Enum: - color = enumerationTypeColor; - break; - case TypeKind.Struct: - color = valueTypeColor; - break; - } + ApplyTypeColor(t, ref color); break; - case IMethod m: + case IMethod: color = methodDeclarationColor; break; - case IField f: + case IField: color = fieldDeclarationColor; break; + case IProperty: + color = propertyDeclarationColor; + break; + case IEvent: + color = eventDeclarationColor; + break; } switch (GetCurrentMemberReference()) { case IType t: - switch (t.Kind) - { - case TypeKind.Delegate: - color = delegateTypeColor; - break; - case TypeKind.Class: - color = referenceTypeColor; - break; - case TypeKind.Interface: - color = interfaceTypeColor; - break; - case TypeKind.Enum: - color = enumerationTypeColor; - break; - case TypeKind.Struct: - color = valueTypeColor; - break; - } + ApplyTypeColor(t, ref color); break; case IMethod m: color = methodCallColor; + if (m.IsConstructor) + ApplyTypeColor(m.DeclaringType, ref color); break; - case IField f: + case IField: color = fieldAccessColor; break; + case IProperty: + color = propertyAccessColor; + break; + case IEvent: + color = eventAccessColor; + break; } if (color != null) { @@ -427,6 +430,28 @@ public override void WriteIdentifier(Identifier identifier) } } + void ApplyTypeColor(IType type, ref HighlightingColor color) + { + switch (type?.Kind) + { + case TypeKind.Delegate: + color = delegateTypeColor; + break; + case TypeKind.Class: + color = referenceTypeColor; + break; + case TypeKind.Interface: + color = interfaceTypeColor; + break; + case TypeKind.Enum: + color = enumerationTypeColor; + break; + case TypeKind.Struct: + color = valueTypeColor; + break; + } + } + public override void WritePrimitiveValue(object value, Decompiler.CSharp.Syntax.LiteralFormat format) { HighlightingColor color = null; diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index a1d9428829..4486366a9a 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -156,6 +156,7 @@ public override void DecompileMethod(IMethod method, ITextOutput output, Decompi CSharpDecompiler decompiler = CreateDecompiler(assembly, options); AddReferenceAssemblyWarningMessage(assembly, output); AddReferenceWarningMessage(assembly, output); + WriteCommentLine(output, assembly.FullName); WriteCommentLine(output, TypeToString(method.DeclaringType, includeNamespace: true)); var methodDefinition = decompiler.TypeSystem.MainModule.ResolveEntity(method.MetadataToken) as IMethod; if (methodDefinition.IsConstructor && methodDefinition.DeclaringType.IsReferenceType != false) @@ -237,6 +238,7 @@ public override void DecompileProperty(IProperty property, ITextOutput output, D CSharpDecompiler decompiler = CreateDecompiler(assembly, options); AddReferenceAssemblyWarningMessage(assembly, output); AddReferenceWarningMessage(assembly, output); + WriteCommentLine(output, assembly.FullName); WriteCommentLine(output, TypeToString(property.DeclaringType, includeNamespace: true)); WriteCode(output, options.DecompilerSettings, decompiler.Decompile(property.MetadataToken), decompiler.TypeSystem); } @@ -247,6 +249,7 @@ public override void DecompileField(IField field, ITextOutput output, Decompilat CSharpDecompiler decompiler = CreateDecompiler(assembly, options); AddReferenceAssemblyWarningMessage(assembly, output); AddReferenceWarningMessage(assembly, output); + WriteCommentLine(output, assembly.FullName); WriteCommentLine(output, TypeToString(field.DeclaringType, includeNamespace: true)); if (field.IsConst) { @@ -315,6 +318,7 @@ public override void DecompileEvent(IEvent @event, ITextOutput output, Decompila CSharpDecompiler decompiler = CreateDecompiler(assembly, options); AddReferenceAssemblyWarningMessage(assembly, output); AddReferenceWarningMessage(assembly, output); + WriteCommentLine(output, assembly.FullName); WriteCommentLine(output, TypeToString(@event.DeclaringType, includeNamespace: true)); WriteCode(output, options.DecompilerSettings, decompiler.Decompile(@event.MetadataToken), decompiler.TypeSystem); } @@ -325,6 +329,7 @@ public override void DecompileType(ITypeDefinition type, ITextOutput output, Dec CSharpDecompiler decompiler = CreateDecompiler(assembly, options); AddReferenceAssemblyWarningMessage(assembly, output); AddReferenceWarningMessage(assembly, output); + WriteCommentLine(output, assembly.FullName); WriteCommentLine(output, TypeToString(type, includeNamespace: true)); WriteCode(output, options.DecompilerSettings, decompiler.Decompile(type.MetadataToken), decompiler.TypeSystem); } @@ -404,6 +409,7 @@ public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput options.DecompilerSettings.UseSdkStyleProjectFormat = false; } var decompiler = new ILSpyWholeProjectDecompiler(assembly, options); + decompiler.ProgressIndicator = options.Progress; return decompiler.DecompileProject(module, options.SaveAsProjectDirectory, new TextOutputWriter(output), options.CancellationToken); } else @@ -511,7 +517,7 @@ public ILSpyWholeProjectDecompiler(LoadedAssembly assembly, DecompilationOptions this.options = options; } - protected override IEnumerable<(string itemType, string fileName, List partialTypes)> WriteResourceToFile(string fileName, string resourceName, Stream entryStream) + protected override IEnumerable WriteResourceToFile(string fileName, string resourceName, Stream entryStream) { var context = new ResourceFileHandlerContext(options); foreach (var handler in App.ExportProvider.GetExportedValues()) @@ -521,7 +527,7 @@ public ILSpyWholeProjectDecompiler(LoadedAssembly assembly, DecompilationOptions entryStream.Position = 0; fileName = handler.WriteResourceToFile(assembly, fileName, entryStream, context); - return new[] { (handler.EntryType, fileName, context.PartialTypes) }; + return new[] { new ProjectItemInfo(handler.EntryType, fileName) { PartialTypes = context.PartialTypes }.With(context.AdditionalProperties) }; } } return base.WriteResourceToFile(fileName, resourceName, entryStream); @@ -743,6 +749,10 @@ public override RichText GetRichTextTooltip(IEntity entity) { flags |= ConversionFlags.SupportRecordStructs; } + if (settings.UnsignedRightShift) + { + flags |= ConversionFlags.SupportUnsignedRightShift; + } if (settings.InitAccessors) { flags |= ConversionFlags.SupportInitAccessors; diff --git a/ILSpy/Languages/IResourceFileHandler.cs b/ILSpy/Languages/IResourceFileHandler.cs index 8fe336f06b..19b977ac5f 100644 --- a/ILSpy/Languages/IResourceFileHandler.cs +++ b/ILSpy/Languages/IResourceFileHandler.cs @@ -36,6 +36,9 @@ public class ResourceFileHandlerContext readonly List partialTypes = new(); internal List PartialTypes => partialTypes; + readonly Dictionary additionalProperties = new(); + public Dictionary AdditionalProperties => additionalProperties; + public DecompilationOptions DecompilationOptions { get; } public ResourceFileHandlerContext(DecompilationOptions options) diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index bf9860abe0..0d236371fb 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -20,6 +20,7 @@ xmlns:styles="urn:TomsToolbox.Wpf.Styles" xmlns:b="http://schemas.microsoft.com/xaml/behaviors" xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" + xmlns:toms="urn:TomsToolbox" d:DataContext="{d:DesignInstance local:MainWindowViewModel}" > @@ -92,8 +93,6 @@ - - @@ -140,6 +139,23 @@ + + + + + @@ -164,17 +180,13 @@ @@ -208,10 +220,6 @@ IsEnabled="{Binding Workspace.ActiveTabPage.SupportsLanguageSwitching}" ItemsSource="{Binding SelectedItem.LanguageVersions, ElementName=languageComboBox, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Workspace.ActiveTabPage.FilterSettings.LanguageVersion, UpdateSourceTrigger=PropertyChanged}"/> - - - - diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index cf64691d78..c31d35affe 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -50,8 +50,10 @@ using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; +using ICSharpCode.ILSpyX.Settings; using ICSharpCode.TreeView; using Microsoft.Win32; @@ -103,9 +105,13 @@ public SearchPane SearchPane { public DecompilerSettings CurrentDecompilerSettings { get; internal set; } - public DisplaySettings CurrentDisplaySettings { get; internal set; } + public DisplaySettingsViewModel CurrentDisplaySettings { get; internal set; } - public DecompilationOptions CreateDecompilationOptions() => new DecompilationOptions(CurrentLanguageVersion, CurrentDecompilerSettings, CurrentDisplaySettings); + public DecompilationOptions CreateDecompilationOptions() + { + var decompilerView = DockWorkspace.Instance.ActiveTabPage.Content as IProgress; + return new DecompilationOptions(CurrentLanguageVersion, CurrentDecompilerSettings, CurrentDisplaySettings) { Progress = decompilerView }; + } public MainWindow() { @@ -179,7 +185,7 @@ private void SessionSettings_PropertyChanged(object sender, PropertyChangedEvent case nameof(SessionSettings.ActiveAssemblyList): ShowAssemblyList(sessionSettings.ActiveAssemblyList); break; - case nameof(SessionSettings.IsDarkMode): + case nameof(SessionSettings.Theme): // update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change) DecompilerTextView.RegisterHighlighting(); DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); @@ -576,7 +582,7 @@ protected override void OnSourceInitialized(EventArgs e) { hwndSource.AddHook(WndProc); } - App.ReleaseSingleInstanceMutex(); + SingleInstanceHandling.ReleaseSingleInstanceMutex(); // Validate and Set Window Bounds Rect bounds = Rect.Transform(sessionSettings.WindowBounds, source.CompositionTarget.TransformToDevice); var boundsRect = new System.Drawing.Rectangle((int)bounds.Left, (int)bounds.Top, (int)bounds.Width, (int)bounds.Height); @@ -949,13 +955,14 @@ public async Task ShowMessageIfUpdatesAvailableAsync(ILSpySettings spySettings, string downloadUrl; if (forceCheck) { - downloadUrl = await AboutPage.CheckForUpdatesAsync(spySettings); + downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(spySettings); } else { - downloadUrl = await AboutPage.CheckForUpdatesIfEnabledAsync(spySettings); + downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesIfEnabledAsync(spySettings); } + // The Update Panel is only available for NotifyOfUpdatesStrategy, AutoUpdate will have differing UI requirements AdjustUpdateUIAfterCheck(downloadUrl, forceCheck); } @@ -973,7 +980,7 @@ async void downloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e) else { updatePanel.Visibility = Visibility.Collapsed; - string downloadUrl = await AboutPage.CheckForUpdatesAsync(ILSpySettings.Load()); + string downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(ILSpySettings.Load()); AdjustUpdateUIAfterCheck(downloadUrl, true); } } @@ -1084,8 +1091,10 @@ public void RefreshTreeViewFilter() // filterSettings is mutable; but the ILSpyTreeNode filtering assumes that filter settings are immutable. // Thus, the main window will use one mutable instance (for data-binding), and assign a new clone to the ILSpyTreeNodes whenever the main // mutable instance changes. + FilterSettings filterSettings = DockWorkspace.Instance.ActiveTabPage.FilterSettings.Clone(); if (assemblyListTreeNode != null) - assemblyListTreeNode.FilterSettings = DockWorkspace.Instance.ActiveTabPage.FilterSettings.Clone(); + assemblyListTreeNode.FilterSettings = filterSettings; + SearchPane.UpdateFilter(filterSettings); } internal AssemblyListTreeNode AssemblyListTreeNode { diff --git a/ILSpy/Metadata/CoffHeaderTreeNode.cs b/ILSpy/Metadata/CoffHeaderTreeNode.cs index 8e7c3fe574..d7c25fa858 100644 --- a/ILSpy/Metadata/CoffHeaderTreeNode.cs +++ b/ILSpy/Metadata/CoffHeaderTreeNode.cs @@ -67,10 +67,12 @@ public override bool View(TabPageModel tabPage) var headers = module.Reader.PEHeaders; var header = headers.CoffHeader; + var linkerDateTime = DateTimeOffset.FromUnixTimeSeconds(unchecked((uint)header.TimeDateStamp)).DateTime; + var entries = new List(); entries.Add(new Entry(headers.CoffHeaderStartOffset, (int)header.Machine, 2, "Machine", header.Machine.ToString())); entries.Add(new Entry(headers.CoffHeaderStartOffset + 2, (int)header.NumberOfSections, 2, "Number of Sections", "Number of sections; indicates size of the Section Table, which immediately follows the headers.")); - entries.Add(new Entry(headers.CoffHeaderStartOffset + 4, header.TimeDateStamp, 4, "Time/Date Stamp", DateTimeOffset.FromUnixTimeSeconds(unchecked((uint)header.TimeDateStamp)).DateTime + " - Time and date the file was created in seconds since January 1st 1970 00:00:00 or 0.")); + entries.Add(new Entry(headers.CoffHeaderStartOffset + 4, header.TimeDateStamp, 4, "Time/Date Stamp", $"{linkerDateTime} (UTC) / {linkerDateTime.ToLocalTime()} - Time and date the file was created in seconds since January 1st 1970 00:00:00 or 0. Note that for deterministic builds this value is meaningless.")); entries.Add(new Entry(headers.CoffHeaderStartOffset + 8, header.PointerToSymbolTable, 4, "Pointer to Symbol Table", "Always 0 in .NET executables.")); entries.Add(new Entry(headers.CoffHeaderStartOffset + 12, header.NumberOfSymbols, 4, "Number of Symbols", "Always 0 in .NET executables.")); entries.Add(new Entry(headers.CoffHeaderStartOffset + 16, (int)header.SizeOfOptionalHeader, 2, "Optional Header Size", "Size of the optional header.")); diff --git a/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs index 4be8cd37a0..4ad56815d7 100644 --- a/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs @@ -87,14 +87,14 @@ struct AssemblyRefEntry public Version Version => assemblyRef.Version; - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public AssemblyFlags Flags => assemblyRef.Flags; public object FlagsTooltip => new FlagsTooltip((int)assemblyRef.Flags, null) { FlagGroup.CreateMultipleChoiceGroup(typeof(AssemblyFlags), selectedValue: (int)assemblyRef.Flags, includeAll: false) }; - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int PublicKeyOrToken => MetadataTokens.GetHeapOffset(assemblyRef.PublicKeyOrToken); public string PublicKeyOrTokenTooltip { diff --git a/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs b/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs index 4c16f67137..5d8409657d 100644 --- a/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs @@ -72,14 +72,14 @@ struct AssemblyEntry + metadata.GetTableMetadataOffset(TableIndex.Assembly) + metadata.GetTableRowSize(TableIndex.Assembly) * (RID - 1); - [StringFormat("X4")] + [ColumnInfo("X4", Kind = ColumnKind.Other)] public AssemblyHashAlgorithm HashAlgorithm => assembly.HashAlgorithm; public object HashAlgorithmTooltip => new FlagsTooltip() { FlagGroup.CreateSingleChoiceGroup(typeof(AssemblyHashAlgorithm), selectedValue: (int)assembly.HashAlgorithm, defaultFlag: new Flag("None (0000)", 0, false), includeAny: false) }; - [StringFormat("X4")] + [ColumnInfo("X4", Kind = ColumnKind.Other)] public AssemblyFlags Flags => assembly.Flags; public object FlagsTooltip => new FlagsTooltip() { diff --git a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs index 97be915963..af78f30442 100644 --- a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs @@ -100,8 +100,7 @@ unsafe struct ClassLayoutEntry public int Offset { get; } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(classLayout.Parent); public void OnParentClick() @@ -109,19 +108,13 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, classLayout.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)classLayout.Parent).WriteTo(module, output, context); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, classLayout.Parent); - [StringFormat("X4")] + [ColumnInfo("X4", Kind = ColumnKind.Other)] public ushort PackingSize => classLayout.PackingSize; - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public uint ClassSize => classLayout.ClassSize; public ClassLayoutEntry(PEFile module, byte* ptr, int metadataOffset, int row) @@ -133,6 +126,7 @@ public ClassLayoutEntry(PEFile module, byte* ptr, int metadataOffset, int row) + metadata.GetTableRowSize(TableIndex.ClassLayout) * (row - 1); this.Offset = metadataOffset + rowOffset; this.classLayout = new ClassLayout(ptr + rowOffset, metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4); + this.parentTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs index 0493abd8ff..3f9dc97c8a 100644 --- a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs @@ -87,13 +87,12 @@ struct ConstantEntry + metadata.GetTableMetadataOffset(TableIndex.Constant) + metadata.GetTableRowSize(TableIndex.Constant) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public ConstantTypeCode Type => constant.TypeCode; public string TypeTooltip => constant.TypeCode.ToString(); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(constant.Parent); public void OnParentClick() @@ -101,16 +100,10 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, constant.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - constant.Parent.WriteTo(module, output, context); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, constant.Parent); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Value => MetadataTokens.GetHeapOffset(constant.Value); public string ValueTooltip { @@ -126,6 +119,7 @@ public ConstantEntry(PEFile module, ConstantHandle handle) this.metadata = module.Metadata; this.handle = handle; this.constant = metadata.GetConstant(handle); + this.parentTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs index 4070ad9bec..30a6cf47f0 100644 --- a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs @@ -87,8 +87,7 @@ struct CustomAttributeEntry + metadata.GetTableMetadataOffset(TableIndex.CustomAttribute) + metadata.GetTableRowSize(TableIndex.CustomAttribute) * (RID - 1); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(customAttr.Parent); public void OnParentClick() @@ -96,17 +95,10 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, customAttr.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - customAttr.Parent.WriteTo(module, output, context); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, customAttr.Parent); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Constructor => MetadataTokens.GetToken(customAttr.Constructor); public void OnConstructorClick() @@ -114,16 +106,10 @@ public void OnConstructorClick() MainWindow.Instance.JumpToReference(new EntityReference(module, customAttr.Constructor, protocol: "metadata")); } - public string ConstructorTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - customAttr.Constructor.WriteTo(module, output, context); - return output.ToString(); - } - } + string constructorTooltip; + public string ConstructorTooltip => GenerateTooltip(ref constructorTooltip, module, customAttr.Constructor); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Value => MetadataTokens.GetHeapOffset(customAttr.Value); public string ValueTooltip { @@ -139,6 +125,8 @@ public CustomAttributeEntry(PEFile module, CustomAttributeHandle handle) this.metadata = module.Metadata; this.handle = handle; this.customAttr = metadata.GetCustomAttribute(handle); + this.parentTooltip = null; + this.constructorTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs index bcfb719ad4..601b7a8b13 100644 --- a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs @@ -88,8 +88,7 @@ struct DeclSecurityEntry + metadata.GetTableMetadataOffset(TableIndex.DeclSecurity) + metadata.GetTableRowSize(TableIndex.DeclSecurity) * (RID - 1); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(declSecAttr.Parent); public void OnParentClick() @@ -97,16 +96,10 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, declSecAttr.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - declSecAttr.Parent.WriteTo(module, output, context); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, declSecAttr.Parent); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public DeclarativeSecurityAction Action => declSecAttr.Action; public string ActionTooltip { @@ -115,7 +108,7 @@ public string ActionTooltip { } } - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int PermissionSet => MetadataTokens.GetHeapOffset(declSecAttr.PermissionSet); public string PermissionSetTooltip { @@ -131,6 +124,7 @@ public DeclSecurityEntry(PEFile module, DeclarativeSecurityAttributeHandle handl this.metadata = module.Metadata; this.handle = handle; this.declSecAttr = metadata.GetDeclarativeSecurityAttribute(handle); + this.parentTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs index d25738f72c..ae08337084 100644 --- a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs @@ -98,8 +98,7 @@ unsafe struct EventMapEntry public int Offset { get; } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(eventMap.Parent); public void OnParentClick() @@ -107,17 +106,10 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, eventMap.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)eventMap.Parent).WriteTo(module, output, context); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, eventMap.Parent); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int EventList => MetadataTokens.GetToken(eventMap.EventList); public void OnEventListClick() @@ -125,14 +117,8 @@ public void OnEventListClick() MainWindow.Instance.JumpToReference(new EntityReference(module, eventMap.EventList, protocol: "metadata")); } - public string EventListTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)eventMap.EventList).WriteTo(module, output, context); - return output.ToString(); - } - } + string eventListTooltip; + public string EventListTooltip => GenerateTooltip(ref eventListTooltip, module, eventMap.EventList); public EventMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) { @@ -145,6 +131,8 @@ public EventMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; int eventDefSize = metadata.GetTableRowCount(TableIndex.Event) < ushort.MaxValue ? 2 : 4; this.eventMap = new EventMap(ptr + rowOffset, typeDefSize, eventDefSize); + this.parentTooltip = null; + this.eventListTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs index 4ed3b9a84c..b24f093dc6 100644 --- a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs @@ -90,7 +90,7 @@ struct EventDefEntry : IMemberTreeNode + metadata.GetTableMetadataOffset(TableIndex.Event) + metadata.GetTableRowSize(TableIndex.Event) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public EventAttributes Attributes => eventDef.Attributes; public object AttributesTooltip => new FlagsTooltip { @@ -103,8 +103,7 @@ struct EventDefEntry : IMemberTreeNode IEntity IMemberTreeNode.Member => ((MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull()?.MainModule).GetDefinition(handle); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Type => MetadataTokens.GetToken(eventDef.Type); public void OnTypeClick() @@ -112,13 +111,8 @@ public void OnTypeClick() MainWindow.Instance.JumpToReference(new EntityReference(module, eventDef.Type, protocol: "metadata")); } - public string TypeTooltip { - get { - ITextOutput output = new PlainTextOutput(); - eventDef.Type.WriteTo(module, output, default); - return output.ToString(); - } - } + string typeTooltip; + public string TypeTooltip => GenerateTooltip(ref typeTooltip, module, eventDef.Type); public EventDefEntry(PEFile module, EventDefinitionHandle handle) { @@ -127,6 +121,7 @@ public EventDefEntry(PEFile module, EventDefinitionHandle handle) this.metadata = module.Metadata; this.handle = handle; this.eventDef = metadata.GetEventDefinition(handle); + this.typeTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs index fee2aff10c..b95a5a9e8e 100644 --- a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs @@ -86,7 +86,7 @@ struct ExportedTypeEntry + metadata.GetTableMetadataOffset(TableIndex.ExportedType) + metadata.GetTableRowSize(TableIndex.ExportedType) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public TypeAttributes Attributes => type.Attributes; const TypeAttributes otherFlagsMask = ~(TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.ClassSemanticsMask | TypeAttributes.StringFormatMask | TypeAttributes.CustomFormatMask); @@ -110,8 +110,7 @@ struct ExportedTypeEntry public string TypeNamespace => metadata.GetString(type.Namespace); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Implementation => MetadataTokens.GetToken(type.Implementation); public void OnImplementationClick() @@ -119,16 +118,8 @@ public void OnImplementationClick() MainWindow.Instance.JumpToReference(new EntityReference(module, type.Implementation, protocol: "metadata")); } - public string ImplementationTooltip { - get { - if (type.Implementation.IsNil) - return null; - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - type.Implementation.WriteTo(module, output, context); - return output.ToString(); - } - } + string implementationTooltip; + public string ImplementationTooltip => GenerateTooltip(ref implementationTooltip, module, type.Implementation); public ExportedTypeEntry(int metadataOffset, PEFile module, ExportedTypeHandle handle, ExportedType type) { @@ -137,6 +128,7 @@ public ExportedTypeEntry(int metadataOffset, PEFile module, ExportedTypeHandle h this.metadata = module.Metadata; this.handle = handle; this.type = type; + this.implementationTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs index d959a49b64..8a80966b0c 100644 --- a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs @@ -98,8 +98,7 @@ unsafe struct FieldLayoutEntry public int Offset { get; } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Field => MetadataTokens.GetToken(fieldLayout.Field); public void OnFieldClick() @@ -107,16 +106,10 @@ public void OnFieldClick() MainWindow.Instance.JumpToReference(new EntityReference(module, fieldLayout.Field, protocol: "metadata")); } - public string FieldTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)fieldLayout.Field).WriteTo(module, output, context); - return output.ToString(); - } - } + string fieldTooltip; + public string FieldTooltip => GenerateTooltip(ref fieldTooltip, module, fieldLayout.Field); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public int FieldOffset => fieldLayout.Offset; public FieldLayoutEntry(PEFile module, byte* ptr, int metadataOffset, int row) @@ -129,6 +122,7 @@ public FieldLayoutEntry(PEFile module, byte* ptr, int metadataOffset, int row) this.Offset = metadataOffset + rowOffset; int fieldDefSize = metadata.GetTableRowCount(TableIndex.Field) < ushort.MaxValue ? 2 : 4; this.fieldLayout = new FieldLayout(ptr + rowOffset, fieldDefSize); + this.fieldTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs index a4d74b9bc3..a9f26ff130 100644 --- a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs @@ -98,8 +98,7 @@ unsafe struct FieldMarshalEntry public int Offset { get; } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(fieldMarshal.Parent); public void OnParentClick() @@ -107,16 +106,10 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, fieldMarshal.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)fieldMarshal.Parent).WriteTo(module, output, context); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, fieldMarshal.Parent); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int NativeType => MetadataTokens.GetHeapOffset(fieldMarshal.NativeType); public FieldMarshalEntry(PEFile module, byte* ptr, int metadataOffset, int row) @@ -130,6 +123,7 @@ public FieldMarshalEntry(PEFile module, byte* ptr, int metadataOffset, int row) int hasFieldMarshalRefSize = metadata.ComputeCodedTokenSize(32768, TableMask.Field | TableMask.Param); int blobHeapSize = metadata.GetHeapSize(HeapIndex.Blob) < ushort.MaxValue ? 2 : 4; this.fieldMarshal = new FieldMarshal(ptr + rowOffset, blobHeapSize, hasFieldMarshalRefSize); + this.parentTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs index c0d54a0233..9317bb3367 100644 --- a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs @@ -90,7 +90,7 @@ unsafe struct FieldRVAEntry { readonly PEFile module; readonly MetadataReader metadata; - readonly FieldRVA fieldLayout; + readonly FieldRVA fieldRVA; public int RID { get; } @@ -98,26 +98,19 @@ unsafe struct FieldRVAEntry public int Offset { get; } - [StringFormat("X8")] - [LinkToTable] - public int Field => MetadataTokens.GetToken(fieldLayout.Field); + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Field => MetadataTokens.GetToken(fieldRVA.Field); public void OnFieldClick() { - MainWindow.Instance.JumpToReference(new EntityReference(module, fieldLayout.Field, protocol: "metadata")); + MainWindow.Instance.JumpToReference(new EntityReference(module, fieldRVA.Field, protocol: "metadata")); } - public string FieldTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)fieldLayout.Field).WriteTo(module, output, context); - return output.ToString(); - } - } + string fieldTooltip; + public string FieldTooltip => GenerateTooltip(ref fieldTooltip, module, fieldRVA.Field); - [StringFormat("X")] - public int FieldOffset => fieldLayout.Offset; + [ColumnInfo("X8", Kind = ColumnKind.Other)] + public int FieldOffset => fieldRVA.Offset; public FieldRVAEntry(PEFile module, byte* ptr, int metadataOffset, int row) { @@ -128,7 +121,8 @@ public FieldRVAEntry(PEFile module, byte* ptr, int metadataOffset, int row) + metadata.GetTableRowSize(TableIndex.FieldRva) * (row - 1); this.Offset = metadataOffset + rowOffset; int fieldDefSize = metadata.GetTableRowCount(TableIndex.Field) < ushort.MaxValue ? 2 : 4; - this.fieldLayout = new FieldRVA(ptr + rowOffset, fieldDefSize); + this.fieldRVA = new FieldRVA(ptr + rowOffset, fieldDefSize); + this.fieldTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs index bdc927b28c..f036661682 100644 --- a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs @@ -94,7 +94,7 @@ struct FieldDefEntry : IMemberTreeNode + metadata.GetTableMetadataOffset(TableIndex.Field) + metadata.GetTableRowSize(TableIndex.Field) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public FieldAttributes Attributes => fieldDef.Attributes; const FieldAttributes otherFlagsMask = ~(FieldAttributes.FieldAccessMask); @@ -110,17 +110,11 @@ struct FieldDefEntry : IMemberTreeNode IEntity IMemberTreeNode.Member => ((MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull()?.MainModule).GetDefinition(handle); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(fieldDef.Signature); - public string SignatureTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)handle).WriteTo(module, output, context); - return output.ToString(); - } - } + string signatureTooltip; + public string SignatureTooltip => GenerateTooltip(ref signatureTooltip, module, handle); public FieldDefEntry(PEFile module, FieldDefinitionHandle handle) { @@ -129,6 +123,7 @@ public FieldDefEntry(PEFile module, FieldDefinitionHandle handle) this.metadata = module.Metadata; this.handle = handle; this.fieldDef = metadata.GetFieldDefinition(handle); + this.signatureTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/FileTableTreeNode.cs b/ILSpy/Metadata/CorTables/FileTableTreeNode.cs index c48b8890bd..ddf0e51895 100644 --- a/ILSpy/Metadata/CorTables/FileTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FileTableTreeNode.cs @@ -85,7 +85,7 @@ struct FileEntry + metadata.GetTableMetadataOffset(TableIndex.File) + metadata.GetTableRowSize(TableIndex.File) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public int Attributes => assemblyFile.ContainsMetadata ? 1 : 0; public string AttributesTooltip => assemblyFile.ContainsMetadata ? "ContainsMetaData" : "ContainsNoMetaData"; @@ -94,7 +94,7 @@ struct FileEntry public string NameTooltip => $"{MetadataTokens.GetHeapOffset(assemblyFile.Name):X} \"{Name}\""; - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int HashValue => MetadataTokens.GetHeapOffset(assemblyFile.HashValue); public string HashValueTooltip { diff --git a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs index 760cb2df15..1f97fdce6f 100644 --- a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs @@ -86,8 +86,7 @@ struct GenericParamConstraintEntry + metadata.GetTableMetadataOffset(TableIndex.GenericParamConstraint) + metadata.GetTableRowSize(TableIndex.GenericParamConstraint) * (RID - 1); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Owner => MetadataTokens.GetToken(genericParamConstraint.Parameter); public void OnOwnerClick() @@ -111,8 +110,7 @@ public string OwnerTooltip { } } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Type => MetadataTokens.GetToken(genericParamConstraint.Type); public void OnTypeClick() @@ -120,13 +118,8 @@ public void OnTypeClick() MainWindow.Instance.JumpToReference(new EntityReference(module, genericParamConstraint.Type, protocol: "metadata")); } - public string TypeTooltip { - get { - ITextOutput output = new PlainTextOutput(); - genericParamConstraint.Type.WriteTo(module, output, default); - return output.ToString(); - } - } + string typeTooltip; + public string TypeTooltip => GenerateTooltip(ref typeTooltip, module, genericParamConstraint.Type); public GenericParamConstraintEntry(PEFile module, GenericParameterConstraintHandle handle) { @@ -136,6 +129,7 @@ public GenericParamConstraintEntry(PEFile module, GenericParameterConstraintHand this.handle = handle; this.genericParamConstraint = metadata.GetGenericParameterConstraint(handle); this.ownerTooltip = null; + this.typeTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs index 5bb682d2b5..789670a59f 100644 --- a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs @@ -88,7 +88,7 @@ struct GenericParamEntry public int Number => genericParam.Index; - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public GenericParameterAttributes Attributes => genericParam.Attributes; public object AttributesTooltip => new FlagsTooltip { @@ -96,22 +96,16 @@ struct GenericParamEntry FlagGroup.CreateSingleChoiceGroup(typeof(GenericParameterAttributes), "Managed type: ", (int)GenericParameterAttributes.SpecialConstraintMask, (int)(genericParam.Attributes & GenericParameterAttributes.SpecialConstraintMask), new Flag("None (0000)", 0, false), includeAny: false), }; - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Owner => MetadataTokens.GetToken(genericParam.Parent); - public void OnParentClick() + public void OnOwnerClick() { MainWindow.Instance.JumpToReference(new EntityReference(module, genericParam.Parent, protocol: "metadata")); } - public string OwnerTooltip { - get { - ITextOutput output = new PlainTextOutput(); - genericParam.Parent.WriteTo(module, output, default); - return output.ToString(); - } - } + string ownerTooltip; + public string OwnerTooltip => GenerateTooltip(ref ownerTooltip, module, genericParam.Parent); public string Name => metadata.GetString(genericParam.Name); @@ -124,6 +118,7 @@ public GenericParamEntry(PEFile module, GenericParameterHandle handle) this.metadata = module.Metadata; this.handle = handle; this.genericParam = metadata.GetGenericParameter(handle); + this.ownerTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs index ccfd7801b0..c6c228ba69 100644 --- a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs @@ -104,7 +104,7 @@ unsafe struct ImplMapEntry public int Offset { get; } - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public PInvokeAttributes MappingFlags => implMap.MappingFlags; const PInvokeAttributes otherFlagsMask = ~(PInvokeAttributes.CallConvMask | PInvokeAttributes.CharSetMask); @@ -115,8 +115,7 @@ unsafe struct ImplMapEntry FlagGroup.CreateMultipleChoiceGroup(typeof(PInvokeAttributes), "Flags:", (int)otherFlagsMask, (int)(implMap.MappingFlags & otherFlagsMask), includeAll: false), }; - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int MemberForwarded => MetadataTokens.GetToken(implMap.MemberForwarded); public void OnMemberForwardedClick() @@ -124,17 +123,10 @@ public void OnMemberForwardedClick() MainWindow.Instance.JumpToReference(new EntityReference(module, implMap.MemberForwarded, protocol: "metadata")); } - public string MemberForwardedTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)implMap.MemberForwarded).WriteTo(module, output, context); - return output.ToString(); - } - } + string memberForwardedTooltip; + public string MemberForwardedTooltip => GenerateTooltip(ref memberForwardedTooltip, module, implMap.MemberForwarded); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int ImportScope => MetadataTokens.GetToken(implMap.ImportScope); public void OnImportScopeClick() @@ -142,14 +134,8 @@ public void OnImportScopeClick() MainWindow.Instance.JumpToReference(new EntityReference(module, implMap.ImportScope, protocol: "metadata")); } - public string ImportScopeTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)implMap.ImportScope).WriteTo(module, output, context); - return output.ToString(); - } - } + string importScopeTooltip; + public string ImportScopeTooltip => GenerateTooltip(ref importScopeTooltip, module, implMap.ImportScope); public string ImportName => metadata.GetString(implMap.ImportName); @@ -167,6 +153,8 @@ public unsafe ImplMapEntry(PEFile module, byte* ptr, int metadataOffset, int row int memberForwardedTagRefSize = metadata.ComputeCodedTokenSize(32768, TableMask.MethodDef | TableMask.Field); int stringHandleSize = metadata.GetHeapSize(HeapIndex.String) < ushort.MaxValue ? 2 : 4; this.implMap = new ImplMap(ptr + rowOffset, moduleRefSize, memberForwardedTagRefSize, stringHandleSize); + this.importScopeTooltip = null; + this.memberForwardedTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs index 6f50900325..35c66ab850 100644 --- a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs @@ -98,8 +98,7 @@ unsafe struct InterfaceImplEntry public int Offset { get; } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Class => MetadataTokens.GetToken(interfaceImpl.Class); public void OnClassClick() @@ -107,17 +106,10 @@ public void OnClassClick() MainWindow.Instance.JumpToReference(new EntityReference(module, interfaceImpl.Class, protocol: "metadata")); } - public string ClassTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)interfaceImpl.Class).WriteTo(module, output, context); - return output.ToString(); - } - } + string classTooltip; + public string ClassTooltip => GenerateTooltip(ref classTooltip, module, interfaceImpl.Class); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Interface => MetadataTokens.GetToken(interfaceImpl.Interface); public void OnInterfaceClick() @@ -125,14 +117,8 @@ public void OnInterfaceClick() MainWindow.Instance.JumpToReference(new EntityReference(module, interfaceImpl.Interface, protocol: "metadata")); } - public string InterfaceTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)interfaceImpl.Interface).WriteTo(module, output, context); - return output.ToString(); - } - } + string interfaceTooltip; + public string InterfaceTooltip => GenerateTooltip(ref interfaceTooltip, module, interfaceImpl.Interface); public InterfaceImplEntry(PEFile module, byte* ptr, int metadataOffset, int row) { @@ -143,6 +129,8 @@ public InterfaceImplEntry(PEFile module, byte* ptr, int metadataOffset, int row) + metadata.GetTableRowSize(TableIndex.InterfaceImpl) * (row - 1); this.Offset = metadataOffset + rowOffset; this.interfaceImpl = new InterfaceImpl(ptr + rowOffset, metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4, metadata.ComputeCodedTokenSize(16384, TableMask.TypeDef | TableMask.TypeRef | TableMask.TypeSpec)); + this.interfaceTooltip = null; + this.classTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs index e720ee1c14..79fd5b76b0 100644 --- a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs @@ -88,7 +88,7 @@ struct ManifestResourceEntry + metadata.GetTableMetadataOffset(TableIndex.ManifestResource) + metadata.GetTableRowSize(TableIndex.ManifestResource) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public ManifestResourceAttributes Attributes => manifestResource.Attributes; public object AttributesTooltip => manifestResource.Attributes.ToString(); @@ -97,8 +97,7 @@ struct ManifestResourceEntry public string NameTooltip => $"{MetadataTokens.GetHeapOffset(manifestResource.Name):X} \"{Name}\""; - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Implementation => MetadataTokens.GetToken(manifestResource.Implementation); public void OnImplementationClick() @@ -106,16 +105,8 @@ public void OnImplementationClick() MainWindow.Instance.JumpToReference(new EntityReference(module, manifestResource.Implementation, protocol: "metadata")); } - public string ImplementationTooltip { - get { - if (manifestResource.Implementation.IsNil) - return null; - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - manifestResource.Implementation.WriteTo(module, output, context); - return output.ToString(); - } - } + string implementationTooltip; + public string ImplementationTooltip => GenerateTooltip(ref implementationTooltip, module, manifestResource.Implementation); public ManifestResourceEntry(PEFile module, ManifestResourceHandle handle) { @@ -124,6 +115,7 @@ public ManifestResourceEntry(PEFile module, ManifestResourceHandle handle) this.metadata = module.Metadata; this.handle = handle; this.manifestResource = metadata.GetManifestResource(handle); + this.implementationTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs index 5c49878465..01e7806dce 100644 --- a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs @@ -87,8 +87,7 @@ struct MemberRefEntry + metadata.GetTableMetadataOffset(TableIndex.MemberRef) + metadata.GetTableRowSize(TableIndex.MemberRef) * (RID - 1); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(memberRef.Parent); public void OnParentClick() @@ -96,29 +95,18 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, memberRef.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - memberRef.Parent.WriteTo(module, output, default); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, memberRef.Parent); public string Name => metadata.GetString(memberRef.Name); public string NameTooltip => $"{MetadataTokens.GetHeapOffset(memberRef.Name):X} \"{Name}\""; - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(memberRef.Signature); - public string SignatureTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)handle).WriteTo(module, output, context); - return output.ToString(); - } - } + string signatureTooltip; + public string SignatureTooltip => GenerateTooltip(ref signatureTooltip, module, handle); public MemberRefEntry(PEFile module, MemberReferenceHandle handle) { @@ -127,6 +115,8 @@ public MemberRefEntry(PEFile module, MemberReferenceHandle handle) this.metadata = module.Metadata; this.handle = handle; this.memberRef = metadata.GetMemberReference(handle); + this.signatureTooltip = null; + this.parentTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs index bd7292df82..24b93afd00 100644 --- a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs @@ -86,8 +86,7 @@ struct MethodImplEntry + metadata.GetTableMetadataOffset(TableIndex.MethodDef) + metadata.GetTableRowSize(TableIndex.MethodDef) * (RID - 1); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int MethodDeclaration => MetadataTokens.GetToken(methodImpl.MethodDeclaration); public void OnMethodDeclarationClick() @@ -95,16 +94,10 @@ public void OnMethodDeclarationClick() MainWindow.Instance.JumpToReference(new EntityReference(module, methodImpl.MethodDeclaration, protocol: "metadata")); } - public string MethodDeclarationTooltip { - get { - ITextOutput output = new PlainTextOutput(); - methodImpl.MethodDeclaration.WriteTo(module, output, default); - return output.ToString(); - } - } + string methodDeclarationTooltip; + public string MethodDeclarationTooltip => GenerateTooltip(ref methodDeclarationTooltip, module, methodImpl.MethodDeclaration); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int MethodBody => MetadataTokens.GetToken(methodImpl.MethodBody); public void OnMethodBodyClick() @@ -112,16 +105,10 @@ public void OnMethodBodyClick() MainWindow.Instance.JumpToReference(new EntityReference(module, methodImpl.MethodBody, protocol: "metadata")); } - public string MethodBodyTooltip { - get { - ITextOutput output = new PlainTextOutput(); - methodImpl.MethodBody.WriteTo(module, output, default); - return output.ToString(); - } - } + string methodBodyTooltip; + public string MethodBodyTooltip => GenerateTooltip(ref methodBodyTooltip, module, methodImpl.MethodBody); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Type => MetadataTokens.GetToken(methodImpl.Type); public void OnTypeClick() @@ -129,13 +116,8 @@ public void OnTypeClick() MainWindow.Instance.JumpToReference(new EntityReference(module, methodImpl.Type, protocol: "metadata")); } - public string TypeTooltip { - get { - ITextOutput output = new PlainTextOutput(); - ((EntityHandle)methodImpl.Type).WriteTo(module, output, default); - return output.ToString(); - } - } + string typeTooltip; + public string TypeTooltip => GenerateTooltip(ref typeTooltip, module, methodImpl.Type); public MethodImplEntry(PEFile module, MethodImplementationHandle handle) { @@ -144,6 +126,9 @@ public MethodImplEntry(PEFile module, MethodImplementationHandle handle) this.metadata = module.Metadata; this.handle = handle; this.methodImpl = metadata.GetMethodImplementation(handle); + this.typeTooltip = null; + this.methodBodyTooltip = null; + this.methodDeclarationTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs index 73316effc3..3f3ab38dfc 100644 --- a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs @@ -90,13 +90,12 @@ struct MethodSemanticsEntry + metadata.GetTableMetadataOffset(TableIndex.MethodDef) + metadata.GetTableRowSize(TableIndex.MethodDef) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public MethodSemanticsAttributes Semantics => semantics; public string SemanticsTooltip => semantics.ToString(); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Method => MetadataTokens.GetToken(method); public void OnMethodClick() @@ -104,16 +103,10 @@ public void OnMethodClick() MainWindow.Instance.JumpToReference(new EntityReference(module, method, protocol: "metadata")); } - public string MethodTooltip { - get { - ITextOutput output = new PlainTextOutput(); - ((EntityHandle)method).WriteTo(module, output, default); - return output.ToString(); - } - } + string methodTooltip; + public string MethodTooltip => GenerateTooltip(ref methodTooltip, module, method); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Association => MetadataTokens.GetToken(association); public void OnAssociationClick() @@ -121,13 +114,8 @@ public void OnAssociationClick() MainWindow.Instance.JumpToReference(new EntityReference(module, association, protocol: "metadata")); } - public string AssociationTooltip { - get { - ITextOutput output = new PlainTextOutput(); - association.WriteTo(module, output, default); - return output.ToString(); - } - } + string associationTooltip; + public string AssociationTooltip => GenerateTooltip(ref associationTooltip, module, association); public MethodSemanticsEntry(PEFile module, Handle handle, MethodSemanticsAttributes semantics, MethodDefinitionHandle method, EntityHandle association) { @@ -138,6 +126,8 @@ public MethodSemanticsEntry(PEFile module, Handle handle, MethodSemanticsAttribu this.semantics = semantics; this.method = method; this.association = association; + this.methodTooltip = null; + this.associationTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs index 702f66594e..c050dbc58a 100644 --- a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs @@ -87,8 +87,7 @@ struct MethodSpecEntry + metadata.GetTableMetadataOffset(TableIndex.MethodSpec) + metadata.GetTableRowSize(TableIndex.MethodSpec) * (RID - 1); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Method => MetadataTokens.GetToken(methodSpec.Method); public void OnMethodClick() @@ -96,15 +95,10 @@ public void OnMethodClick() MainWindow.Instance.JumpToReference(new EntityReference(module, methodSpec.Method, protocol: "metadata")); } - public string MethodTooltip { - get { - ITextOutput output = new PlainTextOutput(); - methodSpec.Method.WriteTo(module, output, default); - return output.ToString(); - } - } + string methodTooltip; + public string MethodTooltip => GenerateTooltip(ref methodTooltip, module, methodSpec.Method); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(methodSpec.Signature); public string SignatureTooltip { @@ -131,6 +125,7 @@ public MethodSpecEntry(PEFile module, MethodSpecificationHandle handle) this.metadata = module.Metadata; this.handle = handle; this.methodSpec = metadata.GetMethodSpecification(handle); + this.methodTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs index c1b1160f28..46f81cde4f 100644 --- a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs @@ -92,7 +92,7 @@ struct MethodDefEntry : IMemberTreeNode + metadata.GetTableMetadataOffset(TableIndex.MethodDef) + metadata.GetTableRowSize(TableIndex.MethodDef) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public MethodAttributes Attributes => methodDef.Attributes; const MethodAttributes otherFlagsMask = ~(MethodAttributes.MemberAccessMask | MethodAttributes.VtableLayoutMask); @@ -103,7 +103,7 @@ struct MethodDefEntry : IMemberTreeNode FlagGroup.CreateMultipleChoiceGroup(typeof(MethodAttributes), "Flags:", (int)otherFlagsMask, (int)(methodDef.Attributes & otherFlagsMask), includeAll: false), }; - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public MethodImplAttributes ImplAttributes => methodDef.ImplAttributes; public object ImplAttributesTooltip => new FlagsTooltip { @@ -117,23 +117,12 @@ struct MethodDefEntry : IMemberTreeNode public string NameTooltip => $"{MetadataTokens.GetHeapOffset(methodDef.Name):X} \"{Name}\""; - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(methodDef.Signature); string signatureTooltip; - public string SignatureTooltip { - get { - if (signatureTooltip == null) - { - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)handle).WriteTo(module, output, context); - signatureTooltip = output.ToString(); - } - return signatureTooltip; - } - } + public string SignatureTooltip => GenerateTooltip(ref signatureTooltip, module, handle); IEntity IMemberTreeNode.Member => ((MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull()?.MainModule).GetDefinition(handle); diff --git a/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs b/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs index b86387ecdd..ba65094264 100644 --- a/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs @@ -82,17 +82,17 @@ struct ModuleEntry public string NameTooltip => $"{MetadataTokens.GetHeapOffset(moduleDef.Name):X} \"{Name}\""; - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Mvid => MetadataTokens.GetHeapOffset(moduleDef.Mvid); public string MvidTooltip => metadata.GetGuid(moduleDef.Mvid).ToString(); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int GenerationId => MetadataTokens.GetHeapOffset(moduleDef.GenerationId); public string GenerationIdTooltip => moduleDef.GenerationId.IsNil ? null : metadata.GetGuid(moduleDef.GenerationId).ToString(); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int BaseGenerationId => MetadataTokens.GetHeapOffset(moduleDef.BaseGenerationId); public string BaseGenerationIdTooltip => moduleDef.BaseGenerationId.IsNil ? null : metadata.GetGuid(moduleDef.BaseGenerationId).ToString(); diff --git a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs index d9560ab7e3..de347fd332 100644 --- a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs @@ -98,8 +98,7 @@ unsafe struct NestedClassEntry public int Offset { get; } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int NestedClass => MetadataTokens.GetToken(nestedClass.Nested); public void OnNestedClassClick() @@ -107,17 +106,10 @@ public void OnNestedClassClick() MainWindow.Instance.JumpToReference(new EntityReference(module, nestedClass.Nested, protocol: "metadata")); } - public string NestedClassTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)nestedClass.Nested).WriteTo(module, output, context); - return output.ToString(); - } - } + string nestedClassTooltip; + public string NestedClassTooltip => GenerateTooltip(ref nestedClassTooltip, module, nestedClass.Nested); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int EnclosingClass => MetadataTokens.GetToken(nestedClass.Enclosing); public void OnEnclosingClassClick() @@ -125,14 +117,8 @@ public void OnEnclosingClassClick() MainWindow.Instance.JumpToReference(new EntityReference(module, nestedClass.Enclosing, protocol: "metadata")); } - public string EnclosingClassTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)nestedClass.Enclosing).WriteTo(module, output, context); - return output.ToString(); - } - } + string enclosingClassTooltip; + public string EnclosingClassTooltip => GenerateTooltip(ref enclosingClassTooltip, module, nestedClass.Enclosing); public unsafe NestedClassEntry(PEFile module, byte* ptr, int metadataOffset, int row) { @@ -144,6 +130,8 @@ public unsafe NestedClassEntry(PEFile module, byte* ptr, int metadataOffset, int this.Offset = metadataOffset + rowOffset; int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; this.nestedClass = new NestedClass(ptr + rowOffset, typeDefSize); + this.nestedClassTooltip = null; + this.enclosingClassTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs b/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs index 5aea6118d3..5fee320ee6 100644 --- a/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs @@ -85,7 +85,7 @@ struct ParamEntry + metadata.GetTableMetadataOffset(TableIndex.Param) + metadata.GetTableRowSize(TableIndex.Param) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public ParameterAttributes Attributes => param.Attributes; public object AttributesTooltip => new FlagsTooltip { diff --git a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs index c4291171fc..64d9bb19ad 100644 --- a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs @@ -98,8 +98,7 @@ unsafe struct PropertyMapEntry public int Offset { get; } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(propertyMap.Parent); public void OnParentClick() @@ -107,17 +106,10 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, propertyMap.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)propertyMap.Parent).WriteTo(module, output, context); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, propertyMap.Parent); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int PropertyList => MetadataTokens.GetToken(propertyMap.PropertyList); public void OnPropertyListClick() @@ -125,14 +117,8 @@ public void OnPropertyListClick() MainWindow.Instance.JumpToReference(new EntityReference(module, propertyMap.PropertyList, protocol: "metadata")); } - public string PropertyListTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)propertyMap.PropertyList).WriteTo(module, output, context); - return output.ToString(); - } - } + string propertyListTooltip; + public string PropertyListTooltip => GenerateTooltip(ref propertyListTooltip, module, propertyMap.PropertyList); public PropertyMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) { @@ -145,6 +131,8 @@ public PropertyMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; int propertyDefSize = metadata.GetTableRowCount(TableIndex.Property) < ushort.MaxValue ? 2 : 4; this.propertyMap = new PropertyMap(ptr + rowOffset, typeDefSize, propertyDefSize); + this.propertyListTooltip = null; + this.parentTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs index 39c3808060..8240c2ac59 100644 --- a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs @@ -90,7 +90,7 @@ struct PropertyDefEntry : IMemberTreeNode + metadata.GetTableMetadataOffset(TableIndex.Property) + metadata.GetTableRowSize(TableIndex.Property) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public PropertyAttributes Attributes => propertyDef.Attributes; public object AttributesTooltip => new FlagsTooltip { @@ -103,16 +103,11 @@ struct PropertyDefEntry : IMemberTreeNode IEntity IMemberTreeNode.Member => ((MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull()?.MainModule).GetDefinition(handle); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(propertyDef.Signature); - public string SignatureTooltip { - get { - ITextOutput output = new PlainTextOutput(); - ((EntityHandle)handle).WriteTo(module, output, default); - return output.ToString(); - } - } + string signatureTooltip; + public string SignatureTooltip => GenerateTooltip(ref signatureTooltip, module, handle); public PropertyDefEntry(PEFile module, PropertyDefinitionHandle handle) { @@ -121,6 +116,7 @@ public PropertyDefEntry(PEFile module, PropertyDefinitionHandle handle) this.metadata = module.Metadata; this.handle = handle; this.propertyDef = metadata.GetPropertyDefinition(handle); + this.signatureTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs b/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs index ff6fbfb765..0e618aad21 100644 --- a/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs @@ -86,17 +86,11 @@ struct StandAloneSigEntry + metadata.GetTableMetadataOffset(TableIndex.StandAloneSig) + metadata.GetTableRowSize(TableIndex.StandAloneSig) * (RID - 1); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(standaloneSig.Signature); - public string SignatureTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)handle).WriteTo(module, output, context); - return output.ToString(); - } - } + string signatureTooltip; + public string SignatureTooltip => GenerateTooltip(ref signatureTooltip, module, handle); public StandAloneSigEntry(PEFile module, StandaloneSignatureHandle handle) { @@ -105,6 +99,7 @@ public StandAloneSigEntry(PEFile module, StandaloneSignatureHandle handle) this.metadata = module.Metadata; this.handle = handle; this.standaloneSig = metadata.GetStandaloneSignature(handle); + this.signatureTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs index 078d8afb05..3388a244ae 100644 --- a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs @@ -91,7 +91,7 @@ struct TypeDefEntry : IMemberTreeNode + metadata.GetTableMetadataOffset(TableIndex.TypeDef) + metadata.GetTableRowSize(TableIndex.TypeDef) * (RID - 1); - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public TypeAttributes Attributes => typeDef.Attributes; const TypeAttributes otherFlagsMask = ~(TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.ClassSemanticsMask | TypeAttributes.StringFormatMask | TypeAttributes.CustomFormatMask); @@ -113,8 +113,7 @@ struct TypeDefEntry : IMemberTreeNode public string Namespace => metadata.GetString(typeDef.Namespace); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int BaseType => MetadataTokens.GetToken(typeDef.BaseType); public void OnBaseTypeClick() @@ -145,8 +144,7 @@ public string BaseTypeTooltip { } } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int FieldList => MetadataTokens.GetToken(typeDef.GetFields().FirstOrDefault()); public void OnFieldListClick() @@ -154,20 +152,17 @@ public void OnFieldListClick() MainWindow.Instance.JumpToReference(new EntityReference(module, typeDef.GetFields().FirstOrDefault(), protocol: "metadata")); } + string fieldListTooltip; public string FieldListTooltip { get { var field = typeDef.GetFields().FirstOrDefault(); if (field.IsNil) return null; - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)field).WriteTo(module, output, context); - return output.ToString(); + return GenerateTooltip(ref fieldListTooltip, module, field); } } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int MethodList => MetadataTokens.GetToken(typeDef.GetMethods().FirstOrDefault()); public void OnMethodListClick() @@ -175,15 +170,13 @@ public void OnMethodListClick() MainWindow.Instance.JumpToReference(new EntityReference(module, typeDef.GetMethods().FirstOrDefault(), protocol: "metadata")); } + string methodListTooltip; public string MethodListTooltip { get { var method = typeDef.GetMethods().FirstOrDefault(); if (method.IsNil) return null; - ITextOutput output = new PlainTextOutput(); - var context = new Decompiler.Metadata.MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)method).WriteTo(module, output, context); - return output.ToString(); + return GenerateTooltip(ref methodListTooltip, module, method); } } @@ -196,6 +189,8 @@ public TypeDefEntry(PEFile module, TypeDefinitionHandle handle) this.metadata = module.Metadata; this.handle = handle; this.typeDef = metadata.GetTypeDefinition(handle); + this.methodListTooltip = null; + this.fieldListTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs index 24db219f93..f0a773dc3d 100644 --- a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs @@ -86,8 +86,7 @@ struct TypeRefEntry + metadata.GetTableMetadataOffset(TableIndex.TypeRef) + metadata.GetTableRowSize(TableIndex.TypeRef) * (RID - 1); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int ResolutionScope => MetadataTokens.GetToken(typeRef.ResolutionScope); public void OnResolutionScopeClick() @@ -95,31 +94,8 @@ public void OnResolutionScopeClick() MainWindow.Instance.JumpToReference(new EntityReference(module, typeRef.ResolutionScope, protocol: "metadata")); } - public string ResolutionScopeTooltip { - get { - if (typeRef.ResolutionScope.IsNil) - return null; - var output = new PlainTextOutput(); - switch (typeRef.ResolutionScope.Kind) - { - case HandleKind.ModuleDefinition: - output.Write(metadata.GetString(metadata.GetModuleDefinition().Name)); - break; - case HandleKind.ModuleReference: - ModuleReference moduleReference = metadata.GetModuleReference((ModuleReferenceHandle)typeRef.ResolutionScope); - output.Write(metadata.GetString(moduleReference.Name)); - break; - case HandleKind.AssemblyReference: - var asmRef = new Decompiler.Metadata.AssemblyReference(module, (AssemblyReferenceHandle)typeRef.ResolutionScope); - output.Write(asmRef.ToString()); - break; - default: - typeRef.ResolutionScope.WriteTo(module, output, default); - break; - } - return output.ToString(); - } - } + string resolutionScopeTooltip; + public string ResolutionScopeTooltip => GenerateTooltip(ref resolutionScopeTooltip, module, typeRef.ResolutionScope); public string NameTooltip => $"{MetadataTokens.GetHeapOffset(typeRef.Name):X} \"{Name}\""; @@ -136,6 +112,7 @@ public TypeRefEntry(PEFile module, TypeReferenceHandle handle) this.metadata = module.Metadata; this.handle = handle; this.typeRef = metadata.GetTypeReference(handle); + this.resolutionScopeTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs index 1d19874e57..5b87adaa13 100644 --- a/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs @@ -86,7 +86,7 @@ struct TypeSpecEntry + metadata.GetTableMetadataOffset(TableIndex.TypeSpec) + metadata.GetTableRowSize(TableIndex.TypeSpec) * (RID - 1); - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(typeSpec.Signature); public string SignatureTooltip { diff --git a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs index 47fb5e36e3..9e9d46c612 100644 --- a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs @@ -120,6 +120,7 @@ internal enum CustomDebugInformationKind DefaultNamespaces, EditAndContinueLocalSlotMap, EditAndContinueLambdaAndClosureMap, + EncStateMachineStateMap, EmbeddedSource, SourceLink, MethodSteppingInformation, @@ -154,6 +155,10 @@ static CustomDebugInformationKind GetKind(MetadataReader metadata, GuidHandle h) { return CustomDebugInformationKind.EditAndContinueLambdaAndClosureMap; } + if (KnownGuids.EncStateMachineStateMap == guid) + { + return CustomDebugInformationKind.EncStateMachineStateMap; + } if (KnownGuids.EmbeddedSource == guid) { return CustomDebugInformationKind.EmbeddedSource; @@ -188,10 +193,11 @@ static CustomDebugInformationKind GetKind(MetadataReader metadata, GuidHandle h) public int RID => MetadataTokens.GetRowNumber(handle); - public object Offset => offset == null ? null : offset; + public int Token => MetadataTokens.GetToken(handle); + + public object Offset => offset == null ? "n/a" : (object)offset; - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(debugInfo.Parent); public void OnParentClick() @@ -199,17 +205,10 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, debugInfo.Parent, protocol: "metadata")); } - public string ParentTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - debugInfo.Parent.WriteTo(module, output, context); - return output.ToString(); - } - } + string parentTooltip; + public string ParentTooltip => GenerateTooltip(ref parentTooltip, module, debugInfo.Parent); string kindString; - public string Kind { get { if (kindString != null) @@ -224,57 +223,28 @@ public string Kind { { guid = Guid.Empty; } - switch (kind) - { - case CustomDebugInformationKind.None: - kindString = ""; - break; - case CustomDebugInformationKind.StateMachineHoistedLocalScopes: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - State Machine Hoisted Local Scopes (C# / VB) [{guid}]"; - break; - case CustomDebugInformationKind.DynamicLocalVariables: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Dynamic Local Variables (C#) [{guid}]"; - break; - case CustomDebugInformationKind.DefaultNamespaces: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Default Namespaces (VB) [{guid}]"; - break; - case CustomDebugInformationKind.EditAndContinueLocalSlotMap: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Edit And Continue Local Slot Map (C# / VB) [{guid}]"; - break; - case CustomDebugInformationKind.EditAndContinueLambdaAndClosureMap: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Edit And Continue Lambda And Closure Map (C# / VB) [{guid}]"; - break; - case CustomDebugInformationKind.EmbeddedSource: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Embedded Source (C# / VB) [{guid}]"; - break; - case CustomDebugInformationKind.SourceLink: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Source Link (C# / VB) [{guid}]"; - break; - case CustomDebugInformationKind.MethodSteppingInformation: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Method Stepping Information (C# / VB) [{guid}]"; - break; - case CustomDebugInformationKind.CompilationOptions: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Compilation Options (C# / VB) [{ guid}]"; - break; - case CustomDebugInformationKind.CompilationMetadataReferences: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Compilation Metadata References (C# / VB) [{ guid}]"; - break; - case CustomDebugInformationKind.TupleElementNames: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Tuple Element Names (C#) [{ guid}]"; - break; - case CustomDebugInformationKind.TypeDefinitionDocuments: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Type Definition Documents (C# / VB) [{ guid}]"; - break; - default: - kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Unknown [{guid}]"; - break; - } - + kindString = kind switch { + CustomDebugInformationKind.None => "", + CustomDebugInformationKind.StateMachineHoistedLocalScopes => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - State Machine Hoisted Local Scopes (C# / VB) [{guid}]", + CustomDebugInformationKind.DynamicLocalVariables => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Dynamic Local Variables (C#) [{guid}]", + CustomDebugInformationKind.DefaultNamespaces => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Default Namespaces (VB) [{guid}]", + CustomDebugInformationKind.EditAndContinueLocalSlotMap => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Edit And Continue Local Slot Map (C# / VB) [{guid}]", + CustomDebugInformationKind.EditAndContinueLambdaAndClosureMap => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Edit And Continue Lambda And Closure Map (C# / VB) [{guid}]", + CustomDebugInformationKind.EncStateMachineStateMap => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Edit And Continue State Machine State Map (C# / VB) [{guid}]", + CustomDebugInformationKind.EmbeddedSource => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Embedded Source (C# / VB) [{guid}]", + CustomDebugInformationKind.SourceLink => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Source Link (C# / VB) [{guid}]", + CustomDebugInformationKind.MethodSteppingInformation => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Method Stepping Information (C# / VB) [{guid}]", + CustomDebugInformationKind.CompilationOptions => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Compilation Options (C# / VB) [{guid}]", + CustomDebugInformationKind.CompilationMetadataReferences => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Compilation Metadata References (C# / VB) [{guid}]", + CustomDebugInformationKind.TupleElementNames => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Tuple Element Names (C#) [{guid}]", + CustomDebugInformationKind.TypeDefinitionDocuments => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Type Definition Documents (C# / VB) [{guid}]", + _ => $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - Unknown [{guid}]", + }; return kindString; } } - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Value => MetadataTokens.GetHeapOffset(debugInfo.Value); public string ValueTooltip { diff --git a/ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs b/ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs index ab1660d3e0..2a7fdb8e4b 100644 --- a/ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs @@ -82,13 +82,15 @@ struct DocumentEntry public int RID => MetadataTokens.GetRowNumber(handle); + public int Token => MetadataTokens.GetToken(handle); + public object Offset => offset == null ? "n/a" : (object)offset; public string Name => metadata.GetString(document.Name); public string NameTooltip => $"{MetadataTokens.GetHeapOffset(document.Name):X} \"{Name}\""; - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int HashAlgorithm => MetadataTokens.GetHeapOffset(document.HashAlgorithm); public string HashAlgorithmTooltip { @@ -104,7 +106,7 @@ public string HashAlgorithmTooltip { } } - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Hash => MetadataTokens.GetHeapOffset(document.Hash); public string HashTooltip { @@ -116,7 +118,7 @@ public string HashTooltip { } } - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Language => MetadataTokens.GetHeapOffset(document.Language); public string LanguageTooltip { diff --git a/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs b/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs index a73e65bdc7..0e6ee99d0a 100644 --- a/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs @@ -86,10 +86,11 @@ struct ImportScopeEntry public int RID => MetadataTokens.GetRowNumber(handle); + public int Token => MetadataTokens.GetToken(handle); + public object Offset => offset == null ? "n/a" : (object)offset; - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(localScope.Parent); public void OnParentClick() @@ -97,7 +98,7 @@ public void OnParentClick() MainWindow.Instance.JumpToReference(new EntityReference(module, localScope.Parent, protocol: "metadata")); } - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Imports => MetadataTokens.GetHeapOffset(localScope.ImportsBlob); public ImportScopeEntry(PEFile module, MetadataReader metadata, bool isEmbedded, ImportScopeHandle handle) diff --git a/ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs b/ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs index 91ca9c0b44..9eaf8be2af 100644 --- a/ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs @@ -84,13 +84,15 @@ struct LocalConstantEntry public int RID => MetadataTokens.GetRowNumber(handle); + public int Token => MetadataTokens.GetToken(handle); + public object Offset => offset == null ? "n/a" : (object)offset; public string Name => metadata.GetString(localConst.Name); public string NameTooltip => $"{MetadataTokens.GetHeapOffset(localConst.Name):X} \"{Name}\""; - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(localConst.Signature); public LocalConstantEntry(PEFile module, MetadataReader metadata, bool isEmbedded, LocalConstantHandle handle) diff --git a/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs b/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs index e934deadfd..8b8ac8b1ab 100644 --- a/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs @@ -86,10 +86,11 @@ struct LocalScopeEntry public int RID => MetadataTokens.GetRowNumber(handle); + public int Token => MetadataTokens.GetToken(handle); + public object Offset => offset == null ? "n/a" : (object)offset; - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Method => MetadataTokens.GetToken(localScope.Method); public void OnMethodClick() @@ -97,16 +98,10 @@ public void OnMethodClick() MainWindow.Instance.JumpToReference(new EntityReference(module, localScope.Method, protocol: "metadata")); } - public string MethodTooltip { - get { - ITextOutput output = new PlainTextOutput(); - ((EntityHandle)localScope.Method).WriteTo(module, output, default); - return output.ToString(); - } - } + string methodTooltip; + public string MethodTooltip => GenerateTooltip(ref methodTooltip, module, localScope.Method); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int ImportScope => MetadataTokens.GetToken(localScope.ImportScope); public void OnImportScopeClick() @@ -114,8 +109,7 @@ public void OnImportScopeClick() MainWindow.Instance.JumpToReference(new EntityReference(module, localScope.ImportScope, protocol: "metadata")); } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int VariableList => MetadataTokens.GetToken(localScope.GetLocalVariables().FirstOrDefault()); public void OnVariableListClick() @@ -123,8 +117,7 @@ public void OnVariableListClick() MainWindow.Instance.JumpToReference(new EntityReference(module, localScope.GetLocalVariables().FirstOrDefault(), protocol: "metadata")); } - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int ConstantList => MetadataTokens.GetToken(localScope.GetLocalConstants().FirstOrDefault()); public void OnConstantListClick() @@ -144,6 +137,7 @@ public LocalScopeEntry(PEFile module, MetadataReader metadata, bool isEmbedded, this.metadata = metadata; this.handle = handle; this.localScope = metadata.GetLocalScope(handle); + this.methodTooltip = null; } } diff --git a/ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs b/ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs index 458bd5abe0..9913e1a6b5 100644 --- a/ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs @@ -80,9 +80,11 @@ struct LocalVariableEntry public int RID => MetadataTokens.GetRowNumber(handle); + public int Token => MetadataTokens.GetToken(handle); + public object Offset => offset == null ? "n/a" : (object)offset; - [StringFormat("X8")] + [ColumnInfo("X8", Kind = ColumnKind.Other)] public LocalVariableAttributes Attributes => localVar.Attributes; public object AttributesTooltip => new FlagsTooltip() { diff --git a/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs index 8ed8b05fb6..d439308e44 100644 --- a/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs @@ -84,10 +84,11 @@ struct MethodDebugInformationEntry public int RID => MetadataTokens.GetRowNumber(handle); + public int Token => MetadataTokens.GetToken(handle); + public object Offset => offset == null ? "n/a" : (object)offset; - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Document => MetadataTokens.GetToken(debugInfo.Document); public void OnDocumentClick() @@ -104,7 +105,7 @@ public string DocumentTooltip { } } - [StringFormat("X")] + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int SequencePoints => MetadataTokens.GetHeapOffset(debugInfo.SequencePointsBlob); public string SequencePointsTooltip { @@ -120,8 +121,7 @@ public string SequencePointsTooltip { } } - [StringFormat("X")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int LocalSignature => MetadataTokens.GetToken(debugInfo.LocalSignature); public void OnLocalSignatureClick() diff --git a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs index 8407f73284..e93c0c3d9a 100644 --- a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs @@ -54,7 +54,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) StateMachineMethodEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.StateMachineMethod); var reader = new BlobReader(metadata.MetadataPointer, metadata.MetadataLength); - reader.Offset = +metadata.GetTableMetadataOffset(TableIndex.StateMachineMethod); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.StateMachineMethod); for (int rid = 1; rid <= length; rid++) { @@ -88,10 +88,11 @@ struct StateMachineMethodEntry public int RID { get; } + public int Token => 0x36000000 + RID; + public object Offset => offset == null ? "n/a" : (object)offset; - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int MoveNextMethod => MetadataTokens.GetToken(moveNextMethod); public void OnMoveNextMethodClick() @@ -99,17 +100,10 @@ public void OnMoveNextMethodClick() MainWindow.Instance.JumpToReference(new EntityReference(module, moveNextMethod, protocol: "metadata")); } - public string MoveNextMethodTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)moveNextMethod).WriteTo(module, output, context); - return output.ToString(); - } - } + string moveNextMethodTooltip; + public string MoveNextMethodTooltip => GenerateTooltip(ref moveNextMethodTooltip, module, moveNextMethod); - [StringFormat("X8")] - [LinkToTable] + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int KickoffMethod => MetadataTokens.GetToken(kickoffMethod); public void OnKickofMethodClick() @@ -117,14 +111,8 @@ public void OnKickofMethodClick() MainWindow.Instance.JumpToReference(new EntityReference(module, kickoffMethod, protocol: "metadata")); } - public string KickoffMethodTooltip { - get { - ITextOutput output = new PlainTextOutput(); - var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); - ((EntityHandle)kickoffMethod).WriteTo(module, output, context); - return output.ToString(); - } - } + string kickoffMethodTooltip; + public string KickoffMethodTooltip => GenerateTooltip(ref kickoffMethodTooltip, module, kickoffMethod); public StateMachineMethodEntry(PEFile module, ref BlobReader reader, bool isEmbedded, int row) { @@ -138,6 +126,8 @@ public StateMachineMethodEntry(PEFile module, ref BlobReader reader, bool isEmbe int methodDefSize = metadata.GetTableRowCount(TableIndex.MethodDef) < ushort.MaxValue ? 2 : 4; this.moveNextMethod = MetadataTokens.MethodDefinitionHandle(methodDefSize == 2 ? reader.ReadInt16() : reader.ReadInt32()); this.kickoffMethod = MetadataTokens.MethodDefinitionHandle(methodDefSize == 2 ? reader.ReadInt16() : reader.ReadInt32()); + this.kickoffMethodTooltip = null; + this.moveNextMethodTooltip = null; } } diff --git a/ILSpy/Metadata/GoToTokenCommand.cs b/ILSpy/Metadata/GoToTokenCommand.cs index b35db7d08d..d8963ce603 100644 --- a/ILSpy/Metadata/GoToTokenCommand.cs +++ b/ILSpy/Metadata/GoToTokenCommand.cs @@ -60,7 +60,7 @@ public bool IsVisible(TextViewContext context) Type type = cell.Item.GetType(); var property = type.GetProperty(cell.Column.Header.ToString()); var moduleField = type.GetField("module", BindingFlags.NonPublic | BindingFlags.Instance); - if (property == null || property.PropertyType != typeof(int) || !property.GetCustomAttributes(false).Any(a => a is StringFormatAttribute sf && sf.Format == "X8")) + if (property == null || property.PropertyType != typeof(int) || !property.GetCustomAttributes(false).Any(a => a is ColumnInfoAttribute { Kind: ColumnKind.Token } c)) return null; module = (PEFile)moduleField.GetValue(cell.Item); return (int)property.GetValue(cell.Item); diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs index cd6558a178..da418b554a 100644 --- a/ILSpy/Metadata/Helpers.cs +++ b/ILSpy/Metadata/Helpers.cs @@ -143,7 +143,7 @@ DataGridColumn GetColumn() var descriptor = (PropertyDescriptor)e.PropertyDescriptor; - if (descriptor.Attributes.OfType().Any()) + if (descriptor.Attributes.OfType().Any(c => c.Kind == ColumnKind.Token || c.LinkToTable)) { return new DataGridTemplateColumn() { Header = e.PropertyName, @@ -200,12 +200,12 @@ static void ApplyAttributes(PropertyDescriptor descriptor, Binding binding, Data string key = descriptor.PropertyType.Name + "Filter"; column.SetTemplate((ControlTemplate)MetadataTableViews.Instance[key]); } - var stringFormat = descriptor.Attributes.OfType().FirstOrDefault(); - if (stringFormat != null) + var columnInfo = descriptor.Attributes.OfType().FirstOrDefault(); + if (columnInfo != null) { - binding.StringFormat = stringFormat.Format; + binding.StringFormat = columnInfo.Format; if (!descriptor.PropertyType.IsEnum - && stringFormat.Format.StartsWith("X", StringComparison.OrdinalIgnoreCase)) + && columnInfo.Format.StartsWith("X", StringComparison.OrdinalIgnoreCase)) { column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["HexFilter"]); } @@ -297,20 +297,28 @@ public static string ReadUTF8StringNullTerminated(this ref BlobReader reader) } } - class StringFormatAttribute : Attribute + enum ColumnKind + { + HeapOffset, + Token, + Other + } + + [AttributeUsage(AttributeTargets.Property)] + class ColumnInfoAttribute : Attribute { public string Format { get; } - public StringFormatAttribute(string format) + public ColumnKind Kind { get; set; } + + public bool LinkToTable { get; set; } + + public ColumnInfoAttribute(string format) { this.Format = format; } } - class LinkToTableAttribute : Attribute - { - } - [Flags] internal enum TableMask : ulong { diff --git a/ILSpy/Metadata/MetadataTableTreeNode.cs b/ILSpy/Metadata/MetadataTableTreeNode.cs index dbd8a424c5..204270dc3e 100644 --- a/ILSpy/Metadata/MetadataTableTreeNode.cs +++ b/ILSpy/Metadata/MetadataTableTreeNode.cs @@ -22,11 +22,10 @@ using System.Windows.Controls; using System.Windows.Threading; +using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.ILSpy.TextView; +using ICSharpCode.Decompiler.IL; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.ViewModels; -using ICSharpCode.TreeView; namespace ICSharpCode.ILSpy.Metadata { @@ -62,6 +61,73 @@ private void View_Loaded(object sender, System.Windows.RoutedEventArgs e) view.Loaded -= View_Loaded; this.scrollTarget = default; } + + protected static string GenerateTooltip(ref string tooltip, PEFile module, EntityHandle handle) + { + if (tooltip == null) + { + if (handle.IsNil) + { + return null; + } + ITextOutput output = new PlainTextOutput(); + var context = new MetadataGenericContext(default(TypeDefinitionHandle), module); + var metadata = module.Metadata; + switch (handle.Kind) + { + case HandleKind.ModuleDefinition: + output.Write(metadata.GetString(metadata.GetModuleDefinition().Name)); + output.Write(" (this module)"); + break; + case HandleKind.ModuleReference: + ModuleReference moduleReference = metadata.GetModuleReference((ModuleReferenceHandle)handle); + output.Write(metadata.GetString(moduleReference.Name)); + break; + case HandleKind.AssemblyReference: + var asmRef = new Decompiler.Metadata.AssemblyReference(module, (AssemblyReferenceHandle)handle); + output.Write(asmRef.ToString()); + break; + case HandleKind.Parameter: + var param = metadata.GetParameter((ParameterHandle)handle); + output.Write(param.SequenceNumber + " - " + metadata.GetString(param.Name)); + break; + case HandleKind.EventDefinition: + var @event = metadata.GetEventDefinition((EventDefinitionHandle)handle); + output.Write(metadata.GetString(@event.Name)); + break; + case HandleKind.PropertyDefinition: + var prop = metadata.GetPropertyDefinition((PropertyDefinitionHandle)handle); + output.Write(metadata.GetString(prop.Name)); + break; + case HandleKind.AssemblyDefinition: + var ad = metadata.GetAssemblyDefinition(); + output.Write(metadata.GetString(ad.Name)); + output.Write(" (this assembly)"); + break; + case HandleKind.AssemblyFile: + var af = metadata.GetAssemblyFile((AssemblyFileHandle)handle); + output.Write(metadata.GetString(af.Name)); + break; + case HandleKind.GenericParameter: + var gp = metadata.GetGenericParameter((GenericParameterHandle)handle); + output.Write(metadata.GetString(gp.Name)); + break; + case HandleKind.ManifestResource: + var mfr = metadata.GetManifestResource((ManifestResourceHandle)handle); + output.Write(metadata.GetString(mfr.Name)); + break; + case HandleKind.Document: + var doc = metadata.GetDocument((DocumentHandle)handle); + output.Write(metadata.GetString(doc.Name)); + break; + default: + handle.WriteTo(module, output, context); + break; + } + tooltip = "(" + handle.Kind + ") " + output.ToString(); + } + return tooltip; + } } internal abstract class DebugMetadataTableTreeNode : MetadataTableTreeNode diff --git a/ILSpy/Metadata/MetadataTreeNode.cs b/ILSpy/Metadata/MetadataTreeNode.cs index f6b0ea37cf..5511d5ad58 100644 --- a/ILSpy/Metadata/MetadataTreeNode.cs +++ b/ILSpy/Metadata/MetadataTreeNode.cs @@ -25,6 +25,7 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.ViewModels; @@ -32,7 +33,7 @@ namespace ICSharpCode.ILSpy.Metadata { class MetadataTreeNode : ILSpyTreeNode { - private PEFile module; + private readonly PEFile module; private AssemblyTreeNode assemblyTreeNode; public MetadataTreeNode(PEFile module, AssemblyTreeNode assemblyTreeNode) @@ -42,7 +43,7 @@ public MetadataTreeNode(PEFile module, AssemblyTreeNode assemblyTreeNode) this.LazyLoading = true; } - public override object Text => "Metadata"; + public override object Text => Resources.Metadata; public override object Icon => Images.Library; diff --git a/ILSpy/NativeMethods.cs b/ILSpy/NativeMethods.cs index 87f087a686..be6ec0c51d 100644 --- a/ILSpy/NativeMethods.cs +++ b/ILSpy/NativeMethods.cs @@ -35,7 +35,7 @@ static class NativeMethods [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern unsafe int GetWindowThreadProcessId(IntPtr hWnd, int* lpdwProcessId); - [DllImport("user32.dll", CharSet = CharSet.Auto)] + [DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern int GetWindowText(IntPtr hWnd, [Out] StringBuilder title, int size); public static string GetWindowText(IntPtr hWnd, int maxLength) @@ -193,6 +193,16 @@ public unsafe static string GetProcessNameFromWindow(IntPtr hWnd) return null; } } + + [DllImport("dwmapi.dll", PreserveSig = true)] + public static extern int DwmSetWindowAttribute(IntPtr hwnd, DwmWindowAttribute attr, ref int attrValue, int attrSize); + + public static bool UseImmersiveDarkMode(IntPtr hWnd, bool enable) + { + int darkMode = enable ? 1 : 0; + int hr = DwmSetWindowAttribute(hWnd, DwmWindowAttribute.UseImmersiveDarkMode, ref darkMode, sizeof(int)); + return hr >= 0; + } } [return: MarshalAs(UnmanagedType.Bool)] @@ -212,4 +222,33 @@ public CopyDataStruct(IntPtr padding, int size, IntPtr buffer) this.Buffer = buffer; } } + + public enum DwmWindowAttribute : uint + { + NCRenderingEnabled = 1, + NCRenderingPolicy, + TransitionsForceDisabled, + AllowNCPaint, + CaptionButtonBounds, + NonClientRtlLayout, + ForceIconicRepresentation, + Flip3DPolicy, + ExtendedFrameBounds, + HasIconicBitmap, + DisallowPeek, + ExcludedFromPeek, + Cloak, + Cloaked, + FreezeRepresentation, + PassiveUpdateMode, + UseHostBackdropBrush, + UseImmersiveDarkMode = 20, + WindowCornerPreference = 33, + BorderColor, + CaptionColor, + TextColor, + VisibleFrameBorderThickness, + SystemBackdropType, + Last + } } diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs index b1947fc745..aec4ced54d 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs @@ -24,6 +24,8 @@ using System.Windows.Data; using System.Xml.Linq; +using ICSharpCode.ILSpyX.Settings; + namespace ICSharpCode.ILSpy.Options { /// @@ -39,17 +41,7 @@ public DecompilerSettingsPanel() public static Decompiler.DecompilerSettings LoadDecompilerSettings(ILSpySettings settings) { - XElement e = settings["DecompilerSettings"]; - var newSettings = new Decompiler.DecompilerSettings(); - var properties = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute()?.Browsable != false); - foreach (var p in properties) - { - var value = (bool?)e.Attribute(p.Name); - if (value.HasValue) - p.SetValue(newSettings, value.Value); - } - return newSettings; + return ISettingsProvider.LoadDecompilerSettings(settings); } public void Load(ILSpySettings settings) @@ -59,19 +51,8 @@ public void Load(ILSpySettings settings) public void Save(XElement root) { - XElement section = new XElement("DecompilerSettings"); var newSettings = ((DecompilerSettingsViewModel)this.DataContext).ToDecompilerSettings(); - var properties = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute()?.Browsable != false); - foreach (var p in properties) - { - section.SetAttributeValue(p.Name, p.GetValue(newSettings)); - } - XElement existingElement = root.Element("DecompilerSettings"); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); + ISettingsProvider.SaveDecompilerSettings(root, newSettings); MainWindow.Instance.CurrentDecompilerSettings = newSettings; MainWindow.Instance.AssemblyListManager.ApplyWinRTProjections = newSettings.ApplyWindowsRuntimeProjections; diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml b/ILSpy/Options/DisplaySettingsPanel.xaml index 1e6852e65b..52f5c3ab13 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml +++ b/ILSpy/Options/DisplaySettingsPanel.xaml @@ -6,12 +6,17 @@ xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - d:DataContext="{d:DesignInstance local:DisplaySettings}"> + xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" + d:DataContext="{d:DesignInstance local:DisplaySettingsViewModel}"> + + diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml.cs b/ILSpy/Options/DisplaySettingsPanel.xaml.cs index b6b18f0f10..908ed35017 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml.cs +++ b/ILSpy/Options/DisplaySettingsPanel.xaml.cs @@ -26,6 +26,8 @@ using System.Windows.Threading; using System.Xml.Linq; +using ICSharpCode.ILSpyX.Settings; + namespace ICSharpCode.ILSpy.Options { /// @@ -94,10 +96,10 @@ orderby ff.Source select ff).ToArray(); } - public static DisplaySettings LoadDisplaySettings(ILSpySettings settings) + public static DisplaySettingsViewModel LoadDisplaySettings(ILSpySettings settings) { XElement e = settings["DisplaySettings"]; - var s = new DisplaySettings(); + var s = new DisplaySettingsViewModel(); s.SelectedFont = new FontFamily((string)e.Attribute("Font") ?? "Consolas"); s.SelectedFontSize = (double?)e.Attribute("FontSize") ?? 10.0 * 4 / 3; s.ShowLineNumbers = (bool?)e.Attribute("ShowLineNumbers") ?? false; @@ -119,12 +121,14 @@ public static DisplaySettings LoadDisplaySettings(ILSpySettings settings) s.ShowRawOffsetsAndBytesBeforeInstruction = (bool?)e.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false; s.StyleWindowTitleBar = (bool?)e.Attribute("StyleWindowTitleBar") ?? false; + s.Theme = MainWindow.Instance.SessionSettings.Theme; + return s; } public void Save(XElement root) { - var s = (DisplaySettings)this.DataContext; + var s = (DisplaySettingsViewModel)this.DataContext; var section = new XElement("DisplaySettings"); section.SetAttributeValue("Font", s.SelectedFont.Source); @@ -148,13 +152,22 @@ public void Save(XElement root) section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", s.ShowRawOffsetsAndBytesBeforeInstruction); section.SetAttributeValue("StyleWindowTitleBar", s.StyleWindowTitleBar); - XElement existingElement = root.Element("DisplaySettings"); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); + MainWindow.Instance.SessionSettings.Theme = s.Theme; + var sessionSettings = MainWindow.Instance.SessionSettings.ToXml(); MainWindow.Instance.CurrentDisplaySettings.CopyValues(s); + + Update(section); + Update(sessionSettings); + + void Update(XElement element) + { + var existingElement = root.Element(element.Name); + if (existingElement != null) + existingElement.ReplaceWith(element); + else + root.Add(element); + } } private void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e) @@ -174,7 +187,7 @@ private void OnPaste(object sender, DataObjectPastingEventArgs e) public void LoadDefaults() { - MainWindow.Instance.CurrentDisplaySettings.CopyValues(new DisplaySettings()); + MainWindow.Instance.CurrentDisplaySettings.CopyValues(new DisplaySettingsViewModel()); this.DataContext = MainWindow.Instance.CurrentDisplaySettings; } } diff --git a/ILSpy/Options/DisplaySettings.cs b/ILSpy/Options/DisplaySettingsViewModel.cs similarity index 94% rename from ILSpy/Options/DisplaySettings.cs rename to ILSpy/Options/DisplaySettingsViewModel.cs index f9070166cb..15b54ebe5b 100644 --- a/ILSpy/Options/DisplaySettings.cs +++ b/ILSpy/Options/DisplaySettingsViewModel.cs @@ -20,15 +20,18 @@ using System.Runtime.CompilerServices; using System.Windows.Media; +using ICSharpCode.ILSpy.Themes; + namespace ICSharpCode.ILSpy.Options { /// /// Description of DisplaySettings. /// - public class DisplaySettings : INotifyPropertyChanged + public class DisplaySettingsViewModel : INotifyPropertyChanged { - public DisplaySettings() + public DisplaySettingsViewModel() { + this.theme = ThemeManager.Current.DefaultTheme; this.selectedFont = new FontFamily("Consolas"); this.selectedFontSize = 10.0 * 4 / 3; this.sortResults = true; @@ -52,6 +55,19 @@ protected void OnPropertyChanged([CallerMemberName] string propertyName = null) } #endregion + string theme; + + public string Theme { + get { return theme; } + set { + if (theme != value) + { + theme = value; + OnPropertyChanged(); + } + } + } + FontFamily selectedFont; public FontFamily SelectedFont { @@ -312,8 +328,9 @@ public bool ShowRawOffsetsAndBytesBeforeInstruction { } } - public void CopyValues(DisplaySettings s) + public void CopyValues(DisplaySettingsViewModel s) { + this.Theme = s.Theme; this.SelectedFont = s.selectedFont; this.SelectedFontSize = s.selectedFontSize; this.ShowLineNumbers = s.showLineNumbers; diff --git a/ILSpy/Options/MiscSettingsPanel.xaml.cs b/ILSpy/Options/MiscSettingsPanel.xaml.cs index 56e492747d..f7c49c14e5 100644 --- a/ILSpy/Options/MiscSettingsPanel.xaml.cs +++ b/ILSpy/Options/MiscSettingsPanel.xaml.cs @@ -19,6 +19,8 @@ using System.Windows.Controls; using System.Xml.Linq; +using ICSharpCode.ILSpyX.Settings; + namespace ICSharpCode.ILSpy.Options { /// @@ -37,44 +39,31 @@ public void Load(ILSpySettings settings) this.DataContext = LoadMiscSettings(settings); } - static MiscSettings currentMiscSettings; + static MiscSettingsViewModel currentMiscSettings; - public static MiscSettings CurrentMiscSettings { + public static MiscSettingsViewModel CurrentMiscSettings { get { return currentMiscSettings ?? (currentMiscSettings = LoadMiscSettings(ILSpySettings.Load())); } } - public static MiscSettings LoadMiscSettings(ILSpySettings settings) + public static MiscSettingsViewModel LoadMiscSettings(ILSpySettings settings) { - XElement e = settings["MiscSettings"]; - var s = new MiscSettings(); - s.AllowMultipleInstances = (bool?)e.Attribute(nameof(s.AllowMultipleInstances)) ?? false; - s.LoadPreviousAssemblies = (bool?)e.Attribute(nameof(s.LoadPreviousAssemblies)) ?? true; - - return s; + var s = MiscSettings.Load(settings); + return new MiscSettingsViewModel(s); } public void Save(XElement root) { - var s = (MiscSettings)this.DataContext; - - var section = new XElement("MiscSettings"); - section.SetAttributeValue(nameof(s.AllowMultipleInstances), s.AllowMultipleInstances); - section.SetAttributeValue(nameof(s.LoadPreviousAssemblies), s.LoadPreviousAssemblies); - - XElement existingElement = root.Element("MiscSettings"); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); + var s = (MiscSettingsViewModel)this.DataContext; + IMiscSettings.Save(root, s); currentMiscSettings = null; // invalidate cached settings } public void LoadDefaults() { - currentMiscSettings = new MiscSettings(); + currentMiscSettings = new MiscSettingsViewModel(MiscSettings.Load(ILSpySettings.Load())); this.DataContext = currentMiscSettings; } } diff --git a/ILSpy/Options/MiscSettings.cs b/ILSpy/Options/MiscSettingsViewModel.cs similarity index 94% rename from ILSpy/Options/MiscSettings.cs rename to ILSpy/Options/MiscSettingsViewModel.cs index 0414d6ad8e..2ab6bb2553 100644 --- a/ILSpy/Options/MiscSettings.cs +++ b/ILSpy/Options/MiscSettingsViewModel.cs @@ -21,23 +21,26 @@ using System.IO; using System.Reflection; using System.Runtime.CompilerServices; -using System.Security.Principal; using System.Windows; using System.Windows.Input; using ICSharpCode.ILSpy.Commands; +using ICSharpCode.ILSpyX.Settings; using Microsoft.Win32; namespace ICSharpCode.ILSpy.Options { - public class MiscSettings : INotifyPropertyChanged + public class MiscSettingsViewModel : IMiscSettings, INotifyPropertyChanged { bool allowMultipleInstances; bool loadPreviousAssemblies = true; - public MiscSettings() + public MiscSettingsViewModel(MiscSettings s) { + AllowMultipleInstances = s.AllowMultipleInstances; + LoadPreviousAssemblies = s.LoadPreviousAssemblies; + AddRemoveShellIntegrationCommand = new DelegateCommand(AddRemoveShellIntegration); } diff --git a/ILSpy/Options/OptionsDialog.xaml.cs b/ILSpy/Options/OptionsDialog.xaml.cs index 262853e316..e93f08b287 100644 --- a/ILSpy/Options/OptionsDialog.xaml.cs +++ b/ILSpy/Options/OptionsDialog.xaml.cs @@ -26,6 +26,7 @@ using System.Xml.Linq; using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy.Options { diff --git a/ILSpy/Properties/AssemblyInfo.cs b/ILSpy/Properties/AssemblyInfo.cs index 38b4c55c9a..8e5ce25307 100644 --- a/ILSpy/Properties/AssemblyInfo.cs +++ b/ILSpy/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ [assembly: AssemblyDescription(".NET assembly inspector and decompiler")] [assembly: AssemblyCompany("ic#code")] [assembly: AssemblyProduct("ILSpy")] -[assembly: AssemblyCopyright("Copyright 2011-2022 AlphaSierraPapa for the SharpDevelop Team")] +[assembly: AssemblyCopyright("Copyright 2011-2023 AlphaSierraPapa for the SharpDevelop Team")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index a90649709c..ccc37662fe 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -567,15 +567,6 @@ public static string CultureLabel { } } - /// - /// Looks up a localized string similar to Dark Mode. - /// - public static string DarkMode { - get { - return ResourceManager.GetString("DarkMode", resourceCulture); - } - } - /// /// Looks up a localized string similar to DEBUG -- Decompile All. /// @@ -1082,15 +1073,6 @@ public static string DecompilerSettings_IsUnmanagedAttributeOnTypeParametersShou } } - /// - /// Looks up a localized string similar to 'scoped' lifetime annotation. - /// - public static string DecompilerSettings_LifetimeAnnotations { - get { - return ResourceManager.GetString("DecompilerSettings.LifetimeAnnotations", resourceCulture); - } - } - /// /// Looks up a localized string similar to Use nint/nuint types. /// @@ -1118,6 +1100,15 @@ public static string DecompilerSettings_NullPropagation { } } + /// + /// Looks up a localized string similar to Treat (U)IntPtr as n(u)int. + /// + public static string DecompilerSettings_NumericIntPtr { + get { + return ResourceManager.GetString("DecompilerSettings.NumericIntPtr", resourceCulture); + } + } + /// /// Looks up a localized string similar to Object/collection initializer expressions. /// @@ -1226,6 +1217,24 @@ public static string DecompilerSettings_RemoveOptionalArgumentsIfPossible { } } + /// + /// Looks up a localized string similar to Required members. + /// + public static string DecompilerSettings_RequiredMembers { + get { + return ResourceManager.GetString("DecompilerSettings.RequiredMembers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 'scoped' lifetime annotation. + /// + public static string DecompilerSettings_ScopedRef { + get { + return ResourceManager.GetString("DecompilerSettings.ScopedRef", resourceCulture); + } + } + /// /// Looks up a localized string similar to Separate local variable declarations and initializers (int x = 5; -> int x; x = 5;), if possible. /// @@ -1271,6 +1280,15 @@ public static string DecompilerSettings_SwitchExpressions { } } + /// + /// Looks up a localized string similar to Unsigned right shift (>>>). + /// + public static string DecompilerSettings_UnsignedRightShift { + get { + return ResourceManager.GetString("DecompilerSettings.UnsignedRightShift", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use discards. /// @@ -1469,6 +1487,15 @@ public static string DecompilerSettings_UseVariableNamesFromDebugSymbolsIfAvaila } } + /// + /// Looks up a localized string similar to UTF-8 string literals. + /// + public static string DecompilerSettings_Utf8StringLiterals { + get { + return ResourceManager.GetString("DecompilerSettings.Utf8StringLiterals", resourceCulture); + } + } + /// /// Looks up a localized string similar to VB-specific options. /// @@ -1559,6 +1586,15 @@ public static string DisplaySettingsPanel_Font { } } + /// + /// Looks up a localized string similar to Theme:. + /// + public static string DisplaySettingsPanel_Theme { + get { + return ResourceManager.GetString("DisplaySettingsPanel_Theme", resourceCulture); + } + } + /// /// Looks up a localized string similar to Download. /// @@ -1865,6 +1901,15 @@ public static string ManageAssemblyLists { } } + /// + /// Looks up a localized string similar to Metadata. + /// + public static string Metadata { + get { + return ResourceManager.GetString("Metadata", resourceCulture); + } + } + /// /// Looks up a localized string similar to Misc. /// @@ -2637,6 +2682,15 @@ public static string TabSize { } } + /// + /// Looks up a localized string similar to Theme. + /// + public static string Theme { + get { + return ResourceManager.GetString("Theme", resourceCulture); + } + } + /// /// Looks up a localized string similar to Toggle All Folding. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index a3b47c1e25..af3e6bfdc6 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -222,9 +222,6 @@ Are you sure you want to continue? DEBUG -- Dump PDB as XML - - Dark Mode - Debug Steps @@ -384,9 +381,6 @@ Are you sure you want to continue? IsUnmanagedAttribute on type parameters should be replaced with 'unmanaged' constraints - - 'scoped' lifetime annotation - Use nint/nuint types @@ -396,6 +390,9 @@ Are you sure you want to continue? Nullable reference types + + Treat (U)IntPtr as n(u)int + Object/collection initializer expressions @@ -432,6 +429,12 @@ Are you sure you want to continue? Remove optional arguments, if possible + + Required members + + + 'scoped' lifetime annotation + Separate local variable declarations and initializers (int x = 5; -> int x; x = 5;), if possible @@ -447,6 +450,9 @@ Are you sure you want to continue? Switch expressions + + Unsigned right shift (>>>) + Use discards @@ -513,6 +519,9 @@ Are you sure you want to continue? Use variable names from debug symbols, if available + + UTF-8 string literals + VB-specific options @@ -540,6 +549,9 @@ Are you sure you want to continue? Font: + + Theme: + Download @@ -642,6 +654,9 @@ Are you sure you want to continue? Manage assembly _lists... + + Metadata + Misc @@ -904,6 +919,9 @@ Do you want to continue? Tab size: + + Theme + Toggle All Folding diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx index bf8f4d400e..0304ce4468 100644 --- a/ILSpy/Properties/Resources.zh-Hans.resx +++ b/ILSpy/Properties/Resources.zh-Hans.resx @@ -202,7 +202,7 @@ 复制完全限定名称 - 无法使用SDK-style项目格式, 因为发现有不兼容目标框架别名 + 无法使用SDK-style项目格式,因为发现有不兼容目标框架别名。 创建 @@ -222,9 +222,6 @@ 调试 -- PDB 转储为 XML - - 深色 - 调试步骤 @@ -238,7 +235,7 @@ 反编译视图选项 - 反编译已取消 + 反编译已取消。 反编译 @@ -270,6 +267,9 @@ 始终使用大括号 + + 总是使用 "global::" 完全限定命名空间 + 在已加载的程序集上应用 Windows 运行时投影 @@ -381,6 +381,9 @@ 类型参数上的 IsUnmanagedAttribute 应替换为 unmanaged 约束 + + 'scoped' 生命周期注解关键字 + 使用 nint/nuint 类型 @@ -396,6 +399,9 @@ 其他 + + 使用方法参数非空校验 + 使用模式匹配表达式 @@ -411,6 +417,9 @@ 记录 + + 记录结构 + 删除死代码和无副作用的代码(请谨慎使用) @@ -420,6 +429,9 @@ 如果可能,删除可选参数 + + Required 必要成员 + 如果可能,分离局部变量的声明与初始化(int x = 5; -> int x; x = 5;) @@ -528,6 +540,9 @@ 字体: + + 主题: + 下载 @@ -634,7 +649,7 @@ 杂项 - .Net 版本 + .NET 版本 名称 @@ -677,7 +692,7 @@ 打开(_O) - 操作已取消 + 操作已取消。 选项 @@ -890,6 +905,9 @@ Tab 长度: + + 主题 + 切换所有折叠 @@ -914,6 +932,9 @@ 使用 logic 语法糖 + + 使用嵌套的命名空间结构 + 使用 Tab 替代空格 @@ -965,6 +986,12 @@ 添加到主列表(_A) + + 分析器(_A) + + + 程序集(_A) + 检查更新(_C) diff --git a/ILSpy/Search/SearchPane.cs b/ILSpy/Search/SearchPane.cs index 7c58bf026c..0bd418539c 100644 --- a/ILSpy/Search/SearchPane.cs +++ b/ILSpy/Search/SearchPane.cs @@ -79,27 +79,13 @@ public SearchPane() ContextMenuProvider.Add(listBox); MainWindow.Instance.CurrentAssemblyListChanged += MainWindow_Instance_CurrentAssemblyListChanged; - DockWorkspace.Instance.PropertyChanged += DockWorkspace_PropertyChanged; filterSettings = MainWindow.Instance.SessionSettings.FilterSettings; - filterSettings.PropertyChanged += FilterSettings_PropertyChanged; CompositionTarget.Rendering += UpdateResults; // This starts empty search right away, so do at the end (we're still in ctor) searchModeComboBox.SelectedIndex = (int)MainWindow.Instance.SessionSettings.SelectedSearchMode; } - private void DockWorkspace_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - switch (e.PropertyName) - { - case nameof(DockWorkspace.Instance.ActiveTabPage): - filterSettings.PropertyChanged -= FilterSettings_PropertyChanged; - filterSettings = DockWorkspace.Instance.ActiveTabPage.FilterSettings; - filterSettings.PropertyChanged += FilterSettings_PropertyChanged; - break; - } - } - void MainWindow_Instance_CurrentAssemblyListChanged(object sender, NotifyCollectionChangedEventArgs e) { if (IsVisible) @@ -113,10 +99,9 @@ void MainWindow_Instance_CurrentAssemblyListChanged(object sender, NotifyCollect } } - void FilterSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) + internal void UpdateFilter(FilterSettings settings) { - if (e.PropertyName != nameof(FilterSettings.ShowApiLevel)) - return; + this.filterSettings = settings; if (IsVisible) { @@ -261,7 +246,7 @@ async void StartSearch(string searchTerm) searchProgressBar.IsIndeterminate = true; startedSearch = new RunningSearch(await mainWindow.CurrentAssemblyList.GetAllAssemblies(), searchTerm, (SearchMode)searchModeComboBox.SelectedIndex, mainWindow.CurrentLanguage, - mainWindow.SessionSettings.FilterSettings.ShowApiLevel); + filterSettings.ShowApiLevel); currentSearch = startedSearch; await startedSearch.Run(); @@ -350,6 +335,14 @@ SearchRequest Parse(string input) { searchTerm = NativeMethods.CommandLineToArgumentArray(searchTerm)[0]; } + else + { + // if searchTerm is only "@" or "prefix:", + // then we do not interpret it as prefix, but as searchTerm. + searchTerm = part; + prefix = null; + prefixLength = -1; + } if (prefix == null || prefix.Length <= 2) { diff --git a/ILSpy/SessionSettings.cs b/ILSpy/SessionSettings.cs index f02e56ee98..ad53d408cd 100644 --- a/ILSpy/SessionSettings.cs +++ b/ILSpy/SessionSettings.cs @@ -29,6 +29,7 @@ using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpyX.Search; +using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy { @@ -63,7 +64,7 @@ public SessionSettings(ILSpySettings spySettings) this.TopPaneSplitterPosition = FromString((string)doc.Element("TopPaneSplitterPosition"), 0.3); this.BottomPaneSplitterPosition = FromString((string)doc.Element("BottomPaneSplitterPosition"), 0.3); this.SelectedSearchMode = FromString((string)doc.Element("SelectedSearchMode"), SearchMode.TypeAndMember); - this.IsDarkMode = FromString((string)doc.Element(nameof(IsDarkMode)), false); + this.Theme = FromString((string)doc.Element(nameof(Theme)), ThemeManager.Current.DefaultTheme); string currentCulture = (string)doc.Element(nameof(CurrentCulture)); this.CurrentCulture = string.IsNullOrEmpty(currentCulture) ? null : currentCulture; @@ -80,10 +81,10 @@ void OnPropertyChanged([CallerMemberName] string propertyName = null) public FilterSettings FilterSettings { get; internal set; } public SearchMode SelectedSearchMode { get; set; } - public bool IsDarkMode { - get => ThemeManager.Current.IsDarkMode; + public string Theme { + get => ThemeManager.Current.Theme; set { - ThemeManager.Current.IsDarkMode = value; + ThemeManager.Current.Theme = value; OnPropertyChanged(); } } @@ -126,7 +127,7 @@ public string ActiveAssemblyList { public DockLayoutSettings DockLayout { get; private set; } - public void Save() + public XElement ToXml() { XElement doc = new XElement("SessionSettings"); doc.Add(this.FilterSettings.SaveAsXml()); @@ -148,7 +149,7 @@ public void Save() doc.Add(new XElement("TopPaneSplitterPosition", ToString(this.TopPaneSplitterPosition))); doc.Add(new XElement("BottomPaneSplitterPosition", ToString(this.BottomPaneSplitterPosition))); doc.Add(new XElement("SelectedSearchMode", ToString(this.SelectedSearchMode))); - doc.Add(new XElement(nameof(IsDarkMode), ToString(this.IsDarkMode))); + doc.Add(new XElement(nameof(Theme), ToString(this.Theme))); if (this.CurrentCulture != null) { doc.Add(new XElement(nameof(CurrentCulture), this.CurrentCulture)); @@ -160,7 +161,12 @@ public void Save() dockLayoutElement.Add(DockLayout.SaveAsXml()); } doc.Add(dockLayoutElement); + return doc; + } + public void Save() + { + var doc = ToXml(); ILSpySettings.SaveSettings(doc); } diff --git a/ILSpy/SingleInstanceHandling.cs b/ILSpy/SingleInstanceHandling.cs new file mode 100644 index 0000000000..5a59f1ee25 --- /dev/null +++ b/ILSpy/SingleInstanceHandling.cs @@ -0,0 +1,154 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; + +namespace ICSharpCode.ILSpy +{ + internal static class SingleInstanceHandling + { + internal static Mutex SingleInstanceMutex; + + internal static void ForceSingleInstance(IEnumerable cmdArgs) + { + bool isFirst; + try + { + SingleInstanceMutex = new Mutex(initiallyOwned: true, @"Local\ILSpyInstance", out isFirst); + } + catch (WaitHandleCannotBeOpenedException) + { + isFirst = true; + } + if (!isFirst) + { + try + { + SingleInstanceMutex.WaitOne(10000); + } + catch (AbandonedMutexException) + { + // continue, there is no concurrent start happening. + } + } + cmdArgs = cmdArgs.Select(FullyQualifyPath); + string message = string.Join(Environment.NewLine, cmdArgs); + if (SendToPreviousInstance("ILSpy:\r\n" + message, !App.CommandLineArguments.NoActivate)) + { + ReleaseSingleInstanceMutex(); + Environment.Exit(0); + } + } + + internal static string FullyQualifyPath(string argument) + { + // Fully qualify the paths before passing them to another process, + // because that process might use a different current directory. + if (string.IsNullOrEmpty(argument) || argument[0] == '/') + return argument; + try + { + return Path.Combine(Environment.CurrentDirectory, argument); + } + catch (ArgumentException) + { + return argument; + } + } + + internal static void ReleaseSingleInstanceMutex() + { + var mutex = SingleInstanceMutex; + SingleInstanceMutex = null; + if (mutex == null) + { + return; + } + using (mutex) + { + mutex.ReleaseMutex(); + } + } + + #region Pass Command Line Arguments to previous instance + internal static bool SendToPreviousInstance(string message, bool activate) + { + string ownProcessName; + using (var ownProcess = Process.GetCurrentProcess()) + { + ownProcessName = ownProcess.ProcessName; + } + + bool success = false; + NativeMethods.EnumWindows( + (hWnd, lParam) => { + string windowTitle = NativeMethods.GetWindowText(hWnd, 100); + if (windowTitle.StartsWith("ILSpy", StringComparison.Ordinal)) + { + string processName = NativeMethods.GetProcessNameFromWindow(hWnd); + Debug.WriteLine("Found {0:x4}: '{1}' in '{2}'", hWnd, windowTitle, processName); + if (string.Equals(processName, ownProcessName, StringComparison.OrdinalIgnoreCase)) + { + IntPtr result = Send(hWnd, message); + Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); + if (result == (IntPtr)1) + { + if (activate) + NativeMethods.SetForegroundWindow(hWnd); + success = true; + return false; // stop enumeration + } + } + } + return true; // continue enumeration + }, IntPtr.Zero); + return success; + } + + unsafe static IntPtr Send(IntPtr hWnd, string message) + { + const uint SMTO_NORMAL = 0; + + CopyDataStruct lParam; + lParam.Padding = IntPtr.Zero; + lParam.Size = message.Length * 2; + fixed (char* buffer = message) + { + lParam.Buffer = (IntPtr)buffer; + IntPtr result; + // SendMessage with 3s timeout (e.g. when the target process is stopped in the debugger) + if (NativeMethods.SendMessageTimeout( + hWnd, NativeMethods.WM_COPYDATA, IntPtr.Zero, ref lParam, + SMTO_NORMAL, 3000, out result) != IntPtr.Zero) + { + return result; + } + else + { + return IntPtr.Zero; + } + } + } + #endregion + } +} diff --git a/ILSpy/TextView/Asm-Mode-Dark.xshd b/ILSpy/TextView/Asm-Mode-Dark.xshd deleted file mode 100644 index 9953acbf07..0000000000 --- a/ILSpy/TextView/Asm-Mode-Dark.xshd +++ /dev/null @@ -1,1211 +0,0 @@ - - - - - - - - - - - - - - aaa - aad - aam - aas - adc - add - and - call - cbw - cdqe - clc - cld - cli - cmc - cmp - cmps - cmpsb - cmpsw - cwd - daa - das - dec - div - esc - hlt - idiv - imul - in - inc - int - into - iret - ja - jae - jb - jbe - jc - jcxz - je - jg - jge - jl - jle - jmp - jna - jnae - jnb - jnbe - jnc - jne - jng - jnge - jnl - jnle - jno - jnp - jns - jnz - jo - jp - jpe - jpo - js - jz - lahf - lds - lea - les - lods - lodsb - lodsw - loop - loope - loopew - loopne - loopnew - loopnz - loopnzw - loopw - loopz - loopzw - mov - movabs - movs - movsb - movsw - mul - neg - nop - not - or - out - pop - popf - push - pushf - rcl - rcr - ret - retf - retn - rol - ror - sahf - sal - sar - sbb - scas - scasb - scasw - shl - shr - stc - std - sti - stos - stosb - stosw - sub - test - wait - xchg - xlat - xlatb - xor - bound - enter - ins - insb - insw - leave - outs - outsb - outsw - popa - pusha - pushw - arpl - lar - lsl - sgdt - sidt - sldt - smsw - str - verr - verw - clts - lgdt - lidt - lldt - lmsw - ltr - bsf - bsr - bt - btc - btr - bts - cdq - cmpsd - cwde - insd - iretd - iretdf - iretf - jecxz - lfs - lgs - lodsd - loopd - looped - loopned - loopnzd - loopzd - lss - movsd - movsx - movsxd - movzx - outsd - popad - popfd - pushad - pushd - pushfd - scasd - seta - setae - setb - setbe - setc - sete - setg - setge - setl - setle - setna - setnae - setnb - setnbe - setnc - setne - setng - setnge - setnl - setnle - setno - setnp - setns - setnz - seto - setp - setpe - setpo - sets - setz - shld - shrd - stosd - bswap - cmpxchg - invd - invlpg - wbinvd - xadd - lock - rep - repe - repne - repnz - repz - cflush - cpuid - emms - femms - cmovo - cmovno - cmovb - cmovc - cmovnae - cmovae - cmovnb - cmovnc - cmove - cmovz - cmovne - cmovnz - cmovbe - cmovna - cmova - cmovnbe - cmovs - cmovns - cmovp - cmovpe - cmovnp - cmovpo - cmovl - cmovnge - cmovge - cmovnl - cmovle - cmovng - cmovg - cmovnle - cmpxchg486 - cmpxchg8b - loadall - loadall286 - ibts - icebp - int1 - int3 - int01 - int03 - iretw - popaw - popfw - pushaw - pushfw - rdmsr - rdpmc - rdshr - rdtsc - rsdc - rsldt - rsm - rsts - salc - smi - smint - smintold - svdc - svldt - svts - syscall - sysenter - sysexit - sysret - ud0 - ud1 - ud2 - umov - xbts - wrmsr - wrshr - - - f2xm1 - fabs - fadd - faddp - fbld - fbstp - fchs - fclex - fcom - fcomp - fcompp - fdecstp - fdisi - fdiv - fdivp - fdivr - fdivrp - feni - ffree - fiadd - ficom - ficomp - fidiv - fidivr - fild - fimul - fincstp - finit - fist - fistp - fisub - fisubr - fld - fld1 - fldcw - fldenv - fldenvw - fldl2e - fldl2t - fldlg2 - fldln2 - fldpi - fldz - fmul - fmulp - fnclex - fndisi - fneni - fninit - fnop - fnsave - fnsavew - fnstcw - fnstenv - fnstenvw - fnstsw - fpatan - fprem - fptan - frndint - frstor - frstorw - fsave - fsavew - fscale - fsqrt - fst - fstcw - fstenv - fstenvw - fstp - fstsw - fsub - fsubp - fsubr - fsubrp - ftst - fwait - fxam - fxch - fxtract - fyl2x - fyl2xp1 - fsetpm - fcos - fldenvd - fnsaved - fnstenvd - fprem1 - frstord - fsaved - fsin - fsincos - fstenvd - fucom - fucomp - fucompp - fcomi - fcomip - ffreep - fcmovb - fcmove - fcmovbe - fcmovu - fcmovnb - fcmovne - fcmovnbe - fcmovnu - - - ah - al - ax - bh - bl - bp - bx - ch - cl - cr0 - cr2 - cr3 - cr4 - cs - cx - dh - di - dl - dr0 - dr1 - dr2 - dr3 - dr6 - dr7 - ds - dx - eax - ebp - ebx - ecx - edi - edx - es - esi - esp - fs - gs - rax - rbx - rcx - rdx - rdi - rsi - rbp - rsp - r8 - r9 - r10 - r11 - r12 - r13 - r14 - r15 - r8d - r9d - r10d - r11d - r12d - r13d - r14d - r15d - r8w - r9w - r10w - r11w - r12w - r13w - r14w - r15w - r8b - r9b - r10b - r11b - r12b - r13b - r14b - r15b - si - sp - ss - st - tr3 - tr4 - tr5 - tr6 - tr7 - st0 - st1 - st2 - st3 - st4 - st5 - st6 - st7 - mm0 - mm1 - mm2 - mm3 - mm4 - mm5 - mm6 - mm7 - xmm0 - xmm1 - xmm2 - xmm3 - xmm4 - xmm5 - xmm6 - xmm7 - xmm8 - xmm9 - xmm10 - xmm11 - xmm12 - xmm13 - xmm14 - xmm15 - - - .186 - .286 - .286c - .286p - .287 - .386 - .386c - .386p - .387 - .486 - .486p - .8086 - .8087 - .alpha - .break - .code - .const - .continue - .cref - .data - .data? - .dosseg - .else - .elseif - .endif - .endw - .err - .err1 - .err2 - .errb - .errdef - .errdif - .errdifi - .erre - .erridn - .erridni - .errnb - .errndef - .errnz - .exit - .fardata - .fardata? - .if - .lall - .lfcond - .list - .listall - .listif - .listmacro - .listmacroall - .model - .no87 - .nocref - .nolist - .nolistif - .nolistmacro - .radix - .repeat - .sall - .seq - .sfcond - .stack - .startup - .tfcond - .type - .until - .untilcxz - .while - .xall - .xcref - .xlist - alias - align - assume - catstr - comm - comment - db - dd - df - dosseg - dq - dt - dup - dw - echo - else - elseif - elseif1 - elseif2 - elseifb - elseifdef - elseifdif - elseifdifi - elseife - elseifidn - elseifidni - elseifnb - elseifndef - end - endif - endm - endp - ends - eq - equ - even - exitm - extern - externdef - extrn - for - forc - ge - goto - group - gt - high - highword - if - if1 - if2 - ifb - ifdef - ifdif - ifdifi - ife - ifidn - ifidni - ifnb - ifndef - include - includelib - instr - invoke - irp - irpc - label - le - length - lengthof - local - low - lowword - lroffset - lt - macro - mask - mod - .msfloat - name - ne - offset - opattr - option - org - %out - page - popcontext - proc - proto - ptr - public - purge - pushcontext - record - repeat - rept - seg - segment - short - size - sizeof - sizestr - struc - struct - substr - subtitle - subttl - textequ - this - title - type - typedef - union - while - width - resb - resw - resd - resq - rest - incbin - times - %define - %idefine - %xdefine - %xidefine - %undef - %assign - %iassign - %strlen - %substr - %macro - %imacro - %endmacro - %rotate - %if - %elif - %else - %endif - %ifdef - %ifndef - %elifdef - %elifndef - %ifmacro - %ifnmacro - %elifmacro - %elifnmacro - %ifctk - %ifnctk - %elifctk - %elifnctk - %ifidn - %ifnidn - %elifidn - %elifnidn - %ifidni - %ifnidni - %elifidni - %elifnidni - %ifid - %ifnid - %elifid - %elifnid - %ifstr - %ifnstr - %elifstr - %elifnstr - %ifnum - %ifnnum - %elifnum - %elifnnum - %error - %rep - %endrep - %exitrep - %include - %push - %pop - %repl - endstruc - istruc - at - iend - alignb - %arg - %stacksize - %local - %line - bits - use16 - use32 - section - absolute - global - common - cpu - import - export - - - $ - ? - @b - @f - addr - basic - byte - c - carry? - dword - far - far16 - fortran - fword - near - near16 - overflow? - parity? - pascal - qword - real4 - real8 - real10 - sbyte - sdword - sign? - stdcall - sword - syscall - tbyte - vararg - word - zero? - flat - near32 - far32 - abs - all - assumes - at - casemap - common - compact - cpu - dotname - emulator - epilogue - error - export - expr16 - expr32 - farstack - forceframe - huge - language - large - listing - ljmp - loadds - m510 - medium - memory - nearstack - nodotname - noemulator - nokeyword - noljmp - nom510 - none - nonunique - nooldmacros - nooldstructs - noreadonly - noscoped - nosignextend - nothing - notpublic - oldmacros - oldstructs - os_dos - para - private - prologue - radix - readonly - req - scoped - setif2 - smallstack - tiny - use16 - use32 - uses - a16 - a32 - o16 - o32 - nosplit - $$ - seq - wrt - small - .text - .data - .bss - %0 - %1 - %2 - %3 - %4 - %5 - %6 - %7 - %8 - %9 - - - addpd - addps - addsd - addss - andpd - andps - andnpd - andnps - cmpeqpd - cmpltpd - cmplepd - cmpunordpd - cmpnepd - cmpnltpd - cmpnlepd - cmpordpd - cmpeqps - cmpltps - cmpleps - cmpunordps - cmpneps - cmpnltps - cmpnleps - cmpordps - cmpeqsd - cmpltsd - cmplesd - cmpunordsd - cmpnesd - cmpnltsd - cmpnlesd - cmpordsd - cmpeqss - cmpltss - cmpless - cmpunordss - cmpness - cmpnltss - cmpnless - cmpordss - comisd - comiss - cvtdq2pd - cvtdq2ps - cvtpd2dq - cvtpd2pi - cvtpd2ps - cvtpi2pd - cvtpi2ps - cvtps2dq - cvtps2pd - cvtps2pi - cvtss2sd - cvtss2si - cvtsd2si - cvtsd2ss - cvtsi2sd - cvtsi2ss - cvttpd2dq - cvttpd2pi - cvttps2dq - cvttps2pi - cvttsd2si - cvttss2si - divpd - divps - divsd - divss - fxrstor - fxsave - ldmxscr - lfence - mfence - maskmovdqu - maskmovdq - maxpd - maxps - paxsd - maxss - minpd - minps - minsd - minss - movapd - movaps - movdq2q - movdqa - movdqu - movhlps - movhpd - movhps - movd - movq - movlhps - movlpd - movlps - movmskpd - movmskps - movntdq - movnti - movntpd - movntps - movntq - movq2dq - movsd - movss - movupd - movups - mulpd - mulps - mulsd - mulss - orpd - orps - packssdw - packsswb - packuswb - paddb - paddsb - paddw - paddsw - paddd - paddsiw - paddq - paddusb - paddusw - pand - pandn - pause - paveb - pavgb - pavgw - pavgusb - pdistib - pextrw - pcmpeqb - pcmpeqw - pcmpeqd - pcmpgtb - pcmpgtw - pcmpgtd - pf2id - pf2iw - pfacc - pfadd - pfcmpeq - pfcmpge - pfcmpgt - pfmax - pfmin - pfmul - pmachriw - pmaddwd - pmagw - pmaxsw - pmaxub - pminsw - pminub - pmovmskb - pmulhrwc - pmulhriw - pmulhrwa - pmulhuw - pmulhw - pmullw - pmuludq - pmvzb - pmvnzb - pmvlzb - pmvgezb - pfnacc - pfpnacc - por - prefetch - prefetchw - prefetchnta - prefetcht0 - prefetcht1 - prefetcht2 - pfrcp - pfrcpit1 - pfrcpit2 - pfrsqit1 - pfrsqrt - pfsub - pfsubr - pi2fd - pinsrw - psadbw - pshufd - pshufhw - pshuflw - pshufw - psllw - pslld - psllq - pslldq - psraw - psrad - psrlw - psrld - psrlq - psrldq - psubb - psubw - psubd - psubq - psubsb - psubsw - psubusb - psubusw - psubsiw - pswapd - punpckhbw - punpckhwd - punpckhdq - punpckhqdq - punpcklbw - punpcklwd - punpckldq - punpcklqdq - pxor - rcpps - rcpss - rsqrtps - rsqrtss - sfence - shufpd - shufps - sqrtpd - sqrtps - sqrtsd - sqrtss - stmxcsr - subpd - subps - subsd - subss - ucomisd - ucomiss - unpckhpd - unpckhps - unpcklpd - unpcklps - xorpd - xorps - - - ; - - - \b(0[xXhH])?[0-9a-fA-F_`]+[h]? # hex number - | - ( \b\d+(\.[0-9]+)? #number with optional floating point - | \.[0-9]+ #or just starting with floating point - ) - ([eE][+-]?[0-9]+)? # optional exponent - - - - - TODO - FIXME - - - HACK - UNDONE - - - \ No newline at end of file diff --git a/ILSpy/TextView/Asm-Mode.xshd b/ILSpy/TextView/Asm-Mode.xshd index 9bf688b83f..8d4fb8c2f8 100644 --- a/ILSpy/TextView/Asm-Mode.xshd +++ b/ILSpy/TextView/Asm-Mode.xshd @@ -8,7 +8,8 @@ - + + aaa @@ -1189,6 +1190,9 @@ ; + + ^ \s* [0-9A-F]+ \s+ [0-9A-F]+ + \b(0[xXhH])?[0-9a-fA-F_`]+[h]? # hex number | diff --git a/ILSpy/TextView/BracketHighlightRenderer.cs b/ILSpy/TextView/BracketHighlightRenderer.cs index 9a61bce318..4b958d407a 100644 --- a/ILSpy/TextView/BracketHighlightRenderer.cs +++ b/ILSpy/TextView/BracketHighlightRenderer.cs @@ -114,6 +114,8 @@ public void Draw(ICSharpCode.AvalonEdit.Rendering.TextView textView, DrawingCont BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder(); builder.CornerRadius = 1; + builder.AlignToWholePixels = true; + builder.BorderThickness = borderPen?.Thickness ?? 0; builder.AddSegment(textView, new TextSegment() { StartOffset = result.OpeningBracketOffset, Length = result.OpeningBracketLength }); builder.CloseFigure(); // prevent connecting the two segments diff --git a/ILSpy/TextView/CSharp-Mode-Dark.xshd b/ILSpy/TextView/CSharp-Mode-Dark.xshd deleted file mode 100644 index 7d7c7aa732..0000000000 --- a/ILSpy/TextView/CSharp-Mode-Dark.xshd +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TODO - FIXME - - - HACK - UNDONE - - - - - - - \# - - - - (define|undef|if|elif|else|endif|line)\b - - - - // - - - - - - (region|endregion|error|warning|pragma)\b - - - - - - - ///(?!/) - - - - - - - - // - - - - /\* - \*/ - - - - " - " - - - - - - - - ' - ' - - - - - - - - @" - " - - - - - - - - \$" - " - - - - - - - - - - - - \b0[xX][0-9a-fA-F]+ # hex number - | - ( \b\d+(\.[0-9]+)? #number with optional floating point - | \.[0-9]+ #or just starting with floating point - ) - ([eE][+-]?[0-9]+)? # optional exponent - - - diff --git a/ILSpy/TextView/CSharp-Mode.xshd b/ILSpy/TextView/CSharp-Mode.xshd index 5f5e0a2fde..d2db085347 100644 --- a/ILSpy/TextView/CSharp-Mode.xshd +++ b/ILSpy/TextView/CSharp-Mode.xshd @@ -39,8 +39,18 @@ + + + + + + + + + + diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 02c00bfda2..fc8af11dfd 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -70,7 +70,7 @@ namespace ICSharpCode.ILSpy.TextView /// Manages the TextEditor showing the decompiled code. /// Contains all the threading logic that makes the decompiler work in the background. /// - public sealed partial class DecompilerTextView : UserControl, IDisposable, IHaveState + public sealed partial class DecompilerTextView : UserControl, IDisposable, IHaveState, IProgress { readonly ReferenceElementGenerator referenceElementGenerator; readonly UIElementGenerator uiElementGenerator; @@ -127,6 +127,7 @@ public DecompilerTextView() // SearchPanel SearchPanel searchPanel = SearchPanel.Install(textEditor.TextArea); searchPanel.RegisterCommands(Application.Current.MainWindow.CommandBindings); + searchPanel.SetResourceReference(SearchPanel.MarkerBrushProperty, ResourceKeys.SearchResultBackgroundBrush); searchPanel.Loaded += (_, _) => { // HACK: fix the hardcoded but misaligned margin of the search text box. var textBox = searchPanel.VisualDescendants().OfType().FirstOrDefault(); @@ -139,13 +140,11 @@ public DecompilerTextView() ShowLineMargin(); SetHighlightCurrentLine(); - // add marker service & margin - textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService); - textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService); - ContextMenuProvider.Add(this); textEditor.TextArea.TextView.SetResourceReference(ICSharpCode.AvalonEdit.Rendering.TextView.LinkTextForegroundBrushProperty, ResourceKeys.LinkTextForegroundBrush); + textEditor.TextArea.TextView.SetResourceReference(ICSharpCode.AvalonEdit.Rendering.TextView.CurrentLineBackgroundProperty, ResourceKeys.CurrentLineBackgroundBrush); + textEditor.TextArea.TextView.SetResourceReference(ICSharpCode.AvalonEdit.Rendering.TextView.CurrentLineBorderProperty, ResourceKeys.CurrentLineBorderPen); this.DataContextChanged += DecompilerTextView_DataContextChanged; } @@ -174,11 +173,11 @@ void RemoveEditCommand(RoutedUICommand command) void CurrentDisplaySettings_PropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(DisplaySettings.ShowLineNumbers)) + if (e.PropertyName == nameof(DisplaySettingsViewModel.ShowLineNumbers)) { ShowLineMargin(); } - else if (e.PropertyName == nameof(DisplaySettings.HighlightCurrentLine)) + else if (e.PropertyName == nameof(DisplaySettingsViewModel.HighlightCurrentLine)) { SetHighlightCurrentLine(); } @@ -231,6 +230,8 @@ void TextViewMouseHover(object sender, MouseEventArgs e) { var popupPosition = GetPopupPosition(e); popupToolTip.Closed += ToolTipClosed; + popupToolTip.Placement = PlacementMode.Relative; + popupToolTip.PlacementTarget = this; popupToolTip.HorizontalOffset = popupPosition.X; popupToolTip.VerticalOffset = popupPosition.Y; popupToolTip.StaysOpen = true; // We will close it ourselves @@ -282,23 +283,19 @@ bool TryCloseExistingPopup(bool mouseClick) Point GetPopupPosition(MouseEventArgs mouseArgs) { Point mousePos = mouseArgs.GetPosition(this); - Point positionInPixels; // align Popup with line bottom TextViewPosition? logicalPos = textEditor.GetPositionFromPoint(mousePos); if (logicalPos.HasValue) { var textView = textEditor.TextArea.TextView; - positionInPixels = - textView.PointToScreen( - textView.GetVisualPosition(logicalPos.Value, VisualYPosition.LineBottom) - textView.ScrollOffset); - positionInPixels.X -= 4; + return textView.GetVisualPosition(logicalPos.Value, VisualYPosition.LineBottom) + - textView.ScrollOffset + + new Vector(-4, 0); } else { - positionInPixels = PointToScreen(mousePos + new Vector(-4, 6)); + return mousePos + new Vector(-4, 6); } - // use device independent units, because Popup Left/Top are in independent units - return positionInPixels.TransformFromDevice(this); } void TextViewMouseHoverStopped(object sender, MouseEventArgs e) @@ -540,18 +537,26 @@ void HighlightBrackets(object? sender, EventArgs e) #endregion #region RunWithCancellation - /// - /// Switches the GUI into "waiting" mode, then calls to create - /// the task. - /// When the task completes without being cancelled, the - /// callback is called on the GUI thread. - /// When the task is cancelled before completing, the callback is not called; and any result - /// of the task (including exceptions) are ignored. - /// - [Obsolete("RunWithCancellation(taskCreation).ContinueWith(taskCompleted) instead")] - public void RunWithCancellation(Func> taskCreation, Action> taskCompleted) + public void Report(DecompilationProgress value) { - RunWithCancellation(taskCreation).ContinueWith(taskCompleted, CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext()); + double v = (double)value.UnitsCompleted / value.TotalUnits; + Dispatcher.BeginInvoke(DispatcherPriority.Normal, delegate { + progressBar.IsIndeterminate = !double.IsFinite(v); + progressBar.Value = v * 100.0; + progressTitle.Text = !string.IsNullOrWhiteSpace(value.Title) ? value.Title : Properties.Resources.Decompiling; + progressText.Text = value.Status; + progressText.Visibility = !string.IsNullOrWhiteSpace(progressText.Text) ? Visibility.Visible : Visibility.Collapsed; + var taskBar = MainWindow.Instance.TaskbarItemInfo; + if (taskBar != null) + { + taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Normal; + taskBar.ProgressValue = v; + } + if (this.DataContext is TabPageModel model) + { + model.Title = progressTitle.Text; + } + }); } /// @@ -566,7 +571,10 @@ public Task RunWithCancellation(Func> taskCreat waitAdorner.Visibility = Visibility.Visible; // Work around a WPF bug by setting IsIndeterminate only while the progress bar is visible. // https://github.com/icsharpcode/ILSpy/issues/593 + progressTitle.Text = Properties.Resources.Decompiling; progressBar.IsIndeterminate = true; + progressText.Text = null; + progressText.Visibility = Visibility.Collapsed; waitAdorner.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.5)), FillBehavior.Stop)); var taskBar = MainWindow.Instance.TaskbarItemInfo; if (taskBar != null) @@ -605,6 +613,8 @@ public Task RunWithCancellation(Func> taskCreat currentCancellationTokenSource = null; waitAdorner.Visibility = Visibility.Collapsed; progressBar.IsIndeterminate = false; + progressText.Text = null; + progressText.Visibility = Visibility.Collapsed; var taskBar = MainWindow.Instance.TaskbarItemInfo; if (taskBar != null) { @@ -828,6 +838,7 @@ Task DoDecompile(DecompilationContext context, int outputLengthLimit) return RunWithCancellation( delegate (CancellationToken ct) { // creation of the background task context.Options.CancellationToken = ct; + context.Options.Progress = this; decompiledNodes = context.TreeNodes; return DecompileAsync(context, outputLengthLimit); }) @@ -1091,6 +1102,7 @@ Task SaveToDiskAsync(DecompilationContext context, string { bool originalProjectFormatSetting = context.Options.DecompilerSettings.UseSdkStyleProjectFormat; context.Options.EscapeInvalidIdentifiers = true; + context.Options.Progress = this; AvalonEditTextOutput output = new AvalonEditTextOutput { EnableHyperlinks = true, Title = string.Join(", ", context.TreeNodes.Select(n => n.Text)) @@ -1351,15 +1363,8 @@ public static void RegisterHighlighting( string[] extensions, string resourceName) { - if (ThemeManager.Current.IsDarkMode) - { - resourceName += "-Dark"; - } - - resourceName += ".xshd"; - Stream? resourceStream = typeof(DecompilerTextView).Assembly - .GetManifestResourceStream(typeof(DecompilerTextView), resourceName); + .GetManifestResourceStream(typeof(DecompilerTextView), resourceName + ".xshd"); if (resourceStream != null) { @@ -1369,7 +1374,9 @@ public static void RegisterHighlighting( using (resourceStream) using (XmlTextReader reader = new XmlTextReader(resourceStream)) { - return HighlightingLoader.Load(reader, manager); + var highlightingDefinition = HighlightingLoader.Load(reader, manager); + ThemeManager.Current.ApplyHighlightingColors(highlightingDefinition); + return highlightingDefinition; } }); } diff --git a/ILSpy/TextView/DecompilerTextView.xaml b/ILSpy/TextView/DecompilerTextView.xaml index 66960f102d..42786e3968 100644 --- a/ILSpy/TextView/DecompilerTextView.xaml +++ b/ILSpy/TextView/DecompilerTextView.xaml @@ -5,23 +5,40 @@ xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls" xmlns:local="clr-namespace:ICSharpCode.ILSpy.TextView" xmlns:ae="clr-namespace:ICSharpCode.AvalonEdit;assembly=ICSharpCode.AvalonEdit" + xmlns:editing="clr-namespace:ICSharpCode.AvalonEdit.Editing;assembly=ICSharpCode.AvalonEdit" xmlns:folding="clr-namespace:ICSharpCode.AvalonEdit.Folding;assembly=ICSharpCode.AvalonEdit" - xmlns:styles="urn:TomsToolbox.Wpf.Styles"> + xmlns:styles="urn:TomsToolbox.Wpf.Styles" + xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes"> + + folding:FoldingMargin.SelectedFoldingMarkerBrush="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}">