Browse Source

Merge

pull/3387/head
Christoph Wille 3 months ago
parent
commit
4887de3899
  1. 2
      .github/workflows/scorecard.yml
  2. 14
      Directory.Packages.props
  3. 45
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  4. 5
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  5. 6
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  6. 25
      ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs
  7. 62
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  8. 16
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs
  9. 16
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs
  10. 26
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs
  11. 116
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il
  12. 18
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs
  13. 4
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs
  14. 39
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
  15. 21
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs
  16. 12
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs
  17. 99
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
  18. 22
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs
  19. 28
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
  20. 16
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
  21. 18
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs
  22. 46
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs
  23. 12
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs
  24. 4
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  25. 5
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  26. 9
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  27. 51
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
  28. 13
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  29. 7
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
  30. 41
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
  31. 5
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
  32. 10
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  33. 105
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  34. 32
      ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs
  35. 18
      ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs
  36. 21
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs
  37. 13
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  38. 2
      ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
  39. 39
      ICSharpCode.Decompiler/DecompilerSettings.cs
  40. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  41. 1
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  42. 29
      ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
  43. 3
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  44. 4
      ICSharpCode.Decompiler/IL/ILReader.cs
  45. 5
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  46. 747
      ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
  47. 6
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  48. 2
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  49. 21
      ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
  50. 16
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
  51. 100
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
  52. 16
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  53. 14
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
  54. 5
      ICSharpCode.Decompiler/Output/IAmbience.cs
  55. 17
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
  56. 2
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs
  57. 39
      ICSharpCode.Decompiler/Util/DelegateComparer.cs
  58. 3
      ICSharpCode.Decompiler/Util/ResXResourceWriter.cs
  59. 2
      ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs
  60. 8
      ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
  61. 24
      ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs
  62. 12
      ILSpy/App.xaml
  63. 2
      ILSpy/App.xaml.cs
  64. 2
      ILSpy/AssemblyTree/AssemblyTreeModel.cs
  65. 2
      ILSpy/Commands/GeneratePdbContextMenuEntry.cs
  66. 2
      ILSpy/Commands/SelectPdbContextMenuEntry.cs
  67. 1
      ILSpy/Controls/MainToolBar.xaml
  68. 12
      ILSpy/Controls/ZoomScrollViewer.xaml
  69. 8
      ILSpy/ExtensionMethods.cs
  70. 1
      ILSpy/ILSpy.csproj
  71. 25
      ILSpy/Languages/CSharpLanguage.cs
  72. 2
      ILSpy/Languages/LanguageService.cs
  73. 3
      ILSpy/MainWindow.xaml.cs
  74. 2
      ILSpy/Metadata/CorTables/EventTableTreeNode.cs
  75. 2
      ILSpy/Metadata/CorTables/FieldTableTreeNode.cs
  76. 2
      ILSpy/Metadata/CorTables/MethodTableTreeNode.cs
  77. 2
      ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs
  78. 2
      ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
  79. 8
      ILSpy/Options/DisplaySettings.cs
  80. 1
      ILSpy/Options/DisplaySettingsPanel.xaml
  81. 37
      ILSpy/Properties/Resources.Designer.cs
  82. 9
      ILSpy/Properties/Resources.resx
  83. 6
      ILSpy/Properties/Resources.zh-Hans.resx
  84. 10
      ILSpy/Search/SearchPane.xaml.cs
  85. 6
      ILSpy/Search/SearchPaneModel.cs
  86. 38
      ILSpy/SolutionWriter.cs
  87. 25
      ILSpy/TextView/DecompilerTextView.cs
  88. 2
      ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
  89. 2
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  90. 2
      ILSpy/TreeNodes/EventTreeNode.cs
  91. 2
      ILSpy/TreeNodes/FieldTreeNode.cs
  92. 2
      ILSpy/TreeNodes/MethodTreeNode.cs
  93. 2
      ILSpy/TreeNodes/PropertyTreeNode.cs
  94. 2
      ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
  95. 2
      ILSpy/TreeNodes/TypeTreeNode.cs

2
.github/workflows/scorecard.yml

@ -29,7 +29,7 @@ jobs: @@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@v2.4.0 # https://github.com/marketplace/actions/ossf-scorecard-action
uses: ossf/scorecard-action@v2.4.1 # https://github.com/marketplace/actions/ossf-scorecard-action
with:
results_file: results.sarif
results_format: sarif

14
Directory.Packages.props

@ -15,8 +15,8 @@ @@ -15,8 +15,8 @@
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.13.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" />
<PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />
@ -45,11 +45,11 @@ @@ -45,11 +45,11 @@
<PackageVersion Include="System.Reflection.Metadata" Version="9.0.1" />
<PackageVersion Include="System.Resources.Extensions" Version="9.0.1" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0" />
<PackageVersion Include="TomsToolbox.Composition.MicrosoftExtensions" Version="2.21.0" />
<PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.20.0" />
<PackageVersion Include="TomsToolbox.Wpf.Composition.AttributedModel" Version="2.21.0" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.21.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="TomsToolbox.Composition.MicrosoftExtensions" Version="2.22.0" />
<PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.22.0" />
<PackageVersion Include="TomsToolbox.Wpf.Composition.AttributedModel" Version="2.22.0" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.22.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.3" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />

45
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -18,7 +18,6 @@ @@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
@ -330,6 +329,45 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -330,6 +329,45 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return tempFile;
}
const string nonEmbeddedAttributesSnippet = @"
using System;
#if !NET60
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute(string featureName)
{
}
}
internal class IsExternalInit
{
}
#endif
#if !NET70
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
internal sealed class RequiredMemberAttribute : Attribute
{
}
#endif
#if !NET60
}
#endif
";
static readonly Lazy<string> nonEmbeddedAttributesSnippetFile = new Lazy<string>(GetNonEmbeddedAttributesSnippetFile);
static string GetNonEmbeddedAttributesSnippetFile()
{
// Note: this leaks a temporary file, we're not attempting to delete it, because it is only one.
var tempFile = Path.GetTempFileName();
File.WriteAllText(tempFile, nonEmbeddedAttributesSnippet);
return tempFile;
}
public static List<string> GetPreprocessorSymbols(CompilerOptions flags)
{
var preprocessorSymbols = new List<string>();
@ -419,6 +457,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -419,6 +457,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
sourceFileNames.Add(targetFrameworkAttributeSnippetFile.Value);
}
if (targetNet40)
{
sourceFileNames.Add(nonEmbeddedAttributesSnippetFile.Value);
}
var preprocessorSymbols = GetPreprocessorSymbols(flags);
if ((flags & CompilerOptions.UseMcsMask) == 0)

5
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -107,6 +107,7 @@ @@ -107,6 +107,7 @@
<None Include="TestCases\ILPretty\Issue1922.il" />
<None Include="TestCases\ILPretty\Issue1918.il" />
<None Include="TestCases\ILPretty\Issue2104.il" />
<None Include="TestCases\ILPretty\Issue3421.il" />
<None Include="TestCases\ILPretty\WeirdEnums.il" />
<None Include="TestCases\ILPretty\ConstantBlobs.il" />
<None Include="TestCases\ILPretty\CS1xSwitch_Debug.il" />
@ -132,7 +133,9 @@ @@ -132,7 +133,9 @@
<Compile Include="Output\InsertParenthesesVisitorTests.cs" />
<Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" />
<Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\ILPretty\Issue3421.cs" />
<Compile Include="TestCases\ILPretty\MonoFixed.cs" />
<Compile Include="TestCases\Pretty\Comparisons.cs" />
<None Include="TestCases\VBPretty\VBAutomaticEvents.vb" />
<Compile Include="TestCases\VBPretty\VBAutomaticEvents.cs" />
<Compile Include="TestCases\VBPretty\VBNonGenericForEach.cs" />
@ -154,7 +157,7 @@ @@ -154,7 +157,7 @@
<None Include="TestCases\Pretty\CovariantReturns.cs" />
<Compile Include="TestCases\VBPretty\VBPropertiesTest.cs" />
<None Include="TestCases\ILPretty\Issue2260SwitchString.cs" />
<None Include="TestCases\Pretty\Records.cs" />
<Compile Include="TestCases\Pretty\Records.cs" />
<Compile Include="TestCases\VBPretty\Issue2192.cs" />
<Compile Include="Util\FileUtilityTests.cs" />
<Compile Include="TestCases\Pretty\FunctionPointers.cs" />

6
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -207,6 +207,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -207,6 +207,12 @@ namespace ICSharpCode.Decompiler.Tests
await Run();
}
[Test]
public async Task Issue3421()
{
await Run();
}
[Test]
public async Task Issue2260SwitchString()
{

25
ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs

@ -283,7 +283,17 @@ namespace ICSharpCode.Decompiler.Tests.Output @@ -283,7 +283,17 @@ namespace ICSharpCode.Decompiler.Tests.Output
[TestCase(ILSpyMainTreeViewMemberFlags, "this[int] : int")]
public void Indexer(ConversionFlags flags, string expectedOutput)
{
var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer).Single();
var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer && !p.IsExplicitInterfaceImplementation).Single();
ambience.ConversionFlags = flags;
Assert.That(ambience.ConvertSymbol(prop), Is.EqualTo(expectedOutput));
}
[TestCase(StandardConversionFlags, "int Interface.this[int index] { get; }")]
[TestCase(ILSpyMainTreeViewMemberFlags, "Interface.this[int] : int")]
public void ExplicitIndexer(ConversionFlags flags, string expectedOutput)
{
var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer && p.IsExplicitInterfaceImplementation).Single();
ambience.ConversionFlags = flags;
Assert.That(ambience.ConvertSymbol(prop), Is.EqualTo(expectedOutput));
@ -323,7 +333,12 @@ namespace ICSharpCode.Decompiler.Tests.Output @@ -323,7 +333,12 @@ namespace ICSharpCode.Decompiler.Tests.Output
readonly struct ReadonlyStruct { }
readonly ref struct ReadonlyRefStruct { }
class Program
interface Interface
{
int this[int x] { get; }
}
class Program : Interface
{
int test;
const int TEST2 = 2;
@ -336,6 +351,12 @@ namespace ICSharpCode.Decompiler.Tests.Output @@ -336,6 +351,12 @@ namespace ICSharpCode.Decompiler.Tests.Output
}
}
int Interface.this[int index] {
get {
return index;
}
}
public event EventHandler ProgramChanged;
public event EventHandler SomeEvent {

62
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -233,22 +233,21 @@ namespace ICSharpCode.Decompiler.Tests @@ -233,22 +233,21 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings {
NullPropagation = false,
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
settings.NullPropagation = false;
// legacy csc generates a dead store in debug builds
RemoveDeadStores = (cscOptions == CompilerOptions.None),
FileScopedNamespaces = false,
settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
settings.FileScopedNamespaces = false;
});
}
[Test]
public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings {
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
// legacy csc generates a dead store in debug builds
RemoveDeadStores = (cscOptions == CompilerOptions.None),
SwitchExpressions = false,
FileScopedNamespaces = false,
settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
settings.SwitchExpressions = false;
});
}
@ -267,7 +266,10 @@ namespace ICSharpCode.Decompiler.Tests @@ -267,7 +266,10 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
settings.QueryExpressions = false;
settings.NullableReferenceTypes = false;
});
}
[Test]
@ -293,9 +295,9 @@ namespace ICSharpCode.Decompiler.Tests @@ -293,9 +295,9 @@ namespace ICSharpCode.Decompiler.Tests
{
await RunForLibrary(
cscOptions: cscOptions,
decompilerSettings: new DecompilerSettings {
UseEnhancedUsing = false,
FileScopedNamespaces = false,
configureDecompiler: settings => {
settings.UseEnhancedUsing = false;
settings.FileScopedNamespaces = false;
}
);
}
@ -327,11 +329,11 @@ namespace ICSharpCode.Decompiler.Tests @@ -327,11 +329,11 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{
DecompilerSettings settings = Tester.GetSettings(cscOptions);
// legacy csc generates a dead store in debug builds
settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false;
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings);
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
// legacy csc generates a dead store in debug builds
settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false;
});
}
[Test]
@ -440,9 +442,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -440,9 +442,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
var settings = Tester.GetSettings(cscOptions);
settings.UseDebugSymbols = false;
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings);
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseDebugSymbols = false);
}
[Test]
@ -474,7 +474,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -474,7 +474,7 @@ namespace ICSharpCode.Decompiler.Tests
{
await RunForLibrary(
cscOptions: cscOptions,
decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false, FileScopedNamespaces = false }
configureDecompiler: settings => { settings.UseEnhancedUsing = false; }
);
}
@ -499,7 +499,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -499,7 +499,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings());
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.FileScopedNamespaces = true);
}
[Test]
@ -586,6 +586,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -586,6 +586,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task Comparisons([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task ConstantsTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
@ -595,7 +601,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -595,7 +601,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6));
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.SetLanguageVersion(CSharp.LanguageVersion.CSharp6));
}
[Test]
@ -706,12 +712,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -706,12 +712,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions);
}
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null)
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
{
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings);
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);
}
async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null)
async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
{
var csFile = Path.Combine(TestCasePath, testName + ".cs");
var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe");
@ -733,7 +739,9 @@ namespace ICSharpCode.Decompiler.Tests @@ -733,7 +739,9 @@ namespace ICSharpCode.Decompiler.Tests
}
// 2. Decompile
var decompiled = await Tester.DecompileCSharp(exeFile, decompilerSettings ?? Tester.GetSettings(cscOptions)).ConfigureAwait(false);
var settings = Tester.GetSettings(cscOptions);
configureDecompiler?.Invoke(settings);
var decompiled = await Tester.DecompileCSharp(exeFile, settings).ConfigureAwait(false);
// 3. Compile
CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(cscOptions).Append("EXPECTED_OUTPUT").ToArray());

16
ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs

@ -318,14 +318,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -318,14 +318,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
};
}
public static void NotAStructInitializer_DefaultConstructor()
{
InitializerTests.StructData data = new InitializerTests.StructData();
data.Field = 1;
data.Property = 2;
InitializerTests.X(InitializerTests.Y(), data);
}
public static void StructInitializer_DefaultConstructor()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData {
@ -334,14 +326,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -334,14 +326,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
});
}
public static void NotAStructInitializer_ExplicitConstructor()
{
InitializerTests.StructData data = new InitializerTests.StructData(0);
data.Field = 1;
data.Property = 2;
InitializerTests.X(InitializerTests.Y(), data);
}
public static void StructInitializer_ExplicitConstructor()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData(0) {

16
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs

@ -15,8 +15,8 @@ namespace ClassLibrary1 @@ -15,8 +15,8 @@ namespace ClassLibrary1
//IL_0007: Expected O, but got Unknown
UnknownClass val = new UnknownClass();
int? unknownProperty = val.UnknownProperty;
int? num2 = (val.UnknownProperty = unknownProperty.GetValueOrDefault());
int? num3 = num2;
int? num = (val.UnknownProperty = unknownProperty.GetValueOrDefault());
int? num3 = num;
List<object> list = new List<object> {
val[unknownProperty.Value] ?? "",
val.NotProperty,
@ -50,9 +50,9 @@ namespace ClassLibrary1 @@ -50,9 +50,9 @@ namespace ClassLibrary1
//IL_00e1: Expected O, but got Unknown
//IL_00e1: Expected O, but got Unknown
UnknownGenericClass<UnknownEventArgs> val = new UnknownGenericClass<UnknownEventArgs>();
UnknownEventArgs val2 = (val.UnknownProperty = val.UnknownProperty);
UnknownEventArgs e = (val.UnknownProperty = val.UnknownProperty);
List<object> list = new List<object> {
val[((object)val2).GetHashCode()] ?? "",
val[((object)e).GetHashCode()] ?? "",
val.NotProperty,
val.get_NotPropertyWithGeneric<string>(42),
val[42],
@ -61,10 +61,10 @@ namespace ClassLibrary1 @@ -61,10 +61,10 @@ namespace ClassLibrary1
};
val.OnEvent += Instance_OnEvent;
val.OnEvent -= Instance_OnEvent;
UnknownEventArgs val3 = val[(UnknownEventArgs)null];
val[new UnknownEventArgs()] = val3;
UnknownEventArgs val4 = val[new UnknownEventArgs(), new UnknownEventArgs()];
val[new UnknownEventArgs(), new UnknownEventArgs()] = val4;
UnknownEventArgs e2 = val[(UnknownEventArgs)null];
val[new UnknownEventArgs()] = e2;
UnknownEventArgs e3 = val[new UnknownEventArgs(), new UnknownEventArgs()];
val[new UnknownEventArgs(), new UnknownEventArgs()] = e3;
}
public void MethodUnknownStatic()

26
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
internal class Issue3421
{
private string name;
private object value;
public virtual void SetValue(object value)
{
switch (name)
{
case "##Name##":
return;
case "##Value##":
this.value = value;
return;
case "##InnerText##":
this.value = value.ToString();
return;
case null:
return;
}
if (this.value == null)
{
this.value = "";
}
}
}

116
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il

@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
#define CORE_ASSEMBLY "System.Runtime"
.assembly extern CORE_ASSEMBLY
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 4:0:0:0
}
.class private auto ansi beforefieldinit Issue3421
extends [CORE_ASSEMBLY]System.Object
{
// Fields
.field private string name
.field private object 'value'
.field private static class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> '<>f__switch$map1D'
.custom instance void [CORE_ASSEMBLY]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Methods
.method public hidebysig virtual
instance void SetValue (
object 'value'
) cil managed
{
// Method begins at RVA 0x2050
// Header size: 12
// Code size: 180 (0xb4)
.maxstack 27
.locals init (
[0] string,
[1] class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>,
[2] int32
)
IL_0000: ldarg.0
IL_0001: ldfld string Issue3421::name
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: brfalse IL_0093
IL_000d: ldsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D'
IL_0012: brtrue IL_0048
IL_0017: ldc.i4.3
IL_0018: newobj instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::.ctor(int32)
IL_001d: stloc.1
IL_001e: ldloc.1
IL_001f: ldstr "##Name##"
IL_0024: ldc.i4.0
IL_0025: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
IL_002a: ldloc.1
IL_002b: ldstr "##Value##"
IL_0030: ldc.i4.1
IL_0031: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
IL_0036: ldloc.1
IL_0037: ldstr "##InnerText##"
IL_003c: ldc.i4.2
IL_003d: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
IL_0042: ldloc.1
IL_0043: stsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D'
IL_0048: ldsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D'
IL_004d: ldloc.0
IL_004e: ldloca.s 2
IL_0050: callvirt instance bool class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::TryGetValue(!0, !1&)
IL_0055: brfalse IL_0098
IL_005a: ldloc.2
IL_005b: switch (IL_0071, IL_0076, IL_0082)
IL_006c: br IL_0098
IL_0071: br IL_00b3
IL_0076: ldarg.0
IL_0077: ldarg.1
IL_0078: stfld object Issue3421::'value'
IL_007d: br IL_00b3
IL_0082: ldarg.0
IL_0083: ldarg.1
IL_0084: callvirt instance string [CORE_ASSEMBLY]System.Object::ToString()
IL_0089: stfld object Issue3421::'value'
IL_008e: br IL_00b3
IL_0093: br IL_00b3
IL_0098: ldarg.0
IL_0099: ldfld object Issue3421::'value'
IL_009e: brtrue IL_00ae
IL_00a3: ldarg.0
IL_00a4: ldstr ""
IL_00a9: stfld object Issue3421::'value'
IL_00ae: br IL_00b3
IL_00b3: ret
} // end of method Issue3421::SetValue
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2110
// Header size: 1
// Code size: 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [CORE_ASSEMBLY]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Issue3421::.ctor
} // end of class Issue3421

18
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class Comparisons
{
private class A
{
}
private class B
{
}
private bool CompareUnrelatedNeedsCast(A a, B b)
{
return (object)a == b;
}
}
}

4
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs

@ -4943,8 +4943,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -4943,8 +4943,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void Issue1779(int value)
{
CustomStruct2 @struct = GetStruct();
@struct.IntProp += value;
CustomStruct2 customStruct = GetStruct();
customStruct.IntProp += value;
}
}
}

39
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs

@ -19,15 +19,33 @@ @@ -19,15 +19,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
#if CS100
using System.Threading.Tasks;
#endif
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
{
public static class DelegateConstruction
{
internal class Dummy
{
public int baz;
public List<Dummy> more;
}
[CompilerGenerated]
internal class Helper
{
internal bool HelpMe(Dummy dum)
{
return true;
}
}
private class InstanceTests
{
public struct SomeData
@ -377,10 +395,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction @@ -377,10 +395,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
public static void NameConflict2(int j)
{
List<Action<int>> list = new List<Action<int>>();
for (int k = 0; k < 10; k++)
for (int i = 0; i < 10; i++)
{
list.Add(delegate (int i) {
Console.WriteLine(i);
list.Add(delegate (int k) {
Console.WriteLine(k);
});
}
}
@ -643,6 +661,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction @@ -643,6 +661,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
{
del(x);
}
public void Issue1572(DelegateConstruction.Dummy dum)
{
#if EXPECTED_OUTPUT
DelegateConstruction.Helper CS_0024_003C_003E8__locals0 = new DelegateConstruction.Helper();
DelegateConstruction.Dummy dummy = dum.more.Where((DelegateConstruction.Dummy dummy2) => true).Where((DelegateConstruction.Dummy dummy2) => true).FirstOrDefault();
Console.WriteLine();
dummy.baz++;
#else
DelegateConstruction.Helper h = new DelegateConstruction.Helper();
DelegateConstruction.Dummy localDummy = dum.more.Where(h.HelpMe).Where(h.HelpMe).FirstOrDefault();
Console.WriteLine();
localDummy.baz++;
#endif
}
}
[AttributeUsage(AttributeTargets.All)]

21
ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
@ -28,6 +28,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -28,6 +28,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Item2
}
public enum NoZero
{
Item1 = 1,
Item2
}
public enum OutOfOrderMembers
{
Item1 = 1,
@ -135,5 +141,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -135,5 +141,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
return AttributeTargets.Class | AttributeTargets.Delegate;
}
public void EnumInNotZeroCheck(SimpleEnum value, NoZero value2)
{
if (value != SimpleEnum.Item1)
{
Console.WriteLine();
}
if (value2 != 0)
{
Console.WriteLine();
}
}
}
}

12
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs

@ -418,9 +418,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -418,9 +418,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
Console.WriteLine(input);
}
catch (TException val)
catch (TException ex)
{
Console.WriteLine(val.Message);
Console.WriteLine(ex.Message);
throw;
}
}
@ -452,9 +452,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -452,9 +452,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
Console.WriteLine(input);
}
catch (TException val) when (val.Message.Contains("Test"))
catch (TException ex) when (ex.Message.Contains("Test"))
{
Console.WriteLine(val.Message);
Console.WriteLine(ex.Message);
throw;
}
}
@ -465,9 +465,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -465,9 +465,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
Console.WriteLine(input);
}
catch (TException val) when (val.Message.Contains("Test"))
catch (TException ex) when (ex.Message.Contains("Test"))
{
Console.WriteLine("{0} {1}", val, val.ToString());
Console.WriteLine("{0} {1}", ex, ex.ToString());
}
}

99
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs

@ -235,6 +235,64 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -235,6 +235,64 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
{
int Property { get; set; }
}
#if CS90
public class Issue3392Type
{
public bool Flag { get; init; }
public List<int> List { get; } = new List<int>();
public Issue3392Type(object x)
{
}
}
private class StructInitPropertiesTest
{
private class TypeA
{
public int A { get; set; }
public int B { get; set; }
}
private struct TypeB
{
public int A { get; set; }
public int B { get; set; }
}
private struct TypeC
{
public int A { get; init; }
public int B { get; init; }
}
private static TypeA TestA()
{
return new TypeA {
A = 1,
B = 2
};
}
private static TypeB TestB()
{
return new TypeB {
A = 1,
B = 2
};
}
private static TypeC TestC()
{
return new TypeC {
A = 1,
B = 2
};
}
}
#endif
#endregion
private S s1;
@ -389,6 +447,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -389,6 +447,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
#endif
#if CS110 && !NET40
public static ReadOnlySpan<byte> UTF8Literal => "Hello, world!"u8;
public static ReadOnlySpan<byte> UTF8LiteralWithNullTerminator => "Hello, world!\0"u8;
#endif
#endregion
@ -747,6 +806,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -747,6 +806,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
array[0] = 1;
Console.WriteLine(array.Length);
}
#if !NET40 && CS70
public static ReadOnlySpan<byte> ReadOnlySpanInitializer_ByteArray()
{
return new byte[3] { 1, 2, 3 };
}
public static ReadOnlySpan<int> ReadOnlySpanInitializer_Int32Array()
{
return new int[3] { 1, 2, 3 };
}
#endif
#endregion
#region Object initializers
@ -915,14 +987,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -915,14 +987,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
});
}
public static void NotAStructInitializer_DefaultConstructor()
{
StructData structData = default(StructData);
structData.Field = 1;
structData.Property = 2;
X(Y(), structData);
}
public static void StructInitializer_DefaultConstructor()
{
X(Y(), new StructData {
@ -943,14 +1007,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -943,14 +1007,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
};
}
public static void NotAStructInitializer_ExplicitConstructor()
{
StructData structData = new StructData(0);
structData.Field = 1;
structData.Property = 2;
X(Y(), structData);
}
public static void StructInitializer_ExplicitConstructor()
{
X(Y(), new StructData(0) {
@ -1010,6 +1066,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -1010,6 +1066,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
otherItem.Data2.Nullable = 3m;
return otherItem;
}
#if CS90
public Issue3392Type Issue3392(Issue3392Type x)
{
x = new Issue3392Type(null) {
Flag = false
};
x.List.AddRange(Enumerable.Range(0, 10));
return x;
}
#endif
#if CS60
public OtherItem2 Issue1345c()
{

22
ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs

@ -32,6 +32,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -32,6 +32,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
B
}
internal class OptionalArgumentTest
{
private static void Test()
{
Test2();
Test3();
Test4();
}
private static void Test2(int a = 0)
{
}
private static void Test3(int a = 0, int? b = null)
{
}
private static void Test4(int? b = null, int a = 0)
{
}
}
public OptionalArguments(string name, int a = 5)
{

28
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs

@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public record PairWithPrimaryCtor<A, B>(A First, B Second);
public record PrimaryCtor(int A, string B);
public record PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a);
public record PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a);
public record PrimaryCtorWithField(int A, string B)
{
public double C = 1.0;
@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public record struct PairWithPrimaryCtor<A, B>(A First, B Second);
public record struct PrimaryCtor(int A, string B);
public record struct PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a);
public record struct PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a);
public record struct PrimaryCtorWithField(int A, string B)
{
public double C = 1.0;
@ -242,27 +242,3 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -242,27 +242,3 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
#endif
}
#if !NET60
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute(string featureName)
{
}
}
internal class IsExternalInit
{
}
#endif
#if !NET70
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
internal sealed class RequiredMemberAttribute : Attribute
{
}
#endif
#if !NET60
}
#endif

16
ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs

@ -182,11 +182,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -182,11 +182,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
GetRef<ReadOnlyStruct>().Method();
// call on a copy, not the original ref:
NormalStruct @ref = GetRef<NormalStruct>();
@ref.Method();
NormalStruct normalStruct = GetRef<NormalStruct>();
normalStruct.Method();
ReadOnlyStruct ref2 = GetRef<ReadOnlyStruct>();
ref2.Method();
ReadOnlyStruct readOnlyStruct = GetRef<ReadOnlyStruct>();
readOnlyStruct.Method();
}
public void CallOnReadOnlyRefReturn()
@ -293,13 +293,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -293,13 +293,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void RefReassignment(ref NormalStruct s)
{
ref NormalStruct @ref = ref GetRef<NormalStruct>();
RefReassignment(ref @ref);
ref NormalStruct reference = ref GetRef<NormalStruct>();
RefReassignment(ref reference);
if (s.GetHashCode() == 0)
{
@ref = ref GetRef<NormalStruct>();
reference = ref GetRef<NormalStruct>();
}
RefReassignment(ref @ref.GetHashCode() == 4 ? ref @ref : ref s);
RefReassignment(ref reference.GetHashCode() == 4 ? ref reference : ref s);
}
public static void Main(string[] args)

18
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs

@ -59,20 +59,4 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -59,20 +59,4 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public required string LastName { get; set; }
}
#endif
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute(string featureName)
{
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
internal sealed class RequiredMemberAttribute : Attribute
{
}
}
}

46
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs

@ -39,6 +39,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -39,6 +39,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
private struct TypeA_Issue3385 : IDisposable
{
private int dummy;
public void Dispose()
{
}
#if !ROSLYN3
public static implicit operator TypeB_Issue3385(TypeA_Issue3385 a)
{
return default(TypeB_Issue3385);
}
#else
public static implicit operator TypeB_Issue3385(in TypeA_Issue3385 a)
{
return default(TypeB_Issue3385);
}
#endif
}
private struct TypeB_Issue3385
{
private int dummy;
}
#if CS80
[StructLayout(LayoutKind.Sequential, Size = 1)]
public ref struct UsingRefStruct
@ -161,5 +187,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -161,5 +187,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
#endif
public static void Issue3385()
{
#if ROSLYN3
using (TypeA_Issue3385 a = default(TypeA_Issue3385))
#else
using (TypeA_Issue3385 typeA_Issue = default(TypeA_Issue3385))
#endif
{
#if ROSLYN3
Empty(a);
#else
Empty(typeA_Issue);
#endif
}
}
private static void Empty(TypeB_Issue3385 b)
{
}
}
}

12
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs

@ -40,9 +40,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty @@ -40,9 +40,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
public async void AwaitDefaultYieldAwaitable()
{
#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
YieldAwaitable yieldAwaitable = default(YieldAwaitable);
YieldAwaitable yieldAwaitable2 = yieldAwaitable;
await yieldAwaitable2;
YieldAwaitable yieldAwaitable2 = default(YieldAwaitable);
YieldAwaitable yieldAwaitable = yieldAwaitable2;
await yieldAwaitable;
#else
await default(YieldAwaitable);
#endif
@ -51,9 +51,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty @@ -51,9 +51,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
public async void AwaitDefaultHopToThreadPool()
{
#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
HopToThreadPoolAwaitable hopToThreadPoolAwaitable = default(HopToThreadPoolAwaitable);
HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = hopToThreadPoolAwaitable;
await hopToThreadPoolAwaitable2;
HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = default(HopToThreadPoolAwaitable);
HopToThreadPoolAwaitable hopToThreadPoolAwaitable = hopToThreadPoolAwaitable2;
await hopToThreadPoolAwaitable;
#else
await default(HopToThreadPoolAwaitable);
#endif

4
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -528,6 +528,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -528,6 +528,8 @@ namespace ICSharpCode.Decompiler.CSharp
{
var typeSystemAstBuilder = new TypeSystemAstBuilder();
typeSystemAstBuilder.ShowAttributes = true;
typeSystemAstBuilder.UsePrivateProtectedAccessibility = settings.IntroducePrivateProtectedAccessibility;
typeSystemAstBuilder.SortAttributes = settings.SortCustomAttributes;
typeSystemAstBuilder.AlwaysUseShortTypeNames = true;
typeSystemAstBuilder.AddResolveResultAnnotations = true;
typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables;
@ -1281,7 +1283,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1281,7 +1283,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList())
{
// needs to be consistent with logic in ILReader.CreateILVarable
// needs to be consistent with logic in ILReader.CreateILVariable
parameter.Name = "P_" + i;
}
i++;

5
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -1043,7 +1043,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1043,7 +1043,10 @@ namespace ICSharpCode.Decompiler.CSharp
bool IsOptionalArgument(IParameter parameter, TranslatedExpression arg)
{
if (!parameter.IsOptional || !arg.ResolveResult.IsCompileTimeConstant)
if (!parameter.IsOptional)
return false;
if (!arg.ResolveResult.IsCompileTimeConstant && arg.ResolveResult is not ConversionResolveResult { Conversion.IsNullLiteralConversion: true })
return false;
if (parameter.GetAttributes().Any(a => a.AttributeType.IsKnownType(KnownAttribute.CallerMemberName)
|| a.AttributeType.IsKnownType(KnownAttribute.CallerFilePath)

9
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2547,15 +2547,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2547,15 +2547,18 @@ namespace ICSharpCode.Decompiler.CSharp
foreach (var parameter in parameters)
{
var pd = astBuilder.ConvertParameter(parameter);
if (variables.TryGetValue(i, out var v))
{
pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type));
pd.Name = v.Name;
}
if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList())
{
// needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition)
// needs to be consistent with logic in ILReader.CreateILVariable
pd.Name = "P_" + i;
}
if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType())
pd.Type = null;
if (variables.TryGetValue(i, out var v))
pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type));
yield return pd;
i++;
}

51
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs

@ -236,6 +236,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -236,6 +236,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
astBuilder.ShowTypeParametersForUnboundTypes = true;
astBuilder.ShowModifiers = (ConversionFlags & ConversionFlags.ShowModifiers) == ConversionFlags.ShowModifiers;
astBuilder.ShowAccessibility = (ConversionFlags & ConversionFlags.ShowAccessibility) == ConversionFlags.ShowAccessibility;
astBuilder.UsePrivateProtectedAccessibility = (ConversionFlags & ConversionFlags.UsePrivateProtectedAccessibility) == ConversionFlags.UsePrivateProtectedAccessibility;
astBuilder.AlwaysUseShortTypeNames = (ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) != ConversionFlags.UseFullyQualifiedTypeNames;
astBuilder.ShowParameterNames = (ConversionFlags & ConversionFlags.ShowParameterNames) == ConversionFlags.ShowParameterNames;
astBuilder.UseNullableSpecifierForValueTypes = (ConversionFlags & ConversionFlags.UseNullableSpecifierForValueTypes) != 0;
@ -279,9 +280,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -279,9 +280,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
ConvertType(member.DeclaringType, writer, formattingPolicy);
writer.WriteToken(Roles.Dot, ".");
}
IType explicitInterfaceType = GetExplicitInterfaceType(member);
string name = member.Name;
if (explicitInterfaceType != null)
{
name = name.Substring(name.LastIndexOf('.') + 1);
}
switch (member.SymbolKind)
{
case SymbolKind.Indexer:
if (explicitInterfaceType != null)
{
ConvertType(explicitInterfaceType, writer, formattingPolicy);
writer.WriteToken(Roles.Dot, ".");
}
writer.WriteKeyword(Roles.Identifier, "this");
break;
case SymbolKind.Constructor:
@ -292,11 +304,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -292,11 +304,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy);
break;
case SymbolKind.Operator:
switch (member.Name)
switch (name)
{
case "op_Implicit":
writer.WriteKeyword(OperatorDeclaration.ImplicitRole, "implicit");
writer.Space();
if (explicitInterfaceType != null)
{
ConvertType(explicitInterfaceType, writer, formattingPolicy);
writer.WriteToken(Roles.Dot, ".");
}
writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator");
writer.Space();
ConvertType(member.ReturnType, writer, formattingPolicy);
@ -305,9 +322,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -305,9 +322,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case "op_CheckedExplicit":
writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit");
writer.Space();
if (explicitInterfaceType != null)
{
ConvertType(explicitInterfaceType, writer, formattingPolicy);
writer.WriteToken(Roles.Dot, ".");
}
writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator");
writer.Space();
if (member.Name == "op_CheckedExplicit")
if (name == "op_CheckedExplicit")
{
writer.WriteToken(OperatorDeclaration.CheckedKeywordRole, "checked");
writer.Space();
@ -315,9 +337,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -315,9 +337,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
ConvertType(member.ReturnType, writer, formattingPolicy);
break;
default:
if (explicitInterfaceType != null)
{
ConvertType(explicitInterfaceType, writer, formattingPolicy);
writer.WriteToken(Roles.Dot, ".");
}
writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator");
writer.Space();
var operatorType = OperatorDeclaration.GetOperatorType(member.Name);
var operatorType = OperatorDeclaration.GetOperatorType(name);
if (operatorType.HasValue && !((ConversionFlags & ConversionFlags.SupportOperatorChecked) == 0 && OperatorDeclaration.IsChecked(operatorType.Value)))
{
if (OperatorDeclaration.IsChecked(operatorType.Value))
@ -335,7 +362,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -335,7 +362,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
break;
default:
writer.WriteIdentifier(Identifier.Create(member.Name));
if (explicitInterfaceType != null)
{
ConvertType(explicitInterfaceType, writer, formattingPolicy);
writer.WriteToken(Roles.Dot, ".");
}
writer.WriteIdentifier(Identifier.Create(name));
break;
}
WriteTypeParameters(node, writer, formattingPolicy);
@ -407,6 +439,17 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -407,6 +439,17 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
astType.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy));
}
IType GetExplicitInterfaceType(IMember member)
{
if (member.IsExplicitInterfaceImplementation)
{
var baseMember = member.ExplicitlyImplementedInterfaceMembers.FirstOrDefault();
if (baseMember != null)
return baseMember.DeclaringType;
}
return null;
}
public string ConvertConstantValue(object constantValue)
{
return TextWriterTokenWriter.PrintPrimitiveValue(constantValue);

13
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team
// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
@ -426,8 +426,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -426,8 +426,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// <summary>
/// Determines whether the specified identifier is a keyword in the given context.
/// If <paramref name="context"/> is <see langword="null" /> all keywords are treated as unconditional.
/// </summary>
public static bool IsKeyword(string identifier, AstNode context)
public static bool IsKeyword(string identifier, AstNode context = null)
{
// only 2-10 char lower-case identifiers can be keywords
if (identifier.Length > maxKeywordLength || identifier.Length < 2 || identifier[0] < 'a')
@ -440,10 +441,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -440,10 +441,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
if (queryKeywords.Contains(identifier))
{
return context.Ancestors.Any(ancestor => ancestor is QueryExpression);
return context == null || context.Ancestors.Any(ancestor => ancestor is QueryExpression);
}
if (identifier == "await")
{
if (context == null)
return true;
foreach (AstNode ancestor in context.Ancestors)
{
// with lambdas/anonymous methods,
@ -1530,8 +1533,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1530,8 +1533,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteToken(Roles.RBracket);
switch (attributeSection.Parent)
{
case ParameterDeclaration _:
if (attributeSection.NextSibling is AttributeSection)
case ParameterDeclaration pd:
if (pd.Attributes.Last() != attributeSection)
Space(policy.SpaceBetweenParameterAttributeSections);
else
Space();

7
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs

@ -81,13 +81,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -81,13 +81,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
w.WriteEndElement(); // </Platform>
string outputType;
PEHeaders headers = (module as PEFile)?.Reader.PEHeaders;
switch ((module as PEFile)?.Reader.PEHeaders.PEHeader.Subsystem)
switch (headers?.PEHeader.Subsystem)
{
case Subsystem.WindowsGui:
case Subsystem.WindowsGui when !headers.IsDll:
outputType = "WinExe";
break;
case Subsystem.WindowsCui:
case Subsystem.WindowsCui when !headers.IsDll:
outputType = "Exe";
break;
default:

41
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
@ -135,7 +136,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -135,7 +136,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
public void DecompileProject(MetadataFile file, string targetDirectory, CancellationToken cancellationToken = default(CancellationToken))
{
string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(file.Name) + ".csproj");
string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(file.Name, ".csproj"));
using (var writer = new StreamWriter(projectFileName))
{
DecompileProject(file, targetDirectory, writer, cancellationToken);
@ -238,7 +239,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -238,7 +239,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
string GetFileFileNameForHandle(TypeDefinitionHandle h)
{
var type = metadata.GetTypeDefinition(h);
string file = SanitizeFileName(metadata.GetString(type.Name) + ".cs");
string file = CleanUpFileName(metadata.GetString(type.Name), ".cs");
string ns = metadata.GetString(type.Namespace);
if (string.IsNullOrEmpty(ns))
{
@ -339,8 +340,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -339,8 +340,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
foreach (var (name, value) in resourcesFile)
{
string fileName = SanitizeFileName(name)
.Replace('/', Path.DirectorySeparatorChar);
string fileName = SanitizeFileName(name);
string dirName = Path.GetDirectoryName(fileName);
if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName))
{
@ -609,9 +609,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -609,9 +609,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// <summary>
/// Cleans up a node name for use as a file name.
/// </summary>
public static string CleanUpFileName(string text)
public static string CleanUpFileName(string text, string extension)
{
return CleanUpName(text, separateAtDots: false, treatAsFileName: false);
Debug.Assert(!string.IsNullOrEmpty(extension));
if (!extension.StartsWith("."))
extension = "." + extension;
text = text + extension;
return CleanUpName(text, separateAtDots: false, treatAsFileName: !string.IsNullOrEmpty(extension), treatAsPath: false);
}
/// <summary>
@ -620,7 +625,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -620,7 +625,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// </summary>
public static string SanitizeFileName(string fileName)
{
return CleanUpName(fileName, separateAtDots: false, treatAsFileName: true);
return CleanUpName(fileName, separateAtDots: false, treatAsFileName: true, treatAsPath: true);
}
/// <summary>
@ -629,15 +634,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -629,15 +634,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// If <paramref name="treatAsFileName"/> is active, we check for file a extension and try to preserve it,
/// if it's valid.
/// </summary>
static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName)
static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName, bool treatAsPath)
{
// Remove anything that could be confused with a rooted path.
int pos = text.IndexOf(':');
if (pos > 0)
text = text.Substring(0, pos);
text = text.Trim();
string extension = null;
int currentSegmentLength = 0;
// Extract extension from the end of the name, if valid
if (treatAsFileName)
{
// Check if input is a file name, i.e., has a valid extension
@ -663,6 +664,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -663,6 +664,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
}
}
}
// Remove anything that could be confused with a rooted path.
int pos = text.IndexOf(':');
if (pos > 0)
text = text.Substring(0, pos);
text = text.Trim();
// Remove generics
pos = text.IndexOf('`');
if (pos > 0)
@ -692,10 +698,10 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -692,10 +698,10 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
if (separateAtDots)
currentSegmentLength = 0;
}
else if (treatAsFileName && (c == '/' || c == '\\') && currentSegmentLength > 1)
else if (treatAsPath && (c is '/' or '\\') && currentSegmentLength > 1)
{
// if we treat this as a file name, we've started a new segment
b.Append(c);
b.Append(Path.DirectorySeparatorChar);
currentSegmentLength = 0;
}
else
@ -732,13 +738,12 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -732,13 +738,12 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// </summary>
public static string CleanUpDirectoryName(string text)
{
return CleanUpName(text, separateAtDots: false, treatAsFileName: false);
return CleanUpName(text, separateAtDots: false, treatAsFileName: false, treatAsPath: false);
}
public static string CleanUpPath(string text)
{
return CleanUpName(text, separateAtDots: true, treatAsFileName: false)
.Replace('.', Path.DirectorySeparatorChar);
return CleanUpName(text, separateAtDots: true, treatAsFileName: true, treatAsPath: true);
}
static bool IsReservedFileSystemName(string name)

5
ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs

@ -805,7 +805,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -805,7 +805,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
}
if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality)
{
if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true)
if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true
&& (conversions.IdentityConversion(lhsType, rhsType)
|| conversions.ExplicitConversion(lhsType, rhsType).IsReferenceConversion
|| conversions.ExplicitConversion(rhsType, lhsType).IsReferenceConversion))
{
// If it's a reference comparison
if (op == BinaryOperatorType.Equality)

10
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -1389,6 +1389,16 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1389,6 +1389,16 @@ namespace ICSharpCode.Decompiler.CSharp
var astBuilder = exprBuilder.astBuilder;
var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod);
var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
foreach (var (i, p) in method.Parameters.WithIndex())
{
if (variables.TryGetValue(i, out var v))
{
p.Name = v.Name;
}
}
if (function.Method.HasBody)
{
var nestedBuilder = new StatementBuilder(

105
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -70,6 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -70,6 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
this.UseKeywordsForBuiltinTypes = true;
this.UseNullableSpecifierForValueTypes = true;
this.ShowAccessibility = true;
this.UsePrivateProtectedAccessibility = true;
this.ShowModifiers = true;
this.ShowBaseTypes = true;
this.ShowTypeParameters = true;
@ -93,13 +94,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -93,13 +94,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public bool AddResolveResultAnnotations { get; set; }
/// <summary>
/// Controls the accessibility modifiers are shown.
/// Controls whether accessibility modifiers are shown.
/// The default value is <see langword="true" />.
/// </summary>
public bool ShowAccessibility { get; set; }
/// <summary>
/// Controls the non-accessibility modifiers are shown.
/// Controls whether "private protected" accessibility modifiers are shown.
/// The default value is <see langword="true" />.
/// </summary>
public bool UsePrivateProtectedAccessibility { get; set; }
/// <summary>
/// Controls whether non-accessibility modifiers are shown.
/// The default value is <see langword="true" />.
/// </summary>
public bool ShowModifiers { get; set; }
@ -147,6 +154,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -147,6 +154,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// </summary>
public bool ShowAttributes { get; set; }
/// <summary>
/// Controls whether to sort attributes, if set to <see langword="false" /> attributes are shown in metadata order.
/// The default value is <see langword="false" />.
/// </summary>
public bool SortAttributes { get; set; }
/// <summary>
/// Controls whether to use fully-qualified type names or short type names.
/// The default value is <see langword="false" />.
@ -793,16 +806,72 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -793,16 +806,72 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return attr;
}
private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes)
{
return attributes.Select(a => new AttributeSection(ConvertAttribute(a)));
}
private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes, string target)
private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes, string target = null)
{
return attributes.Select(a => new AttributeSection(ConvertAttribute(a)) {
AttributeTarget = target
if (SortAttributes)
attributes = attributes.OrderBy(a => a, new DelegateComparer<IAttribute>(CompareAttribute));
return attributes.Select(a => {
var section = new AttributeSection(ConvertAttribute(a));
if (target != null)
section.AttributeTarget = target;
return section;
});
static int CompareAttribute(IAttribute a, IAttribute b)
{
int result = CompareType(a.AttributeType, b.AttributeType);
if (result != 0)
return result;
if (a.HasDecodeErrors && b.HasDecodeErrors)
return 0;
if (a.HasDecodeErrors)
return -1;
if (b.HasDecodeErrors)
return 1;
result = a.FixedArguments.Length - b.FixedArguments.Length;
if (result != 0)
return result;
for (int i = 0; i < a.FixedArguments.Length; i++)
{
var argA = a.FixedArguments[i];
var argB = b.FixedArguments[i];
result = CompareType(argA.Type, argB.Type);
if (result != 0)
return result;
if (argA.Value is IComparable compA && argB.Value is IComparable compB)
result = compA.CompareTo(compB);
else
result = 0;
if (result != 0)
return result;
}
result = a.NamedArguments.Length - b.NamedArguments.Length;
if (result != 0)
return result;
for (int i = 0; i < a.NamedArguments.Length; i++)
{
var argA = a.NamedArguments[i];
var argB = b.NamedArguments[i];
result = argA.Name.CompareTo(argB.Name);
if (result != 0)
return result;
result = CompareType(argA.Type, argB.Type);
if (result != 0)
return result;
if (argA.Value is IComparable compA && argB.Value is IComparable compB)
result = compA.CompareTo(compB);
else
result = 0;
if (result != 0)
return result;
}
return 0;
}
static int CompareType(IType a, IType b)
{
return a.FullName.CompareTo(b.FullName);
}
}
#endregion
@ -1072,7 +1141,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1072,7 +1141,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
expr = new PrimitiveExpression(constantValue, format);
if (AddResolveResultAnnotations && literalType != null)
expr.AddAnnotation(new ConstantResolveResult(literalType, constantValue));
if (integerTypeMismatch && !type.Equals(expectedType))
if (integerTypeMismatch && !type.Equals(expectedType) || underlyingType.Kind == TypeKind.Unknown)
{
expr = new CastExpression(ConvertType(type), expr);
}
@ -1722,10 +1791,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1722,10 +1791,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case SymbolKind.Event:
return ConvertEvent((IEvent)entity);
case SymbolKind.Method:
if (entity.Name.Contains(".op_"))
{
goto case SymbolKind.Operator;
}
return ConvertMethod((IMethod)entity);
case SymbolKind.Operator:
return ConvertOperator((IMethod)entity);
@ -1747,7 +1812,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1747,7 +1812,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
Modifiers modifiers = Modifiers.None;
if (this.ShowAccessibility)
{
modifiers |= ModifierFromAccessibility(typeDefinition.Accessibility);
modifiers |= ModifierFromAccessibility(typeDefinition.Accessibility, UsePrivateProtectedAccessibility);
}
if (this.ShowModifiers)
{
@ -2015,7 +2080,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -2015,7 +2080,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
}
if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility)
decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility);
decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility, UsePrivateProtectedAccessibility);
if (this.ShowModifiers && accessor.HasReadonlyModifier())
decl.Modifiers |= Modifiers.Readonly;
TokenRole keywordRole = kind switch {
@ -2284,7 +2349,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -2284,7 +2349,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
#endregion
#region Convert Modifiers
public static Modifiers ModifierFromAccessibility(Accessibility accessibility)
public static Modifiers ModifierFromAccessibility(Accessibility accessibility, bool usePrivateProtected)
{
switch (accessibility)
{
@ -2299,7 +2364,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -2299,7 +2364,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case Accessibility.ProtectedOrInternal:
return Modifiers.Protected | Modifiers.Internal;
case Accessibility.ProtectedAndInternal:
return Modifiers.Private | Modifiers.Protected;
return usePrivateProtected ? Modifiers.Private | Modifiers.Protected : Modifiers.Protected;
default:
return Modifiers.None;
}
@ -2330,7 +2395,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -2330,7 +2395,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
Modifiers m = Modifiers.None;
if (this.ShowAccessibility && NeedsAccessibility(member))
{
m |= ModifierFromAccessibility(member.Accessibility);
m |= ModifierFromAccessibility(member.Accessibility, UsePrivateProtectedAccessibility);
}
if (this.ShowModifiers)
{

32
ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs

@ -16,12 +16,12 @@ @@ -16,12 +16,12 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.CSharp.Transforms
{
@ -54,12 +54,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -54,12 +54,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
next = child.NextSibling;
CombineQueries(child, fromOrLetIdentifiers);
}
QueryExpression query = node as QueryExpression;
if (query != null)
if (node is QueryExpression query)
{
QueryFromClause fromClause = (QueryFromClause)query.Clauses.First();
QueryExpression innerQuery = fromClause.Expression as QueryExpression;
if (innerQuery != null)
if (fromClause.Expression is QueryExpression innerQuery)
{
if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery, fromOrLetIdentifiers))
{
@ -165,21 +163,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -165,21 +163,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{
RemoveTransparentIdentifierReferences(child, fromOrLetIdentifiers);
}
MemberReferenceExpression mre = node as MemberReferenceExpression;
if (mre != null)
if (node is MemberReferenceExpression mre && mre.Target is IdentifierExpression ident
&& CSharpDecompiler.IsTransparentIdentifier(ident.Identifier))
{
IdentifierExpression ident = mre.Target as IdentifierExpression;
if (ident != null && CSharpDecompiler.IsTransparentIdentifier(ident.Identifier))
{
IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName);
mre.TypeArguments.MoveTo(newIdent.TypeArguments);
newIdent.CopyAnnotationsFrom(mre);
newIdent.RemoveAnnotations<Semantics.MemberResolveResult>(); // remove the reference to the property of the anonymous type
if (fromOrLetIdentifiers.TryGetValue(mre.MemberName, out var annotation))
newIdent.AddAnnotation(annotation);
mre.ReplaceWith(newIdent);
return;
}
IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName);
mre.TypeArguments.MoveTo(newIdent.TypeArguments);
newIdent.CopyAnnotationsFrom(mre);
newIdent.RemoveAnnotations<Semantics.MemberResolveResult>(); // remove the reference to the property of the anonymous type
if (fromOrLetIdentifiers.TryGetValue(mre.MemberName, out var annotation))
newIdent.AddAnnotation(annotation);
mre.ReplaceWith(newIdent);
return;
}
}
}

18
ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs

@ -172,13 +172,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -172,13 +172,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
"Microsoft.CodeAnalysis.EmbeddedAttribute",
};
internal static readonly HashSet<string> nonEmbeddedAttributeNames = new HashSet<string>() {
// non-embedded attributes, but we still want to remove them
"System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute",
"System.Runtime.CompilerServices.RequiredMemberAttribute",
"System.Runtime.CompilerServices.IsExternalInit",
};
public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration)
{
var typeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition;
if (typeDefinition == null || !attributeNames.Contains(typeDefinition.FullName))
if (typeDefinition == null)
return;
if (!typeDefinition.HasAttribute(KnownAttribute.Embedded))
if (attributeNames.Contains(typeDefinition.FullName))
{
if (!typeDefinition.HasAttribute(KnownAttribute.Embedded))
return;
}
else if (!nonEmbeddedAttributeNames.Contains(typeDefinition.FullName))
return;
if (typeDeclaration.Parent is NamespaceDeclaration ns && ns.Members.Count == 1)
ns.Remove();
else

21
ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs

@ -17,10 +17,10 @@ @@ -17,10 +17,10 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.IL;
namespace ICSharpCode.Decompiler.CSharp.Transforms
{
@ -54,13 +54,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -54,13 +54,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
while (IsDegenerateQuery(innerQuery))
{
QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
if (fromClause.Identifier != innerFromClause.Identifier)
break;
ILVariable innerVariable = innerFromClause.Annotation<ILVariableResolveResult>()?.Variable;
ILVariable rangeVariable = fromClause.Annotation<ILVariableResolveResult>()?.Variable;
// Replace the fromClause with all clauses from the inner query
fromClause.Remove();
QueryClause insertionPos = null;
foreach (var clause in innerQuery.Clauses)
{
CombineRangeVariables(clause, innerVariable, rangeVariable);
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
}
fromClause = innerFromClause;
@ -69,6 +70,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -69,6 +70,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
}
private void CombineRangeVariables(QueryClause clause, ILVariable oldVariable, ILVariable newVariable)
{
foreach (var identifier in clause.DescendantNodes().OfType<Identifier>())
{
var variable = identifier.Parent.Annotation<ILVariableResolveResult>()?.Variable;
if (variable == oldVariable)
{
identifier.Parent.RemoveAnnotations<ILVariableResolveResult>();
identifier.Parent.AddAnnotation(new ILVariableResolveResult(newVariable));
identifier.ReplaceWith(Identifier.Create(newVariable.Name));
}
}
}
bool IsDegenerateQuery(QueryExpression query)
{
if (query == null)

13
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -1,4 +1,4 @@ @@ -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
@ -731,6 +731,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -731,6 +731,17 @@ namespace ICSharpCode.Decompiler.CSharp
.WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual,
this.ResolveResult, nullRef.ResolveResult));
}
else if (Type.Kind == TypeKind.Enum && Type.GetDefinition() is { } typeDef &&
typeDef.Fields.Any(f => f.GetConstantValue() is { } val && (ulong)CSharpPrimitiveCast.Cast(TypeCode.UInt64, val, false) == 0L))
{
var zero = expressionBuilder
.ConvertConstantValue(new ConstantResolveResult(Type, 0), allowImplicitConversion: true);
var op = negate ? BinaryOperatorType.Equality : BinaryOperatorType.InEquality;
return new BinaryOperatorExpression(Expression, op, zero.Expression)
.WithoutILInstruction()
.WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual,
this.ResolveResult, zero.ResolveResult));
}
else
{
var zero = new PrimitiveExpression(0)

2
ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs

@ -92,7 +92,7 @@ namespace ICSharpCode.Decompiler.DebugInfo @@ -92,7 +92,7 @@ namespace ICSharpCode.Decompiler.DebugInfo
string ns = settings.UseNestedDirectoriesForNamespaces
? WholeProjectDecompiler.CleanUpPath(typeName.Namespace)
: WholeProjectDecompiler.CleanUpDirectoryName(typeName.Namespace);
return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs");
return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name, ".cs"));
}
var sourceFiles = reader.GetTopLevelTypeDefinitions().Where(t => IncludeTypeWhenGeneratingPdb(file, t, settings)).GroupBy(BuildFileNameFromTypeName).ToList();

39
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -112,6 +112,7 @@ namespace ICSharpCode.Decompiler @@ -112,6 +112,7 @@ namespace ICSharpCode.Decompiler
introduceRefModifiersOnStructs = false;
nonTrailingNamedArguments = false;
refExtensionMethods = false;
introducePrivateProtectedAccessibilty = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7_3)
{
@ -185,7 +186,7 @@ namespace ICSharpCode.Decompiler @@ -185,7 +186,7 @@ namespace ICSharpCode.Decompiler
|| patternBasedFixedStatement)
return CSharp.LanguageVersion.CSharp7_3;
if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers
|| nonTrailingNamedArguments || refExtensionMethods)
|| nonTrailingNamedArguments || refExtensionMethods || introducePrivateProtectedAccessibilty)
return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing
if (outVariables || throwExpressions || tupleTypes || tupleConversions
@ -1418,6 +1419,24 @@ namespace ICSharpCode.Decompiler @@ -1418,6 +1419,24 @@ namespace ICSharpCode.Decompiler
}
}
bool introducePrivateProtectedAccessibilty = true;
/// <summary>
/// Gets/Sets whether "private protected" should be used.
/// </summary>
[Category("C# 7.2 / VS 2017.4")]
[Description("DecompilerSettings.IntroducePrivateProtectedAccessibility")]
public bool IntroducePrivateProtectedAccessibility {
get { return introducePrivateProtectedAccessibilty; }
set {
if (introducePrivateProtectedAccessibilty != value)
{
introducePrivateProtectedAccessibilty = value;
OnPropertyChanged();
}
}
}
bool readOnlyMethods = true;
[Category("C# 8.0 / VS 2019")]
@ -2148,6 +2167,24 @@ namespace ICSharpCode.Decompiler @@ -2148,6 +2167,24 @@ namespace ICSharpCode.Decompiler
}
}
bool sortCustomAttributes = false;
/// <summary>
/// Sort custom attributes.
/// </summary>
[Category("DecompilerSettings.Other")]
[Description("DecompilerSettings.SortCustomAttributes")]
public bool SortCustomAttributes {
get { return sortCustomAttributes; }
set {
if (sortCustomAttributes != value)
{
sortCustomAttributes = value;
OnPropertyChanged();
}
}
}
CSharpFormattingOptions csharpFormattingOptions;
[Browsable(false)]

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -154,6 +154,7 @@ @@ -154,6 +154,7 @@
<Compile Include="Properties\DecompilerVersionInfo.cs" />
<Compile Include="TypeSystem\ITypeDefinitionOrUnknown.cs" />
<Compile Include="Util\BitOperations.cs" />
<Compile Include="Util\DelegateComparer.cs" />
<Compile Include="Util\Index.cs" />
<Compile Include="Metadata\WebCilFile.cs" />
<None Include="Properties\DecompilerVersionInfo.template.cs" />

1
ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

@ -1165,6 +1165,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -1165,6 +1165,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
function.MoveNextMethod = moveNextFunction.Method;
function.SequencePointCandidates = moveNextFunction.SequencePointCandidates;
function.CodeSize = moveNextFunction.CodeSize;
function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength;
function.IsIterator = IsAsyncEnumerator;
moveNextFunction.Variables.Clear();
moveNextFunction.ReleaseRef();

29
ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs

@ -21,11 +21,8 @@ @@ -21,11 +21,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
@ -225,6 +222,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -225,6 +222,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
goto default;
}
}
case StObj stobj when mode == StateRangeAnalysisMode.IteratorDispose:
{
if (stobj.MatchStFld(out var target, out var field, out var value) && target.MatchLdThis())
{
if (field.MemberDefinition == stateField && value.MatchLdcI4(-2))
{
// Roslyn 4.13 sets the state field in Dispose() to mark the iterator as disposed,
// don't consider this user code.
return stateRange;
}
else if (value.MatchDefaultOrNullOrZero())
{
// Roslyn 4.13 clears any local hoisted local variables in Dispose(),
// don't consider this user code.
return stateRange;
}
else
{
goto default;
}
}
else
{
goto default;
}
}
default:
// User code - abort analysis
if (mode == StateRangeAnalysisMode.IteratorDispose && !(inst is Leave l && l.IsLeavingFunction))

3
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -656,6 +656,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -656,6 +656,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
function.MoveNextMethod = moveNextFunction.Method;
function.SequencePointCandidates = moveNextFunction.SequencePointCandidates;
function.CodeSize = moveNextFunction.CodeSize;
function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength;
// Copy-propagate temporaries holding a copy of 'this'.
// This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables.
@ -1013,7 +1014,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -1013,7 +1014,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
// We can't use MatchStLoc like above since the doFinallyBodies variable is split by SplitVariables.
// This occurs for the Legacy VBC compiler.
if (oldBlock.Instructions[pos].MatchStLoc(out var var, out value) && var.Kind == VariableKind.Local && var.Index == doFinallyBodies.Index)
if (oldBlock.Instructions[pos].MatchStLoc(out var var, out value) && var.Kind == VariableKind.Local && var.Index == doFinallyBodies?.Index)
{
if (!value.MatchLdcI4(0))
{

4
ICSharpCode.Decompiler/IL/ILReader.cs

@ -342,7 +342,10 @@ namespace ICSharpCode.Decompiler.IL @@ -342,7 +342,10 @@ namespace ICSharpCode.Decompiler.IL
if (index < 0)
ilVar.Name = "this";
else if (string.IsNullOrWhiteSpace(name))
{
ilVar.Name = "P_" + index;
ilVar.HasGeneratedName = true;
}
else
ilVar.Name = name;
return ilVar;
@ -706,6 +709,7 @@ namespace ICSharpCode.Decompiler.IL @@ -706,6 +709,7 @@ namespace ICSharpCode.Decompiler.IL
var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind);
function.Variables.AddRange(parameterVariables);
function.Variables.AddRange(localVariables);
function.LocalVariableSignatureLength = localVariables.Length;
Debug.Assert(stackVariables != null);
function.Variables.AddRange(stackVariables);
function.Variables.AddRange(variableByExceptionHandler.Values);

5
ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs

@ -67,6 +67,11 @@ namespace ICSharpCode.Decompiler.IL @@ -67,6 +67,11 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
public readonly ILVariableCollection Variables;
/// <summary>
/// Gets
/// </summary>
public int LocalVariableSignatureLength;
/// <summary>
/// Gets the scope in which the local function is declared.
/// Returns null, if this is not a local function.

747
ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
@ -25,6 +26,7 @@ using System.Reflection.Metadata; @@ -25,6 +26,7 @@ using System.Reflection.Metadata;
using Humanizer.Inflections;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -32,7 +34,7 @@ using ICSharpCode.Decompiler.Util; @@ -32,7 +34,7 @@ using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
public class AssignVariableNames : IILTransform
public class AssignVariableNames : ILVisitor<AssignVariableNames.VariableScope, Unit>, IILTransform
{
static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> {
{ "System.Boolean", "flag" },
@ -53,52 +55,83 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -53,52 +55,83 @@ namespace ICSharpCode.Decompiler.IL.Transforms
};
ILTransformContext context;
List<string> currentLowerCaseTypeOrMemberNames;
Dictionary<string, int> reservedVariableNames;
HashSet<ILVariable> loopCounters;
const char maxLoopVariableName = 'n';
int numDisplayClassLocals;
public void Run(ILFunction function, ILTransformContext context)
public class VariableScope
{
this.context = context;
readonly ILTransformContext context;
readonly VariableScope parentScope;
readonly ILFunction function;
readonly Dictionary<MethodDefinitionHandle, string> localFunctions = new();
readonly Dictionary<ILVariable, string> variableMapping = new(ILVariableEqualityComparer.Instance);
readonly string[] assignedLocalSignatureIndices;
IImmutableSet<string> currentLowerCaseTypeOrMemberNames;
Dictionary<string, int> reservedVariableNames;
HashSet<ILVariable> loopCounters;
int numDisplayClassLocals;
public VariableScope(ILFunction function, ILTransformContext context, VariableScope parentScope = null)
{
this.function = function;
this.context = context;
this.parentScope = parentScope;
numDisplayClassLocals = 0;
assignedLocalSignatureIndices = new string[function.LocalVariableSignatureLength];
reservedVariableNames = new Dictionary<string, int>();
// find all loop counters in the current function
loopCounters = new HashSet<ILVariable>();
foreach (var inst in TreeTraversal.PreOrder((ILInstruction)function, i => i.Children))
{
if (inst is ILFunction && inst != function)
break;
if (inst is BlockContainer { Kind: ContainerKind.For, Blocks: [.., var incrementBlock] })
{
foreach (var i in incrementBlock.Instructions)
{
if (HighLevelLoopTransform.MatchIncrement(i, out var variable))
loopCounters.Add(variable);
}
}
}
reservedVariableNames = new Dictionary<string, int>();
currentLowerCaseTypeOrMemberNames = new List<string>();
var currentLowerCaseMemberNames = CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition);
foreach (var name in currentLowerCaseMemberNames)
currentLowerCaseTypeOrMemberNames.Add(name);
var currentLowerCaseTypeNames = CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition);
foreach (var name in currentLowerCaseTypeNames)
{
currentLowerCaseTypeOrMemberNames.Add(name);
AddExistingName(reservedVariableNames, name);
}
loopCounters = CollectLoopCounters(function);
foreach (var f in function.Descendants.OfType<ILFunction>())
{
if (f.Method != null)
// if this is the root scope, we also collect all lower-case type and member names
// and fixed parameter names to avoid conflicts when naming local variables.
if (parentScope == null)
{
if (IsSetOrEventAccessor(f.Method) && f.Method.Parameters.Count > 0)
var currentLowerCaseTypeOrMemberNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var name in CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition))
currentLowerCaseTypeOrMemberNames.Add(name);
foreach (var name in CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition))
{
currentLowerCaseTypeOrMemberNames.Add(name);
AddExistingName(reservedVariableNames, name);
}
this.currentLowerCaseTypeOrMemberNames = currentLowerCaseTypeOrMemberNames.ToImmutableHashSet();
// handle implicit parameters of set or event accessors
if (function.Method != null && IsSetOrEventAccessor(function.Method) && function.Parameters.Count > 0)
{
for (int i = 0; i < f.Method.Parameters.Count - 1; i++)
for (int i = 0; i < function.Method.Parameters.Count - 1; i++)
{
AddExistingName(reservedVariableNames, f.Method.Parameters[i].Name);
AddExistingName(reservedVariableNames, function.Method.Parameters[i].Name);
}
var lastParameter = f.Method.Parameters.Last();
switch (f.Method.AccessorOwner)
var lastParameter = function.Method.Parameters.Last();
switch (function.Method.AccessorOwner)
{
case IProperty prop:
if (f.Method.AccessorKind == MethodSemanticsAttributes.Setter)
if (function.Method.AccessorKind == MethodSemanticsAttributes.Setter)
{
if (prop.Parameters.Any(p => p.Name == "value"))
{
f.Warnings.Add("Parameter named \"value\" already present in property signature!");
function.Warnings.Add("Parameter named \"value\" already present in property signature!");
break;
}
var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f
var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
&& v.Kind == VariableKind.Parameter
&& v.Index == f.Method.Parameters.Count - 1);
&& v.Index == function.Method.Parameters.Count - 1);
if (variableForLastParameter == null)
{
AddExistingName(reservedVariableNames, lastParameter.Name);
@ -114,11 +147,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -114,11 +147,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
break;
case IEvent ev:
if (f.Method.AccessorKind != MethodSemanticsAttributes.Raiser)
if (function.Method.AccessorKind != MethodSemanticsAttributes.Raiser)
{
var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f
var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
&& v.Kind == VariableKind.Parameter
&& v.Index == f.Method.Parameters.Count - 1);
&& v.Index == function.Method.Parameters.Count - 1);
if (variableForLastParameter == null)
{
AddExistingName(reservedVariableNames, lastParameter.Name);
@ -140,183 +173,425 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -140,183 +173,425 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
else
{
foreach (var p in f.Method.Parameters)
AddExistingName(reservedVariableNames, p.Name);
var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0).ToDictionary(v => v.Index);
foreach (var (i, p) in function.Parameters.WithIndex())
{
string name = p.Name;
if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList)
{
// needs to be consistent with logic in ILReader.CreateILVariable
name = "P_" + i;
}
if (variables.TryGetValue(i, out var v))
variableMapping[v] = name;
AddExistingName(reservedVariableNames, name);
}
}
static bool IsSetOrEventAccessor(IMethod method)
{
switch (method.AccessorKind)
{
case MethodSemanticsAttributes.Setter:
case MethodSemanticsAttributes.Adder:
case MethodSemanticsAttributes.Remover:
return true;
default:
return false;
}
}
}
else
{
foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter))
AddExistingName(reservedVariableNames, p.Name);
this.currentLowerCaseTypeOrMemberNames = parentScope.currentLowerCaseTypeOrMemberNames;
var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
foreach (var (i, p) in function.Parameters.WithIndex())
{
if (function.Kind is ILFunctionKind.Delegate or ILFunctionKind.ExpressionTree
&& CSharpDecompiler.IsTransparentIdentifier(p.Name))
{
AddExistingName(reservedVariableNames, p.Name);
if (variables.TryGetValue(i, out var v))
variableMapping[v] = p.Name;
}
string nameWithoutNumber = SplitName(p.Name, out int newIndex);
if (!parentScope.IsReservedVariableName(nameWithoutNumber, out _))
{
AddExistingName(reservedVariableNames, p.Name);
if (variables.TryGetValue(i, out var v))
variableMapping[v] = p.Name;
}
else if (variables.TryGetValue(i, out var v))
{
v.HasGeneratedName = true;
}
}
}
}
numDisplayClassLocals = 0;
PerformAssignment(function);
}
static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type)
{
foreach (var item in type.GetMembers(m => IsLowerCase(m.Name)))
yield return item.Name;
}
public void Add(MethodDefinitionHandle localFunction, string name)
{
this.localFunctions[localFunction] = name;
}
static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type)
{
var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
foreach (var item in ns.Types)
public string TryGetExistingName(MethodDefinitionHandle localFunction)
{
if (IsLowerCase(item.Name))
yield return item.Name;
if (localFunctions.TryGetValue(localFunction, out var name))
return name;
return parentScope?.TryGetExistingName(localFunction);
}
}
static bool IsLowerCase(string name)
{
return name.Length > 0 && char.ToLower(name[0]) == name[0];
}
public string TryGetExistingName(ILVariable v)
{
if (variableMapping.TryGetValue(v, out var name))
return name;
return parentScope?.TryGetExistingName(v);
}
bool IsSetOrEventAccessor(IMethod method)
{
switch (method.AccessorKind)
public string TryGetExistingName(ILFunction function, int index)
{
case MethodSemanticsAttributes.Setter:
case MethodSemanticsAttributes.Adder:
case MethodSemanticsAttributes.Remover:
if (this.function == function)
{
return this.assignedLocalSignatureIndices[index];
}
else
{
return parentScope?.TryGetExistingName(function, index);
}
}
public void AssignNameToLocalSignatureIndex(ILFunction function, int index, string name)
{
var scope = this;
while (scope != null && scope.function != function)
scope = scope.parentScope;
Debug.Assert(scope != null);
scope.assignedLocalSignatureIndices[index] = name;
}
public bool IsReservedVariableName(string name, out int index)
{
if (reservedVariableNames.TryGetValue(name, out index))
return true;
default:
return false;
return parentScope?.IsReservedVariableName(name, out index) ?? false;
}
}
void PerformAssignment(ILFunction function)
{
var localFunctionMapping = new Dictionary<MethodDefinitionHandle, string>();
var variableMapping = new Dictionary<ILVariable, string>(ILVariableEqualityComparer.Instance);
var assignedLocalSignatureIndices = new Dictionary<(ILFunction, int), string>();
public void ReserveVariableName(string name, int index = 1)
{
reservedVariableNames[name] = index;
}
public string NextDisplayClassLocal()
{
return parentScope?.NextDisplayClassLocal() ?? "CS$<>8__locals" + (numDisplayClassLocals++);
}
public bool IsLoopCounter(ILVariable v)
{
return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true);
}
public string AssignNameIfUnassigned(ILVariable v)
{
if (variableMapping.TryGetValue(v, out var name))
return name;
return AssignName(v);
}
public string AssignName(ILVariable v)
{
// variable has no valid name
string newName = v.Name;
if (v.HasGeneratedName || !IsValidName(newName))
{
// don't use the name from the debug symbols if it looks like a generated name
// generate a new one based on how the variable is used
newName = GenerateNameForVariable(v);
}
// use the existing name and update index appended to future conflicts
string nameWithoutNumber = SplitName(newName, out int newIndex);
if (IsReservedVariableName(nameWithoutNumber, out int lastUsedIndex))
{
if (v.Type.IsKnownType(KnownTypeCode.Int32) && IsLoopCounter(v))
{
// special case for loop counters,
// we don't want them to be named i, i2, ..., but i, j, ...
newName = GenerateNameForVariable(v);
nameWithoutNumber = newName;
newIndex = 1;
}
}
if (IsReservedVariableName(nameWithoutNumber, out lastUsedIndex))
{
// name without number was already used
if (newIndex > lastUsedIndex)
{
// new index is larger than last, so we can use it
}
else
{
// new index is smaller or equal, so we use the next value
newIndex = lastUsedIndex + 1;
}
// resolve conflicts by appending the index to the new name:
newName = nameWithoutNumber + newIndex.ToString();
}
// update the last used index
ReserveVariableName(nameWithoutNumber, newIndex);
variableMapping.Add(v, newName);
return newName;
}
foreach (var inst in function.Descendants)
string GenerateNameForVariable(ILVariable variable)
{
if (inst is ILFunction { Kind: ILFunctionKind.LocalFunction } localFunction)
string proposedName = null;
if (variable.Type.IsKnownType(KnownTypeCode.Int32))
{
// test whether the variable might be a loop counter
if (loopCounters.Contains(variable))
{
// For loop variables, use i,j,k,l,m,n
for (char c = 'i'; c <= maxLoopVariableName; c++)
{
if (!IsReservedVariableName(c.ToString(), out _))
{
proposedName = c.ToString();
break;
}
}
}
}
// The ComponentResourceManager inside InitializeComponent must be named "resources",
// otherwise the WinForms designer won't load the Form.
if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager")
{
proposedName = "resources";
}
if (string.IsNullOrEmpty(proposedName))
{
// assign names to local functions
if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName))
newName = null;
if (newName == null)
var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
.Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
.Where(arg => !string.IsNullOrWhiteSpace(arg))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForAddress.Count > 0)
{
string nameWithoutNumber = "f";
if (!reservedVariableNames.TryGetValue(nameWithoutNumber, out int currentIndex))
proposedName = proposedNameForAddress[0];
}
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForStores = new HashSet<string>();
foreach (var store in variable.StoreInstructions)
{
if (store is StLoc stloc)
{
currentIndex = 1;
var name = GetNameFromInstruction(stloc.Value);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
int count = Math.Max(1, currentIndex) + 1;
reservedVariableNames[nameWithoutNumber] = count;
if (count > 1)
else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
{
newName = nameWithoutNumber + count.ToString();
var name = GetNameFromInstruction(match.TestedOperand);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
else
else if (store is PinnedRegion pinnedRegion)
{
newName = nameWithoutNumber;
var name = GetNameFromInstruction(pinnedRegion.Init);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
}
localFunction.Name = newName;
localFunction.ReducedMethod.Name = newName;
localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName;
}
else if (inst is IInstructionWithVariableOperand i)
{
var v = i.Variable;
// if there is already a valid name for the variable slot, just use it
if (variableMapping.TryGetValue(v, out string name))
if (proposedNameForStores.Count == 1)
{
v.Name = name;
continue;
proposedName = proposedNameForStores.Single();
}
switch (v.Kind)
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForLoads = variable.LoadInstructions
.Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForLoads.Count == 1)
{
case VariableKind.Parameter:
// Parameter names are handled in ILReader.CreateILVariable
// and CSharpDecompiler.FixParameterNames
break;
case VariableKind.InitializerTarget: // keep generated names
AddExistingName(reservedVariableNames, v.Name);
break;
case VariableKind.DisplayClassLocal:
v.Name = "CS$<>8__locals" + (numDisplayClassLocals++);
break;
case VariableKind.Local when v.Index != null:
if (assignedLocalSignatureIndices.TryGetValue((v.Function, v.Index.Value), out name))
{
// make sure all local ILVariables that refer to the same slot in the locals signature
// are assigned the same name.
v.Name = name;
}
else
{
v.Name = AssignName(v, variableMapping);
// Remember the newly assigned name:
assignedLocalSignatureIndices.Add((v.Function, v.Index.Value), v.Name);
}
break;
default:
v.Name = AssignName(v, variableMapping);
break;
proposedName = proposedNameForLoads[0];
}
}
else if (inst is (Call or LdFtn) and IInstructionWithMethodOperand m)
if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
{
// update references to local functions
if (m.Method is LocalFunctionMethod lf
&& localFunctionMapping.TryGetValue((MethodDefinitionHandle)lf.MetadataToken, out var name))
var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
.Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForStoresFromNewObj.Count == 1)
{
lf.Name = name;
proposedName = proposedNameForStoresFromNewObj[0];
}
}
if (string.IsNullOrEmpty(proposedName))
{
proposedName = GetNameByType(variable.Type);
}
// for generated names remove number-suffixes
return SplitName(proposedName, out _);
}
}
string AssignName(ILVariable v, Dictionary<ILVariable, string> variableMapping)
public void Run(ILFunction function, ILTransformContext context)
{
this.context = context;
function.AcceptVisitor(this, null);
}
Unit VisitChildren(ILInstruction inst, VariableScope context)
{
// variable has no valid name
string newName = v.Name;
if (v.HasGeneratedName || !IsValidName(newName))
foreach (var child in inst.Children)
{
// don't use the name from the debug symbols if it looks like a generated name
// generate a new one based on how the variable is used
newName = GenerateNameForVariable(v);
child.AcceptVisitor(this, context);
}
// use the existing name and update index appended to future conflicts
string nameWithoutNumber = SplitName(newName, out int newIndex);
if (reservedVariableNames.TryGetValue(nameWithoutNumber, out int lastUsedIndex))
return default;
}
protected override Unit Default(ILInstruction inst, VariableScope context)
{
if (inst is IInstructionWithVariableOperand { Variable: var v })
{
if (v.Type.IsKnownType(KnownTypeCode.Int32) && loopCounters.Contains(v))
// if there is already a valid name for the variable slot, just use it
string name = context.TryGetExistingName(v);
if (!string.IsNullOrEmpty(name))
{
// special case for loop counters,
// we don't want them to be named i, i2, ..., but i, j, ...
newName = GenerateNameForVariable(v);
nameWithoutNumber = newName;
newIndex = 1;
v.Name = name;
return VisitChildren(inst, context);
}
switch (v.Kind)
{
case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction:
// Parameter names of top-level functions are handled in ILReader.CreateILVariable
// and CSharpDecompiler.FixParameterNames
break;
case VariableKind.InitializerTarget: // keep generated names
case VariableKind.NamedArgument:
context.ReserveVariableName(v.Name);
break;
case VariableKind.UsingLocal when v.AddressCount == 0 && v.LoadCount == 0:
// using variables that are not read, will not be declared in source source
break;
case VariableKind.DisplayClassLocal:
v.Name = context.NextDisplayClassLocal();
break;
case VariableKind.Local when v.Index != null:
name = context.TryGetExistingName(v.Function, v.Index.Value);
if (name != null)
{
// make sure all local ILVariables that refer to the same slot in the locals signature
// are assigned the same name.
v.Name = name;
}
else
{
v.Name = context.AssignName(v);
context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name);
}
break;
default:
v.Name = context.AssignName(v);
break;
}
}
if (reservedVariableNames.TryGetValue(nameWithoutNumber, out lastUsedIndex))
return VisitChildren(inst, context);
}
protected internal override Unit VisitILFunction(ILFunction function, VariableScope context)
{
if (function.Kind == ILFunctionKind.LocalFunction)
{
// name without number was already used
if (newIndex > lastUsedIndex)
// assign names to local functions
if (!LocalFunctionDecompiler.ParseLocalFunctionName(function.Name, out _, out var newName) || !IsValidName(newName))
newName = null;
if (newName == null)
{
// new index is larger than last, so we can use it
string nameWithoutNumber = "f";
if (!context.IsReservedVariableName(nameWithoutNumber, out int currentIndex))
{
currentIndex = 1;
}
int count = Math.Max(1, currentIndex) + 1;
context.ReserveVariableName(nameWithoutNumber, count);
if (count > 1)
{
newName = nameWithoutNumber + count.ToString();
}
else
{
newName = nameWithoutNumber;
}
}
else
function.Name = newName;
function.ReducedMethod.Name = newName;
context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName);
}
var nestedContext = new VariableScope(function, this.context, context);
base.VisitILFunction(function, nestedContext);
if (function.Kind != ILFunctionKind.TopLevelFunction)
{
foreach (var p in function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0))
{
// new index is smaller or equal, so we use the next value
newIndex = lastUsedIndex + 1;
p.Name = nestedContext.AssignNameIfUnassigned(p);
}
// resolve conflicts by appending the index to the new name:
newName = nameWithoutNumber + newIndex.ToString();
}
// update the last used index
reservedVariableNames[nameWithoutNumber] = newIndex;
variableMapping.Add(v, newName);
return newName;
return default;
}
protected internal override Unit VisitCall(Call inst, VariableScope context)
{
if (inst.Method is LocalFunctionMethod m)
{
string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken);
if (!string.IsNullOrEmpty(name))
m.Name = name;
}
return base.VisitCall(inst, context);
}
protected internal override Unit VisitLdFtn(LdFtn inst, VariableScope context)
{
if (inst.Method is LocalFunctionMethod m)
{
string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken);
if (!string.IsNullOrEmpty(name))
m.Name = name;
}
return base.VisitLdFtn(inst, context);
}
static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type)
{
foreach (var item in type.GetMembers(m => IsLowerCase(m.Name)))
yield return item.Name;
}
static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type)
{
var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
foreach (var item in ns.Types)
{
if (IsLowerCase(item.Name))
yield return item.Name;
}
}
static bool IsLowerCase(string name)
{
return name.Length > 0 && char.ToLower(name[0]) == name[0];
}
/// <remarks>
@ -351,118 +626,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -351,118 +626,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
HashSet<ILVariable> CollectLoopCounters(ILFunction function)
{
var loopCounters = new HashSet<ILVariable>();
foreach (BlockContainer possibleLoop in function.Descendants.OfType<BlockContainer>())
{
if (possibleLoop.Kind != ContainerKind.For)
continue;
foreach (var inst in possibleLoop.Blocks.Last().Instructions)
{
if (HighLevelLoopTransform.MatchIncrement(inst, out var variable))
loopCounters.Add(variable);
}
}
return loopCounters;
}
string GenerateNameForVariable(ILVariable variable)
{
string proposedName = null;
if (variable.Type.IsKnownType(KnownTypeCode.Int32))
{
// test whether the variable might be a loop counter
if (loopCounters.Contains(variable))
{
// For loop variables, use i,j,k,l,m,n
for (char c = 'i'; c <= maxLoopVariableName; c++)
{
if (!reservedVariableNames.ContainsKey(c.ToString()))
{
proposedName = c.ToString();
break;
}
}
}
}
// The ComponentResourceManager inside InitializeComponent must be named "resources",
// otherwise the WinForms designer won't load the Form.
if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager")
{
proposedName = "resources";
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
.Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
.Where(arg => !string.IsNullOrWhiteSpace(arg))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForAddress.Count > 0)
{
proposedName = proposedNameForAddress[0];
}
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForStores = new HashSet<string>();
foreach (var store in variable.StoreInstructions)
{
if (store is StLoc stloc)
{
var name = GetNameFromInstruction(stloc.Value);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
{
var name = GetNameFromInstruction(match.TestedOperand);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
else if (store is PinnedRegion pinnedRegion)
{
var name = GetNameFromInstruction(pinnedRegion.Init);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
}
if (proposedNameForStores.Count == 1)
{
proposedName = proposedNameForStores.Single();
}
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForLoads = variable.LoadInstructions
.Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForLoads.Count == 1)
{
proposedName = proposedNameForLoads[0];
}
}
if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
{
var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
.Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForStoresFromNewObj.Count == 1)
{
proposedName = proposedNameForStoresFromNewObj[0];
}
}
if (string.IsNullOrEmpty(proposedName))
{
proposedName = GetNameByType(variable.Type);
}
// for generated names remove number-suffixes
return SplitName(proposedName, out _);
}
static string GetNameFromInstruction(ILInstruction inst)
{
switch (inst)
@ -566,22 +729,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -566,22 +729,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType);
}
string name = type.Kind switch {
TypeKind.Array => "array",
TypeKind.Pointer => "ptr",
TypeKind.TypeParameter => "val",
TypeKind.Unknown => "val",
TypeKind.Dynamic => "val",
TypeKind.ByReference => "reference",
TypeKind.Tuple => "tuple",
TypeKind.NInt => "num",
TypeKind.NUInt => "num",
_ => null
};
if (name != null)
{
return name;
}
string name;
if (type.IsAnonymousType())
{
name = "anon";
@ -590,13 +738,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -590,13 +738,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
name = "ex";
}
else if (type.Name.EndsWith("EventArgs", StringComparison.Ordinal))
{
name = "e";
}
else if (type.IsCSharpNativeIntegerType())
{
name = "num";
}
else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name))
{
name = type.Name;
name = type.Kind switch {
TypeKind.Array => "array",
TypeKind.Pointer => "ptr",
TypeKind.TypeParameter => "val",
TypeKind.Unknown => "val",
TypeKind.Dynamic => "val",
TypeKind.ByReference => "reference",
TypeKind.Tuple => "tuple",
TypeKind.NInt => "num",
TypeKind.NUInt => "num",
_ => type.Name
};
// remove the 'I' for interfaces
if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2]))
name = name.Substring(1);
@ -657,11 +820,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -657,11 +820,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (name.Length == 0)
return "obj";
else
return char.ToLower(name[0]) + name.Substring(1);
string lowerCaseName = char.ToLower(name[0]) + name.Substring(1);
if (CSharp.OutputVisitor.CSharpOutputVisitor.IsKeyword(lowerCaseName))
return null;
return lowerCaseName;
}
internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
{
if (!variableType.IsKnownType(KnownTypeCode.Object))
return variableType;

6
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -289,6 +289,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -289,6 +289,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
replacement.AcceptVisitor(this);
return;
}
if (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization(inst, context, out var replacement2))
{
context.Step("TransformRuntimeHelpersCreateSpanInitialization: single-dim", inst);
inst.ReplaceWith(replacement2);
return;
}
base.VisitCall(inst);
TransformAssignment.HandleCompoundAssign(inst, context);
}

2
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -448,7 +448,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -448,7 +448,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return inst != ldloca && inst.Parent is LdObj;
}
static bool IsPassedToInParameter(LdLoca ldloca)
internal static bool IsPassedToInParameter(LdLoca ldloca)
{
if (ldloca.Parent is not CallInstruction call)
{

21
ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

@ -142,9 +142,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -142,9 +142,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
DetermineCaptureAndDeclarationScope(info, useSite);
if (context.Function.Method.IsConstructor && localFunction.DeclarationScope == null)
if (context.Function.Method.IsConstructor)
{
localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite);
if (localFunction.DeclarationScope == null)
{
localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite);
}
else
{
localFunction.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(useSite, localFunction.DeclarationScope);
if (localFunction.DeclarationScope == null)
{
localFunction.DeclarationScope = (BlockContainer)context.Function.Body;
}
}
}
}
@ -658,6 +669,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -658,6 +669,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switch (useSite)
{
case CallInstruction call:
if (DelegateConstruction.MatchDelegateConstruction(useSite, out _, out _, out _))
{
// if this is a delegate construction, skip the use-site, because the capture scope
// was already determined when analyzing "this".
break;
}
int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1;
for (int i = call.Arguments.Count - 1; i >= firstArgumentIndex; i--)
{

16
ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

@ -668,13 +668,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -668,13 +668,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!FixCasesWithoutValue(sections, stringValues))
return false;
// switch contains case null:
if (nullValueCaseBlock != defaultBlock)
if (nullValueCaseBlock != null && nullValueCaseBlock != defaultBlock)
{
if (!AddNullSection(sections, stringValues, nullValueCaseBlock))
{
return false;
}
}
else if (leaveContainer != null && !defaultBlockJump.MatchLeave(leaveContainer))
{
if (!AddNullSection(sections, stringValues, (Leave)exitBlockJump))
{
return false;
}
}
context.Step(nameof(MatchLegacySwitchOnStringWithDict), instructions[i]);
bool keepAssignmentBefore = false;
if (switchValueVar.LoadCount > 2 || switchValue == null)
@ -741,6 +748,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -741,6 +748,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
bool AddNullSection(List<SwitchSection> sections, List<(string Value, int Index)> stringValues, Block nullValueCaseBlock)
{
return AddNullSection(sections, stringValues, new Branch(nullValueCaseBlock));
}
bool AddNullSection(List<SwitchSection> sections, List<(string Value, int Index)> stringValues, ILInstruction body)
{
var label = new LongSet(stringValues.Max(item => item.Index) + 1);
var possibleConflicts = sections.Where(sec => sec.Labels.Overlaps(label)).ToArray();
@ -753,7 +765,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -753,7 +765,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
possibleConflicts[0].Labels = possibleConflicts[0].Labels.ExceptWith(label);
}
stringValues.Add((null, (int)label.Values.First()));
sections.Add(new SwitchSection() { Labels = label, Body = new Branch(nullValueCaseBlock) });
sections.Add(new SwitchSection() { Labels = label, Body = body });
return true;
}

100
ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

@ -119,28 +119,57 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -119,28 +119,57 @@ namespace ICSharpCode.Decompiler.IL.Transforms
replacement = null;
if (!context.Settings.ArrayInitializers)
return false;
if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size))
if (!MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size))
return false;
if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
return false;
var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size);
return replacement != null;
}
internal static bool TransformRuntimeHelpersCreateSpanInitialization(Call inst, StatementTransformContext context, out ILInstruction replacement)
{
replacement = null;
if (!context.Settings.ArrayInitializers)
return false;
if (!MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field))
return false;
if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
return false;
if (IsSubPatternOfCpblkInitializer(inst))
return false;
var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
var elementTypeSize = elementType.GetSize();
if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0)
return false;
var size = initialValue.Length / elementTypeSize;
replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size);
return replacement != null;
}
private static bool IsSubPatternOfCpblkInitializer(Call inst)
{
if (inst.Parent is not AddressOf { Parent: Call { Parent: Cpblk cpblk } get_Item })
return false;
return MatchGetStaticFieldAddress(get_Item, out _);
}
private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, BlobReader initialValue, int size)
{
if (context.Settings.Utf8StringLiterals && elementType.IsKnownType(KnownTypeCode.Byte)
&& DecodeUTF8String(initialValue, size, out string text))
{
if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
{
var valuesList = new List<ILInstruction>();
var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
if (context.Settings.Utf8StringLiterals &&
elementType.IsKnownType(KnownTypeCode.Byte) &&
DecodeUTF8String(initialValue, size, out string text))
{
replacement = new LdStrUtf8(text);
return true;
}
if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
{
var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
return true;
}
}
return new LdStrUtf8(text);
}
return false;
var valuesList = new List<ILInstruction>();
if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
{
var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
return BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
}
return null;
}
private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text)
@ -153,9 +182,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -153,9 +182,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
for (int i = 0; i < size; i++)
{
byte val = blob.CurrentPointer[i];
// If the string has control characters, it's probably binary data and not a string.
if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t'))
if (val == 0 && i == size - 1 && size > 1)
{
// Allow explicit null-termination character.
}
else if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t'))
{
// If the string has control characters, it's probably binary data and not a string.
text = null;
return false;
}
@ -187,6 +220,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -187,6 +220,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
static bool MatchRuntimeHelpersCreateSpan(Call inst, StatementTransformContext context, out IType elementType, out FieldDefinition field)
{
field = default;
elementType = null;
if (!IsRuntimeHelpers(inst.Method.DeclaringType))
return false;
if (inst.Arguments.Count != 1)
return false;
if (inst.Method is not { Name: "CreateSpan", TypeArguments: [var type] })
return false;
elementType = type;
if (!inst.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdMemberToken(out var member))
return false;
if (member.MetadataToken.IsNil)
return false;
field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken);
return true;
}
bool DoTransformMultiDim(ILFunction function, Block body, int pos)
{
if (pos >= body.Instructions.Count - 2)
@ -334,7 +386,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -334,7 +386,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
static bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
{
if (input.MatchLdsFlda(out field))
return true;
@ -357,7 +409,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -357,7 +409,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return field != null;
}
static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices" };
static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices", TypeParameterCount: 0 };
unsafe bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove)
{

16
ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

@ -73,14 +73,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -73,14 +73,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
instType = newObjInst.Method.DeclaringType;
break;
case DefaultValue defaultVal:
if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !currentMethod.IsConstructor)
{
// on statement level (no other expressions on IL stack),
// prefer to keep local variables (but not stack slots),
// unless we are in a constructor (where inlining object initializers might be
// critical for the base ctor call)
return;
}
instType = defaultVal.Type;
break;
case Call c when c.Method.FullNameIs("System.Activator", "CreateInstance") && c.Method.TypeArguments.Count == 1:
@ -103,6 +95,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -103,6 +95,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return;
}
int initializerItemsCount = 0;
bool initializerContainsInitOnlyItems = false;
possibleIndexVariables.Clear();
currentPath.Clear();
isCollection = false;
@ -113,13 +106,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -113,13 +106,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// if the method is a setter we're dealing with an object initializer
// if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
while (pos + initializerItemsCount + 1 < block.Instructions.Count
&& IsPartOfInitializer(block.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind, context))
&& IsPartOfInitializer(block.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind, ref initializerContainsInitOnlyItems, context))
{
initializerItemsCount++;
}
// Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable
// directly after the possible initializer.
if (IsMethodCallOnVariable(block.Instructions[pos + initializerItemsCount + 1], v))
if (!initializerContainsInitOnlyItems && IsMethodCallOnVariable(block.Instructions[pos + initializerItemsCount + 1], v))
return;
// Calculate the correct number of statements inside the initializer:
// All index variables that were used in the initializer have Index set to -1.
@ -200,7 +193,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -200,7 +193,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool isCollection;
readonly Stack<HashSet<AccessPathElement>> pathStack = new Stack<HashSet<AccessPathElement>>();
bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, StatementTransformContext context)
bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, ref bool initializerContainsInitOnlyItems, StatementTransformContext context)
{
// Include any stores to local variables that are single-assigned and do not reference the initializer-variable
// in the list of possible index variables.
@ -255,6 +248,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -255,6 +248,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer)
blockKind = BlockKind.ObjectInitializer;
initializerContainsInitOnlyItems |= lastElement.Member is IProperty { Setter.IsInitOnly: true };
return true;
default:
return false;

14
ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

@ -89,7 +89,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -89,7 +89,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally)))
return false;
if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la))))
if (!storeInst.Variable.AddressInstructions.All(ValidateAddressUse))
return false;
if (storeInst.Variable.StoreInstructions.Count > 1)
return false;
@ -104,6 +104,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -104,6 +104,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike
}.WithILRange(storeInst);
return true;
bool ValidateAddressUse(LdLoca la)
{
if (!la.IsDescendantOf(tryFinally))
return false;
if (la.IsDescendantOf(tryFinally.TryBlock))
{
if (!(ILInlining.IsUsedAsThisPointerInCall(la) || ILInlining.IsPassedToInParameter(la)))
return false;
}
return true;
}
}
/// <summary>

5
ICSharpCode.Decompiler/Output/IAmbience.cs

@ -117,9 +117,14 @@ namespace ICSharpCode.Decompiler.Output @@ -117,9 +117,14 @@ namespace ICSharpCode.Decompiler.Output
/// Support C# 11 <c>operator checked</c>.
/// </summary>
SupportOperatorChecked = 0x100000,
/// <summary>
/// Support C# 7.2 <c>private protected</c>.
/// </summary>
UsePrivateProtectedAccessibility = 0x200000,
StandardConversionFlags = ShowParameterNames |
ShowAccessibility |
UsePrivateProtectedAccessibility |
ShowParameterList |
ShowParameterModifiers |
ShowParameterDefaultValues |

17
ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs

@ -96,6 +96,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -96,6 +96,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
this.symbolKind = SymbolKind.Destructor;
}
}
else if ((attributes & MethodAttributes.Static) != 0 && typeParameters.Length == 0)
{
// Operators that are explicit interface implementations are not marked
// with MethodAttributes.SpecialName or MethodAttributes.RTSpecialName
string name = this.Name;
int index = name.LastIndexOf('.');
if (index > 0)
{
name = name.Substring(index + 1);
if (name.StartsWith("op_", StringComparison.Ordinal)
&& CSharp.Syntax.OperatorDeclaration.GetOperatorType(name) != null)
{
this.symbolKind = SymbolKind.Operator;
}
}
}
this.IsExtensionMethod = (attributes & MethodAttributes.Static) == MethodAttributes.Static
&& (module.TypeSystemOptions & TypeSystemOptions.ExtensionMethods) == TypeSystemOptions.ExtensionMethods
&& def.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.Extension);

2
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -225,7 +225,7 @@ namespace ICSharpCode.Decompiler.Util @@ -225,7 +225,7 @@ namespace ICSharpCode.Decompiler.Util
yield return func(index++, element);
}
public static IEnumerable<(int, T)> WithIndex<T>(this ICollection<T> source)
public static IEnumerable<(int, T)> WithIndex<T>(this IEnumerable<T> source)
{
int index = 0;
foreach (var item in source)

39
ICSharpCode.Decompiler/Util/DelegateComparer.cs

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
#nullable enable
// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
namespace ICSharpCode.Decompiler.Util
{
public class DelegateComparer<T> : IComparer<T>
{
private readonly Func<T?, T?, int> func;
public DelegateComparer(Func<T?, T?, int> func)
{
this.func = func ?? throw new ArgumentNullException(nameof(func));
}
public int Compare(T? x, T? y)
{
return func(x, y);
}
}
}

3
ICSharpCode.Decompiler/Util/ResXResourceWriter.cs

@ -312,6 +312,9 @@ namespace ICSharpCode.Decompiler.Util @@ -312,6 +312,9 @@ namespace ICSharpCode.Decompiler.Util
public void Generate()
{
if (writer == null)
InitWriter();
if (written)
throw new InvalidOperationException("The resource is already generated.");

2
ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs

@ -62,7 +62,7 @@ namespace ILSpy.BamlDecompiler @@ -62,7 +62,7 @@ namespace ILSpy.BamlDecompiler
var typeDefinition = result.TypeName.HasValue ? typeSystem.MainModule.GetTypeDefinition(result.TypeName.Value.TopLevelTypeName) : null;
if (typeDefinition != null)
{
fileName = WholeProjectDecompiler.CleanUpPath(typeDefinition.ReflectionName) + ".xaml";
fileName = WholeProjectDecompiler.CleanUpPath(typeDefinition.ReflectionName + ".xaml");
var partialTypeInfo = new PartialTypeInfo(typeDefinition);
foreach (var member in result.GeneratedMembers)
{

8
ILSpy/Analyzers/AnalyzerEntityTreeNode.cs

@ -22,8 +22,8 @@ using System.Windows; @@ -22,8 +22,8 @@ using System.Windows;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
using ICSharpCode.ILSpyX.TreeView;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
namespace ICSharpCode.ILSpy.Analyzers
{
@ -46,8 +46,14 @@ namespace ICSharpCode.ILSpy.Analyzers @@ -46,8 +46,14 @@ namespace ICSharpCode.ILSpy.Analyzers
MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(this.Member.ParentModule?.MetadataFile, this.Member.MetadataToken)));
}
public override object ToolTip => Member?.ParentModule?.MetadataFile?.FileName;
public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies)
{
if (Member == null)
{
return true;
}
foreach (LoadedAssembly asm in removedAssemblies)
{
if (this.Member.ParentModule.MetadataFile == asm.GetMetadataFileOrNull())

24
ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs

@ -17,9 +17,12 @@ @@ -17,9 +17,12 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Windows;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.TreeView;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
@ -38,6 +41,8 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes @@ -38,6 +41,8 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
public override object Text => analyzedModule.AssemblyName;
public override object ToolTip => analyzedModule.MetadataFile?.FileName;
protected override void LoadChildren()
{
foreach (var lazy in Analyzers)
@ -62,5 +67,24 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes @@ -62,5 +67,24 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
}
public override IEntity Member => null;
public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies)
{
if (analyzedModule == null)
{
return true;
}
foreach (LoadedAssembly asm in removedAssemblies)
{
if (this.analyzedModule.MetadataFile == asm.GetMetadataFileOrNull())
return false; // remove this node
}
this.Children.RemoveAll(
delegate (SharpTreeNode n) {
AnalyzerTreeNode an = n as AnalyzerTreeNode;
return an == null || !an.HandleAssemblyListChanged(removedAssemblies, addedAssemblies);
});
return true;
}
}
}

12
ILSpy/App.xaml

@ -3,7 +3,9 @@ @@ -3,7 +3,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:styles="urn:TomsToolbox.Wpf.Styles"
xmlns:toms="urn:TomsToolbox"
xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes">
xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes"
xmlns:composition="urn:TomsToolbox.Composition"
xmlns:util="clr-namespace:ICSharpCode.ILSpy.Util">
<Application.Resources>
<Style x:Key="DialogWindow" TargetType="{x:Type Window}">
<Setter Property="ShowInTaskbar" Value="False" />
@ -24,7 +26,13 @@ @@ -24,7 +26,13 @@
</Style>
<Style TargetType="ScrollViewer">
<Setter Property="toms:AdvancedScrollWheelBehavior.Attach" Value="WithAnimation" />
<Setter Property="toms:StyleBindings.Behaviors">
<Setter.Value>
<toms:BehaviorCollection>
<toms:AdvancedScrollWheelBehavior UseScrollingAnimation="{Binding Path=(util:SettingsService.DisplaySettings).EnableSmoothScrolling, Source={composition:Import util:SettingsService}}"/>
</toms:BehaviorCollection>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>

2
ILSpy/App.xaml.cs

@ -69,6 +69,8 @@ namespace ICSharpCode.ILSpy @@ -69,6 +69,8 @@ namespace ICSharpCode.ILSpy
var cmdArgs = Environment.GetCommandLineArgs().Skip(1);
CommandLineArguments = CommandLineArguments.Create(cmdArgs);
// This is only a temporary, read only handle to the settings service to access the AllowMultipleInstances setting before DI is initialized.
// At runtime, you must use the service via DI!
var settingsService = new SettingsService();
bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true)

2
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -767,6 +767,8 @@ namespace ICSharpCode.ILSpy.AssemblyTree @@ -767,6 +767,8 @@ namespace ICSharpCode.ILSpy.AssemblyTree
}
else
{
// ensure that we are only connected once to the event, else we might get multiple notifications
ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed;
ContextMenuProvider.ContextMenuClosed += ContextMenuClosed;
}
}

2
ILSpy/Commands/GeneratePdbContextMenuEntry.cs

@ -71,7 +71,7 @@ namespace ICSharpCode.ILSpy @@ -71,7 +71,7 @@ namespace ICSharpCode.ILSpy
return;
}
SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb";
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName, ".pdb");
dlg.Filter = Resources.PortablePDBPdbAllFiles;
dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName);
if (dlg.ShowDialog() != true)

2
ILSpy/Commands/SelectPdbContextMenuEntry.cs

@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy @@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy
if (assembly == null)
return;
OpenFileDialog dlg = new OpenFileDialog();
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb";
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName, ".pdb");
dlg.Filter = Resources.PortablePDBPdbAllFiles;
dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName);
if (dlg.ShowDialog() != true)

1
ILSpy/Controls/MainToolBar.xaml

@ -75,6 +75,7 @@ @@ -75,6 +75,7 @@
<Grid Margin="2,0">
<ItemsControl
ItemsSource="{Binding SelectedItem.LanguageVersions, ElementName=languageComboBox, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding SelectedItem.HasLanguageVersions, ElementName=languageComboBox, Converter={toms:BooleanToVisibilityConverter}}"
DisplayMemberPath="DisplayName" Height="0" Margin="15,0" />
<ComboBox Name="languageVersionComboBox" DisplayMemberPath="DisplayName" MaxDropDownHeight="Auto"
ToolTip="{x:Static properties:Resources.SelectVersionDropdownTooltip}"

12
ILSpy/Controls/ZoomScrollViewer.xaml

@ -1,10 +1,18 @@ @@ -1,10 +1,18 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:ICSharpCode.ILSpy.Controls"
xmlns:toms="urn:TomsToolbox">
xmlns:toms="urn:TomsToolbox"
xmlns:composition="urn:TomsToolbox.Composition"
xmlns:util="clr-namespace:ICSharpCode.ILSpy.Util">
<Style TargetType="{x:Type Controls:ZoomScrollViewer}">
<Setter Property="toms:AdvancedScrollWheelBehavior.Attach" Value="WithAnimation" />
<Setter Property="toms:StyleBindings.Behaviors">
<Setter.Value>
<toms:BehaviorCollection>
<toms:AdvancedScrollWheelBehavior UseScrollingAnimation="{Binding Path=(util:SettingsService.DisplaySettings).EnableSmoothScrolling, Source={composition:Import util:SettingsService}}"/>
</toms:BehaviorCollection>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:ZoomScrollViewer}">

8
ILSpy/ExtensionMethods.cs

@ -76,11 +76,15 @@ namespace ICSharpCode.ILSpy @@ -76,11 +76,15 @@ namespace ICSharpCode.ILSpy
return result;
}
public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService)
public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService, LanguageVersion languageVersion)
{
var decompilerSettings = settingsService.DecompilerSettings.Clone();
if (!Enum.TryParse(languageVersion?.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion))
csharpLanguageVersion = Decompiler.CSharp.LanguageVersion.Latest;
decompilerSettings.SetLanguageVersion(csharpLanguageVersion);
return file
.GetLoadedAssembly()
.GetTypeSystemOrNull(DecompilerTypeSystem.GetOptions(settingsService.DecompilerSettings));
.GetTypeSystemOrNull(DecompilerTypeSystem.GetOptions(decompilerSettings));
}
#region DPI independence

1
ILSpy/ILSpy.csproj

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<RollForward>major</RollForward>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>

25
ILSpy/Languages/CSharpLanguage.cs

@ -578,10 +578,18 @@ namespace ICSharpCode.ILSpy @@ -578,10 +578,18 @@ namespace ICSharpCode.ILSpy
CSharpAmbience ambience = new CSharpAmbience();
// Do not forget to update CSharpAmbienceTests.ILSpyMainTreeViewTypeFlags, if this ever changes.
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList | ConversionFlags.PlaceReturnTypeAfterParameterList;
if (SettingsService.DecompilerSettings.LiftNullables)
var decompilerSettings = SettingsService.DecompilerSettings.Clone();
if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
decompilerSettings.SetLanguageVersion(languageVersion);
if (decompilerSettings.LiftNullables)
{
ambience.ConversionFlags |= ConversionFlags.UseNullableSpecifierForValueTypes;
}
if (decompilerSettings.IntroducePrivateProtectedAccessibility)
{
ambience.ConversionFlags |= ConversionFlags.UsePrivateProtectedAccessibility;
}
return ambience;
}
@ -781,7 +789,11 @@ namespace ICSharpCode.ILSpy @@ -781,7 +789,11 @@ namespace ICSharpCode.ILSpy
public override bool ShowMember(IEntity member)
{
MetadataFile assembly = member.ParentModule.MetadataFile;
return showAllMembers || !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, SettingsService.DecompilerSettings);
var decompilerSettings = SettingsService.DecompilerSettings.Clone();
if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
decompilerSettings.SetLanguageVersion(languageVersion);
return showAllMembers || !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, decompilerSettings);
}
public override RichText GetRichTextTooltip(IEntity entity)
@ -790,7 +802,10 @@ namespace ICSharpCode.ILSpy @@ -790,7 +802,10 @@ namespace ICSharpCode.ILSpy
var output = new StringWriter();
var decoratedWriter = new TextWriterTokenWriter(output);
var writer = new CSharpHighlightingTokenWriter(TokenWriter.InsertRequiredSpaces(decoratedWriter), locatable: decoratedWriter);
var settings = SettingsService.DecompilerSettings;
var settings = SettingsService.DecompilerSettings.Clone();
if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
settings.SetLanguageVersion(languageVersion);
if (!settings.LiftNullables)
{
flags &= ~ConversionFlags.UseNullableSpecifierForValueTypes;
@ -815,6 +830,10 @@ namespace ICSharpCode.ILSpy @@ -815,6 +830,10 @@ namespace ICSharpCode.ILSpy
{
flags |= ConversionFlags.SupportInitAccessors;
}
if (settings.IntroducePrivateProtectedAccessibility)
{
flags |= ConversionFlags.UsePrivateProtectedAccessibility;
}
if (entity is IMethod m && m.IsLocalFunction)
{
writer.WriteIdentifier(Identifier.Create("(local)"));

2
ILSpy/Languages/LanguageService.cs

@ -35,10 +35,12 @@ namespace ICSharpCode.ILSpy @@ -35,10 +35,12 @@ namespace ICSharpCode.ILSpy
[Shared]
public class LanguageService : ObservableObjectBase
{
private readonly SettingsService settingsService;
private readonly LanguageSettings languageSettings;
public LanguageService(IEnumerable<Language> languages, SettingsService settingsService, DockWorkspace dockWorkspace)
{
this.settingsService = settingsService;
languageSettings = settingsService.SessionSettings.LanguageSettings;
var sortedLanguages = languages.ToList();

3
ILSpy/MainWindow.xaml.cs

@ -102,6 +102,9 @@ namespace ICSharpCode.ILSpy @@ -102,6 +102,9 @@ namespace ICSharpCode.ILSpy
MessageBus.Send(this, new ApplySessionSettingsEventArgs(sessionSettings));
sessionSettings.WindowBounds = this.RestoreBounds;
// store window state in settings only if it's not minimized
if (this.WindowState != WindowState.Minimized)
sessionSettings.WindowState = this.WindowState;
sessionSettings.DockLayout.Serialize(new(DockManager));
snapshot.Save();

2
ILSpy/Metadata/CorTables/EventTableTreeNode.cs

@ -94,7 +94,7 @@ namespace ICSharpCode.ILSpy.Metadata @@ -94,7 +94,7 @@ namespace ICSharpCode.ILSpy.Metadata
IEntity IMemberTreeNode.Member {
get {
return ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
return ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
}
}

2
ILSpy/Metadata/CorTables/FieldTableTreeNode.cs

@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.Metadata @@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.Metadata
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(fieldDef.Name):X} \"{Name}\"";
IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(fieldDef.Signature);

2
ILSpy/Metadata/CorTables/MethodTableTreeNode.cs

@ -131,7 +131,7 @@ namespace ICSharpCode.ILSpy.Metadata @@ -131,7 +131,7 @@ namespace ICSharpCode.ILSpy.Metadata
}
}
IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
public MethodDefEntry(MetadataFile metadataFile, MethodDefinitionHandle handle)
{

2
ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs

@ -92,7 +92,7 @@ namespace ICSharpCode.ILSpy.Metadata @@ -92,7 +92,7 @@ namespace ICSharpCode.ILSpy.Metadata
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(propertyDef.Name):X} \"{Name}\"";
IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(propertyDef.Signature);

2
ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs

@ -173,7 +173,7 @@ namespace ICSharpCode.ILSpy.Metadata @@ -173,7 +173,7 @@ namespace ICSharpCode.ILSpy.Metadata
}
}
IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
public TypeDefEntry(MetadataFile metadataFile, TypeDefinitionHandle handle)
{

8
ILSpy/Options/DisplaySettings.cs

@ -148,6 +148,12 @@ namespace ICSharpCode.ILSpy.Options @@ -148,6 +148,12 @@ namespace ICSharpCode.ILSpy.Options
set => SetProperty(ref showRawOffsetsAndBytesBeforeInstruction, value);
}
private bool enableSmoothScrolling;
public bool EnableSmoothScrolling {
get => enableSmoothScrolling;
set => SetProperty(ref enableSmoothScrolling, value);
}
public XName SectionName => "DisplaySettings";
public void LoadFromXml(XElement section)
@ -172,6 +178,7 @@ namespace ICSharpCode.ILSpy.Options @@ -172,6 +178,7 @@ namespace ICSharpCode.ILSpy.Options
UseNestedNamespaceNodes = (bool?)section.Attribute("UseNestedNamespaceNodes") ?? false;
ShowRawOffsetsAndBytesBeforeInstruction = (bool?)section.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false;
StyleWindowTitleBar = (bool?)section.Attribute("StyleWindowTitleBar") ?? false;
EnableSmoothScrolling = (bool?)section.Attribute("EnableSmoothScrolling") ?? true;
}
public XElement SaveToXml()
@ -198,6 +205,7 @@ namespace ICSharpCode.ILSpy.Options @@ -198,6 +205,7 @@ namespace ICSharpCode.ILSpy.Options
section.SetAttributeValue("UseNestedNamespaceNodes", UseNestedNamespaceNodes);
section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", ShowRawOffsetsAndBytesBeforeInstruction);
section.SetAttributeValue("StyleWindowTitleBar", StyleWindowTitleBar);
section.SetAttributeValue("EnableSmoothScrolling", EnableSmoothScrolling);
return section;
}

1
ILSpy/Options/DisplaySettingsPanel.xaml

@ -78,6 +78,7 @@ @@ -78,6 +78,7 @@
<StackPanel Margin="3">
<CheckBox IsChecked="{Binding Settings.SortResults}" Content="{x:Static properties:Resources.SortResultsFitness}"></CheckBox>
<CheckBox IsChecked="{Binding Settings.StyleWindowTitleBar}" Content="{x:Static properties:Resources.StyleTheWindowTitleBar}"></CheckBox>
<CheckBox IsChecked="{Binding Settings.EnableSmoothScrolling}" Content="{x:Static properties:Resources.EnableSmoothScrolling}"></CheckBox>
</StackPanel>
</GroupBox>
</StackPanel>

37
ILSpy/Properties/Resources.Designer.cs generated

@ -1080,6 +1080,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1080,6 +1080,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Introduce &apos;private protected&apos; accessibility.
/// </summary>
public static string DecompilerSettings_IntroducePrivateProtectedAccessibility {
get {
return ResourceManager.GetString("DecompilerSettings.IntroducePrivateProtectedAccessibility", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Introduce static local functions.
/// </summary>
@ -1118,6 +1127,16 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1118,6 +1127,16 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Object.
/// </summary>
public static object DecompilerSettings_LifetimeAnnotations {
get {
object obj = ResourceManager.GetObject("DecompilerSettings.LifetimeAnnotations", resourceCulture);
return ((object)(obj));
}
}
/// <summary>
/// Looks up a localized string similar to Use nint/nuint types.
/// </summary>
@ -1325,6 +1344,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1325,6 +1344,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Sort custom attributes.
/// </summary>
public static string DecompilerSettings_SortCustomAttributes {
get {
return ResourceManager.GetString("DecompilerSettings.SortCustomAttributes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detect switch on integer even if IL code does not use a jump table.
/// </summary>
@ -1722,6 +1750,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1722,6 +1750,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Enable smooth scrolling.
/// </summary>
public static string EnableSmoothScrolling {
get {
return ResourceManager.GetString("EnableSmoothScrolling", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enable word wrap.
/// </summary>

9
ILSpy/Properties/Resources.resx

@ -462,6 +462,9 @@ Are you sure you want to continue?</value> @@ -462,6 +462,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.ShowInfoFromDebugSymbolsIfAvailable" xml:space="preserve">
<value>Show info from debug symbols, if available</value>
</data>
<data name="DecompilerSettings.SortCustomAttributes" xml:space="preserve">
<value>Sort custom attributes</value>
</data>
<data name="DecompilerSettings.SparseIntegerSwitch" xml:space="preserve">
<value>Detect switch on integer even if IL code does not use a jump table</value>
</data>
@ -591,6 +594,9 @@ Are you sure you want to continue?</value> @@ -591,6 +594,9 @@ Are you sure you want to continue?</value>
<data name="EnableFoldingBlocksBraces" xml:space="preserve">
<value>Enable folding on all blocks in braces</value>
</data>
<data name="EnableSmoothScrolling" xml:space="preserve">
<value>Enable smooth scrolling</value>
</data>
<data name="EnableWordWrap" xml:space="preserve">
<value>Enable word wrap</value>
</data>
@ -1102,4 +1108,7 @@ Do you want to continue?</value> @@ -1102,4 +1108,7 @@ Do you want to continue?</value>
<data name="_Window" xml:space="preserve">
<value>_Window</value>
</data>
<data name="DecompilerSettings.IntroducePrivateProtectedAccessibility" xml:space="preserve">
<value>Introduce 'private protected' accessibility</value>
</data>
</root>

6
ILSpy/Properties/Resources.zh-Hans.resx

@ -369,6 +369,9 @@ @@ -369,6 +369,9 @@
<data name="DecompilerSettings.IntroduceLocalFunctions" xml:space="preserve">
<value>引入局部函数(local functions)</value>
</data>
<data name="DecompilerSettings.IntroducePrivateProtectedAccessibility" xml:space="preserve">
<value />
</data>
<data name="DecompilerSettings.IntroduceStaticLocalFunctions" xml:space="preserve">
<value>引入静态局部函数(static local functions)</value>
</data>
@ -435,6 +438,9 @@ @@ -435,6 +438,9 @@
<data name="DecompilerSettings.ShowInfoFromDebugSymbolsIfAvailable" xml:space="preserve">
<value>显示调试符号中的信息(如果可用)</value>
</data>
<data name="DecompilerSettings.SortCustomAttributes" xml:space="preserve">
<value />
</data>
<data name="DecompilerSettings.SparseIntegerSwitch" xml:space="preserve">
<value>检测整型 switch 即使 IL 代码不使用跳转表</value>
</data>

10
ILSpy/Search/SearchPane.xaml.cs

@ -266,6 +266,7 @@ namespace ICSharpCode.ILSpy.Search @@ -266,6 +266,7 @@ namespace ICSharpCode.ILSpy.Search
searchTerm,
(SearchMode)searchModeComboBox.SelectedIndex,
assemblyTreeModel.CurrentLanguage,
assemblyTreeModel.CurrentLanguageVersion,
treeNodeFactory,
settingsService);
currentSearch = startedSearch;
@ -295,6 +296,7 @@ namespace ICSharpCode.ILSpy.Search @@ -295,6 +296,7 @@ namespace ICSharpCode.ILSpy.Search
readonly SearchRequest searchRequest;
readonly SearchMode searchMode;
readonly Language language;
readonly LanguageVersion languageVersion;
readonly ApiVisibility apiVisibility;
readonly ITreeNodeFactory treeNodeFactory;
readonly SettingsService settingsService;
@ -302,7 +304,7 @@ namespace ICSharpCode.ILSpy.Search @@ -302,7 +304,7 @@ namespace ICSharpCode.ILSpy.Search
public IProducerConsumerCollection<SearchResult> ResultQueue { get; } = new ConcurrentQueue<SearchResult>();
public RunningSearch(IList<LoadedAssembly> assemblies, string searchTerm, SearchMode searchMode,
Language language, ITreeNodeFactory treeNodeFactory, SettingsService settingsService)
Language language, LanguageVersion languageVersion, ITreeNodeFactory treeNodeFactory, SettingsService settingsService)
{
this.assemblies = assemblies;
this.language = language;
@ -471,7 +473,11 @@ namespace ICSharpCode.ILSpy.Search @@ -471,7 +473,11 @@ namespace ICSharpCode.ILSpy.Search
request.RegEx = regex;
request.SearchResultFactory = new SearchResultFactory(language);
request.TreeNodeFactory = this.treeNodeFactory;
request.DecompilerSettings = settingsService.DecompilerSettings;
var decompilerSettings = settingsService.DecompilerSettings.Clone();
if (!Enum.TryParse(this.languageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
decompilerSettings.SetLanguageVersion(languageVersion);
request.DecompilerSettings = settingsService.DecompilerSettings.Clone();
return request;
}

6
ILSpy/Search/SearchPaneModel.cs

@ -54,6 +54,12 @@ namespace ICSharpCode.ILSpy.Search @@ -54,6 +54,12 @@ namespace ICSharpCode.ILSpy.Search
SearchTerm = e.SearchTerm;
Show();
};
MessageBus<ApplySessionSettingsEventArgs>.Subscribers += ApplySessionSettings;
}
private void ApplySessionSettings(object sender, ApplySessionSettingsEventArgs e)
{
e.SessionSettings.SelectedSearchMode = SessionSettings.SelectedSearchMode;
}
public SearchModeModel[] SearchModes { get; } = [

38
ILSpy/SolutionWriter.cs

@ -28,7 +28,6 @@ using System.Threading.Tasks; @@ -28,7 +28,6 @@ using System.Threading.Tasks;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX;
@ -99,13 +98,42 @@ namespace ICSharpCode.ILSpy @@ -99,13 +98,42 @@ namespace ICSharpCode.ILSpy
{
var result = new AvalonEditTextOutput();
var duplicates = new HashSet<string>();
if (assemblies.Any(asm => !duplicates.Add(asm.ShortName)))
var assembliesByShortName = assemblies.ToLookup(_ => _.ShortName);
bool first = true;
bool abort = false;
foreach (var item in assembliesByShortName)
{
result.WriteLine("Duplicate assembly names selected, cannot generate a solution.");
return result;
var enumerator = item.GetEnumerator();
if (!enumerator.MoveNext())
continue;
var firstAssembly = enumerator.Current;
if (!enumerator.MoveNext())
continue;
if (first)
{
result.WriteLine("Duplicate assembly names selected, cannot generate a solution:");
abort = true;
}
result.Write("- " + firstAssembly.Text + " conflicts with ");
first = true;
do
{
var asm = enumerator.Current;
if (!first)
result.Write(", ");
result.Write(asm.Text);
first = false;
} while (enumerator.MoveNext());
result.WriteLine();
first = false;
}
if (abort)
return result;
Stopwatch stopwatch = Stopwatch.StartNew();
try

25
ILSpy/TextView/DecompilerTextView.cs

@ -1091,7 +1091,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -1091,7 +1091,7 @@ namespace ICSharpCode.ILSpy.TextView
SaveFileDialog dlg = new SaveFileDialog();
dlg.DefaultExt = language.FileExtension;
dlg.Filter = language.Name + "|*" + language.FileExtension + Properties.Resources.AllFiles;
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(treeNodes.First().ToString()) + language.FileExtension;
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(treeNodes.First().ToString(), language.FileExtension);
if (dlg.ShowDialog() == true)
{
SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), dlg.FileName);
@ -1447,17 +1447,20 @@ namespace ICSharpCode.ILSpy.TextView @@ -1447,17 +1447,20 @@ namespace ICSharpCode.ILSpy.TextView
if (resourceStream != null)
{
IHighlightingDefinition highlightingDefinition;
using (resourceStream)
using (XmlTextReader reader = new XmlTextReader(resourceStream))
{
highlightingDefinition = HighlightingLoader.Load(reader, manager);
}
manager.RegisterHighlighting(
name, extensions,
delegate {
using (resourceStream)
using (XmlTextReader reader = new XmlTextReader(resourceStream))
{
var highlightingDefinition = HighlightingLoader.Load(reader, manager);
ThemeManager.Current.ApplyHighlightingColors(highlightingDefinition);
return highlightingDefinition;
}
});
name, extensions,
delegate {
ThemeManager.Current.ApplyHighlightingColors(highlightingDefinition);
return highlightingDefinition;
});
}
}
}

2
ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs

@ -89,7 +89,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -89,7 +89,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
var referencedModule = resolver.Resolve(r);
if (referencedModule != null)
{
var module = (MetadataModule)referencedModule.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule;
var module = (MetadataModule)referencedModule.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule;
foreach (var childRef in referencedModule.AssemblyReferences)
this.Children.Add(new AssemblyReferenceTreeNode(module, childRef, parentAssembly));
}

2
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -475,7 +475,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -475,7 +475,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
if (string.IsNullOrEmpty(language.ProjectFileExtension))
return false;
SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(LoadedAssembly.ShortName) + language.ProjectFileExtension;
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(LoadedAssembly.ShortName, language.ProjectFileExtension);
dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*";
if (dlg.ShowDialog() == true)
{

2
ILSpy/TreeNodes/EventTreeNode.cs

@ -52,7 +52,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -52,7 +52,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
private IEvent GetEventDefinition()
{
return ((MetadataModule)EventDefinition.ParentModule?.MetadataFile
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
?.MainModule)?.GetDefinition((EventDefinitionHandle)EventDefinition.MetadataToken) ?? EventDefinition;
}

2
ILSpy/TreeNodes/FieldTreeNode.cs

@ -44,7 +44,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -44,7 +44,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
private IField GetFieldDefinition()
{
return ((MetadataModule)FieldDefinition.ParentModule?.MetadataFile
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
?.MainModule)?.GetDefinition((FieldDefinitionHandle)FieldDefinition.MetadataToken) ?? FieldDefinition;
}

2
ILSpy/TreeNodes/MethodTreeNode.cs

@ -44,7 +44,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -44,7 +44,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
private IMethod GetMethodDefinition()
{
return ((MetadataModule)MethodDefinition.ParentModule?.MetadataFile
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
?.MainModule)?.GetDefinition((MethodDefinitionHandle)MethodDefinition.MetadataToken) ?? MethodDefinition;
}

2
ILSpy/TreeNodes/PropertyTreeNode.cs

@ -54,7 +54,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -54,7 +54,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
private IProperty GetPropertyDefinition()
{
return ((MetadataModule)PropertyDefinition.ParentModule?.MetadataFile
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
?.MainModule)?.GetDefinition((PropertyDefinitionHandle)PropertyDefinition.MetadataToken) ?? PropertyDefinition;
}

2
ILSpy/TreeNodes/ReferenceFolderTreeNode.cs

@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
protected override void LoadChildren()
{
var metadata = module.Metadata;
var metadataModule = (MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule;
var metadataModule = (MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule;
foreach (var r in module.AssemblyReferences.OrderBy(r => r.Name))
this.Children.Add(new AssemblyReferenceTreeNode(metadataModule, r, parentAssembly));
foreach (var r in metadata.GetModuleReferences().OrderBy(r => metadata.GetString(metadata.GetModuleReference(r).Name)))

2
ILSpy/TreeNodes/TypeTreeNode.cs

@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
{
return ((MetadataModule)ParentAssemblyNode.LoadedAssembly
.GetMetadataFileOrNull()
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
?.MainModule)?.GetDefinition((SRM.TypeDefinitionHandle)TypeDefinition.MetadataToken);
}

Loading…
Cancel
Save