Browse Source

Merge

pull/3387/head
Christoph Wille 4 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. 16
      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:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - 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: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif

14
Directory.Packages.props

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

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

@ -18,7 +18,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
@ -330,6 +329,45 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return tempFile; 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) public static List<string> GetPreprocessorSymbols(CompilerOptions flags)
{ {
var preprocessorSymbols = new List<string>(); var preprocessorSymbols = new List<string>();
@ -419,6 +457,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
sourceFileNames.Add(targetFrameworkAttributeSnippetFile.Value); sourceFileNames.Add(targetFrameworkAttributeSnippetFile.Value);
} }
if (targetNet40)
{
sourceFileNames.Add(nonEmbeddedAttributesSnippetFile.Value);
}
var preprocessorSymbols = GetPreprocessorSymbols(flags); var preprocessorSymbols = GetPreprocessorSymbols(flags);
if ((flags & CompilerOptions.UseMcsMask) == 0) if ((flags & CompilerOptions.UseMcsMask) == 0)

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

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

6
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

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

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

@ -283,7 +283,17 @@ namespace ICSharpCode.Decompiler.Tests.Output
[TestCase(ILSpyMainTreeViewMemberFlags, "this[int] : int")] [TestCase(ILSpyMainTreeViewMemberFlags, "this[int] : int")]
public void Indexer(ConversionFlags flags, string expectedOutput) 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; ambience.ConversionFlags = flags;
Assert.That(ambience.ConvertSymbol(prop), Is.EqualTo(expectedOutput)); Assert.That(ambience.ConvertSymbol(prop), Is.EqualTo(expectedOutput));
@ -323,7 +333,12 @@ namespace ICSharpCode.Decompiler.Tests.Output
readonly struct ReadonlyStruct { } readonly struct ReadonlyStruct { }
readonly ref struct ReadonlyRefStruct { } readonly ref struct ReadonlyRefStruct { }
class Program interface Interface
{
int this[int x] { get; }
}
class Program : Interface
{ {
int test; int test;
const int TEST2 = 2; const int TEST2 = 2;
@ -336,6 +351,12 @@ namespace ICSharpCode.Decompiler.Tests.Output
} }
} }
int Interface.this[int index] {
get {
return index;
}
}
public event EventHandler ProgramChanged; public event EventHandler ProgramChanged;
public event EventHandler SomeEvent { public event EventHandler SomeEvent {

62
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -233,22 +233,21 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
NullPropagation = false, settings.NullPropagation = false;
// legacy csc generates a dead store in debug builds // legacy csc generates a dead store in debug builds
RemoveDeadStores = (cscOptions == CompilerOptions.None), settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
FileScopedNamespaces = false, settings.FileScopedNamespaces = false;
}); });
} }
[Test] [Test]
public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) 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 // legacy csc generates a dead store in debug builds
RemoveDeadStores = (cscOptions == CompilerOptions.None), settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
SwitchExpressions = false, settings.SwitchExpressions = false;
FileScopedNamespaces = false,
}); });
} }
@ -267,7 +266,10 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) 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] [Test]
@ -293,9 +295,9 @@ namespace ICSharpCode.Decompiler.Tests
{ {
await RunForLibrary( await RunForLibrary(
cscOptions: cscOptions, cscOptions: cscOptions,
decompilerSettings: new DecompilerSettings { configureDecompiler: settings => {
UseEnhancedUsing = false, settings.UseEnhancedUsing = false;
FileScopedNamespaces = false, settings.FileScopedNamespaces = false;
} }
); );
} }
@ -327,11 +329,11 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{ {
DecompilerSettings settings = Tester.GetSettings(cscOptions); await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
// legacy csc generates a dead store in debug builds // legacy csc generates a dead store in debug builds
settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false; settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false;
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings); });
} }
[Test] [Test]
@ -440,9 +442,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {
var settings = Tester.GetSettings(cscOptions); await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseDebugSymbols = false);
settings.UseDebugSymbols = false;
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings);
} }
[Test] [Test]
@ -474,7 +474,7 @@ namespace ICSharpCode.Decompiler.Tests
{ {
await RunForLibrary( await RunForLibrary(
cscOptions: cscOptions, cscOptions: cscOptions,
decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false, FileScopedNamespaces = false } configureDecompiler: settings => { settings.UseEnhancedUsing = false; }
); );
} }
@ -499,7 +499,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) 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] [Test]
@ -586,6 +586,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions); await RunForLibrary(cscOptions: cscOptions);
} }
[Test]
public async Task Comparisons([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}
[Test] [Test]
public async Task ConstantsTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public async Task ConstantsTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {
@ -595,7 +601,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) 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] [Test]
@ -706,12 +712,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions); 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 csFile = Path.Combine(TestCasePath, testName + ".cs");
var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe"); var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe");
@ -733,7 +739,9 @@ namespace ICSharpCode.Decompiler.Tests
} }
// 2. Decompile // 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 // 3. Compile
CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(cscOptions).Append("EXPECTED_OUTPUT").ToArray()); 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
}; };
} }
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() public static void StructInitializer_DefaultConstructor()
{ {
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData { InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData {
@ -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() public static void StructInitializer_ExplicitConstructor()
{ {
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData(0) { InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData(0) {

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

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

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

@ -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 @@
#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 @@
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
public void Issue1779(int value) public void Issue1779(int value)
{ {
CustomStruct2 @struct = GetStruct(); CustomStruct2 customStruct = GetStruct();
@struct.IntProp += value; customStruct.IntProp += value;
} }
} }
} }

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

@ -19,15 +19,33 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
#if CS100 #if CS100
using System.Threading.Tasks; using System.Threading.Tasks;
#endif #endif
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
{ {
public static class 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 private class InstanceTests
{ {
public struct SomeData public struct SomeData
@ -377,10 +395,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
public static void NameConflict2(int j) public static void NameConflict2(int j)
{ {
List<Action<int>> list = new List<Action<int>>(); 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) { list.Add(delegate (int k) {
Console.WriteLine(i); Console.WriteLine(k);
}); });
} }
} }
@ -643,6 +661,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
{ {
del(x); 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)] [AttributeUsage(AttributeTargets.All)]

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

@ -1,4 +1,4 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // Copyright (c) AlphaSierraPapa for the SharpDevelop Team
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of this // 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 // software and associated documentation files (the "Software"), to deal in the Software
@ -28,6 +28,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Item2 Item2
} }
public enum NoZero
{
Item1 = 1,
Item2
}
public enum OutOfOrderMembers public enum OutOfOrderMembers
{ {
Item1 = 1, Item1 = 1,
@ -135,5 +141,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
return AttributeTargets.Class | AttributeTargets.Delegate; 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
{ {
Console.WriteLine(input); Console.WriteLine(input);
} }
catch (TException val) catch (TException ex)
{ {
Console.WriteLine(val.Message); Console.WriteLine(ex.Message);
throw; throw;
} }
} }
@ -452,9 +452,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
Console.WriteLine(input); 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; throw;
} }
} }
@ -465,9 +465,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
Console.WriteLine(input); 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
{ {
int Property { get; set; } 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 #endregion
private S s1; private S s1;
@ -389,6 +447,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
#endif #endif
#if CS110 && !NET40 #if CS110 && !NET40
public static ReadOnlySpan<byte> UTF8Literal => "Hello, world!"u8; public static ReadOnlySpan<byte> UTF8Literal => "Hello, world!"u8;
public static ReadOnlySpan<byte> UTF8LiteralWithNullTerminator => "Hello, world!\0"u8;
#endif #endif
#endregion #endregion
@ -747,6 +806,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
array[0] = 1; array[0] = 1;
Console.WriteLine(array.Length); 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 #endregion
#region Object initializers #region Object initializers
@ -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() public static void StructInitializer_DefaultConstructor()
{ {
X(Y(), new StructData { X(Y(), new StructData {
@ -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() public static void StructInitializer_ExplicitConstructor()
{ {
X(Y(), new StructData(0) { X(Y(), new StructData(0) {
@ -1010,6 +1066,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
otherItem.Data2.Nullable = 3m; otherItem.Data2.Nullable = 3m;
return otherItem; 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 #if CS60
public OtherItem2 Issue1345c() public OtherItem2 Issue1345c()
{ {

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

@ -32,6 +32,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
B 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) 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
public record PairWithPrimaryCtor<A, B>(A First, B Second); public record PairWithPrimaryCtor<A, B>(A First, B Second);
public record PrimaryCtor(int A, string B); 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 record PrimaryCtorWithField(int A, string B)
{ {
public double C = 1.0; public double C = 1.0;
@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public record struct PairWithPrimaryCtor<A, B>(A First, B Second); public record struct PairWithPrimaryCtor<A, B>(A First, B Second);
public record struct PrimaryCtor(int A, string B); 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 record struct PrimaryCtorWithField(int A, string B)
{ {
public double C = 1.0; public double C = 1.0;
@ -242,27 +242,3 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
#endif #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
GetRef<ReadOnlyStruct>().Method(); GetRef<ReadOnlyStruct>().Method();
// call on a copy, not the original ref: // call on a copy, not the original ref:
NormalStruct @ref = GetRef<NormalStruct>(); NormalStruct normalStruct = GetRef<NormalStruct>();
@ref.Method(); normalStruct.Method();
ReadOnlyStruct ref2 = GetRef<ReadOnlyStruct>(); ReadOnlyStruct readOnlyStruct = GetRef<ReadOnlyStruct>();
ref2.Method(); readOnlyStruct.Method();
} }
public void CallOnReadOnlyRefReturn() public void CallOnReadOnlyRefReturn()
@ -293,13 +293,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void RefReassignment(ref NormalStruct s) public void RefReassignment(ref NormalStruct s)
{ {
ref NormalStruct @ref = ref GetRef<NormalStruct>(); ref NormalStruct reference = ref GetRef<NormalStruct>();
RefReassignment(ref @ref); RefReassignment(ref reference);
if (s.GetHashCode() == 0) 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) public static void Main(string[] args)

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

@ -60,19 +60,3 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
#endif #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
} }
} }
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 #if CS80
[StructLayout(LayoutKind.Sequential, Size = 1)] [StructLayout(LayoutKind.Sequential, Size = 1)]
public ref struct UsingRefStruct public ref struct UsingRefStruct
@ -161,5 +187,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
#endif #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
public async void AwaitDefaultYieldAwaitable() public async void AwaitDefaultYieldAwaitable()
{ {
#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
YieldAwaitable yieldAwaitable = default(YieldAwaitable); YieldAwaitable yieldAwaitable2 = default(YieldAwaitable);
YieldAwaitable yieldAwaitable2 = yieldAwaitable; YieldAwaitable yieldAwaitable = yieldAwaitable2;
await yieldAwaitable2; await yieldAwaitable;
#else #else
await default(YieldAwaitable); await default(YieldAwaitable);
#endif #endif
@ -51,9 +51,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
public async void AwaitDefaultHopToThreadPool() public async void AwaitDefaultHopToThreadPool()
{ {
#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
HopToThreadPoolAwaitable hopToThreadPoolAwaitable = default(HopToThreadPoolAwaitable); HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = default(HopToThreadPoolAwaitable);
HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = hopToThreadPoolAwaitable; HopToThreadPoolAwaitable hopToThreadPoolAwaitable = hopToThreadPoolAwaitable2;
await hopToThreadPoolAwaitable2; await hopToThreadPoolAwaitable;
#else #else
await default(HopToThreadPoolAwaitable); await default(HopToThreadPoolAwaitable);
#endif #endif

4
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -528,6 +528,8 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
var typeSystemAstBuilder = new TypeSystemAstBuilder(); var typeSystemAstBuilder = new TypeSystemAstBuilder();
typeSystemAstBuilder.ShowAttributes = true; typeSystemAstBuilder.ShowAttributes = true;
typeSystemAstBuilder.UsePrivateProtectedAccessibility = settings.IntroducePrivateProtectedAccessibility;
typeSystemAstBuilder.SortAttributes = settings.SortCustomAttributes;
typeSystemAstBuilder.AlwaysUseShortTypeNames = true; typeSystemAstBuilder.AlwaysUseShortTypeNames = true;
typeSystemAstBuilder.AddResolveResultAnnotations = true; typeSystemAstBuilder.AddResolveResultAnnotations = true;
typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables; typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables;
@ -1281,7 +1283,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList()) 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; parameter.Name = "P_" + i;
} }
i++; i++;

5
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -1043,7 +1043,10 @@ namespace ICSharpCode.Decompiler.CSharp
bool IsOptionalArgument(IParameter parameter, TranslatedExpression arg) 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; return false;
if (parameter.GetAttributes().Any(a => a.AttributeType.IsKnownType(KnownAttribute.CallerMemberName) if (parameter.GetAttributes().Any(a => a.AttributeType.IsKnownType(KnownAttribute.CallerMemberName)
|| a.AttributeType.IsKnownType(KnownAttribute.CallerFilePath) || a.AttributeType.IsKnownType(KnownAttribute.CallerFilePath)

9
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2547,15 +2547,18 @@ namespace ICSharpCode.Decompiler.CSharp
foreach (var parameter in parameters) foreach (var parameter in parameters)
{ {
var pd = astBuilder.ConvertParameter(parameter); 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()) 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; pd.Name = "P_" + i;
} }
if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType())
pd.Type = null; pd.Type = null;
if (variables.TryGetValue(i, out var v))
pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type));
yield return pd; yield return pd;
i++; i++;
} }

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

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

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

@ -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 // 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 // software and associated documentation files (the "Software"), to deal in the Software
@ -426,8 +426,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// <summary> /// <summary>
/// Determines whether the specified identifier is a keyword in the given context. /// 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> /// </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 // only 2-10 char lower-case identifiers can be keywords
if (identifier.Length > maxKeywordLength || identifier.Length < 2 || identifier[0] < 'a') if (identifier.Length > maxKeywordLength || identifier.Length < 2 || identifier[0] < 'a')
@ -440,10 +441,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
} }
if (queryKeywords.Contains(identifier)) 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 (identifier == "await")
{ {
if (context == null)
return true;
foreach (AstNode ancestor in context.Ancestors) foreach (AstNode ancestor in context.Ancestors)
{ {
// with lambdas/anonymous methods, // with lambdas/anonymous methods,
@ -1530,8 +1533,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteToken(Roles.RBracket); WriteToken(Roles.RBracket);
switch (attributeSection.Parent) switch (attributeSection.Parent)
{ {
case ParameterDeclaration _: case ParameterDeclaration pd:
if (attributeSection.NextSibling is AttributeSection) if (pd.Attributes.Last() != attributeSection)
Space(policy.SpaceBetweenParameterAttributeSections); Space(policy.SpaceBetweenParameterAttributeSections);
else else
Space(); Space();

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

@ -81,13 +81,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
w.WriteEndElement(); // </Platform> w.WriteEndElement(); // </Platform>
string outputType; 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"; outputType = "WinExe";
break; break;
case Subsystem.WindowsCui: case Subsystem.WindowsCui when !headers.IsDll:
outputType = "Exe"; outputType = "Exe";
break; break;
default: default:

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

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

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

@ -805,7 +805,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
} }
if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality) 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 it's a reference comparison
if (op == BinaryOperatorType.Equality) if (op == BinaryOperatorType.Equality)

10
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -1389,6 +1389,16 @@ namespace ICSharpCode.Decompiler.CSharp
var astBuilder = exprBuilder.astBuilder; var astBuilder = exprBuilder.astBuilder;
var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod); 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) if (function.Method.HasBody)
{ {
var nestedBuilder = new StatementBuilder( var nestedBuilder = new StatementBuilder(

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

@ -70,6 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
this.UseKeywordsForBuiltinTypes = true; this.UseKeywordsForBuiltinTypes = true;
this.UseNullableSpecifierForValueTypes = true; this.UseNullableSpecifierForValueTypes = true;
this.ShowAccessibility = true; this.ShowAccessibility = true;
this.UsePrivateProtectedAccessibility = true;
this.ShowModifiers = true; this.ShowModifiers = true;
this.ShowBaseTypes = true; this.ShowBaseTypes = true;
this.ShowTypeParameters = true; this.ShowTypeParameters = true;
@ -93,13 +94,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public bool AddResolveResultAnnotations { get; set; } public bool AddResolveResultAnnotations { get; set; }
/// <summary> /// <summary>
/// Controls the accessibility modifiers are shown. /// Controls whether accessibility modifiers are shown.
/// The default value is <see langword="true" />. /// The default value is <see langword="true" />.
/// </summary> /// </summary>
public bool ShowAccessibility { get; set; } public bool ShowAccessibility { get; set; }
/// <summary> /// <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" />. /// The default value is <see langword="true" />.
/// </summary> /// </summary>
public bool ShowModifiers { get; set; } public bool ShowModifiers { get; set; }
@ -147,6 +154,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// </summary> /// </summary>
public bool ShowAttributes { get; set; } 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> /// <summary>
/// Controls whether to use fully-qualified type names or short type names. /// Controls whether to use fully-qualified type names or short type names.
/// The default value is <see langword="false" />. /// The default value is <see langword="false" />.
@ -793,16 +806,72 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return attr; return attr;
} }
private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes) private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes, string target = null)
{
return attributes.Select(a => new AttributeSection(ConvertAttribute(a)));
}
private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes, string target)
{ {
return attributes.Select(a => new AttributeSection(ConvertAttribute(a)) { if (SortAttributes)
AttributeTarget = target 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 #endregion
@ -1072,7 +1141,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
expr = new PrimitiveExpression(constantValue, format); expr = new PrimitiveExpression(constantValue, format);
if (AddResolveResultAnnotations && literalType != null) if (AddResolveResultAnnotations && literalType != null)
expr.AddAnnotation(new ConstantResolveResult(literalType, constantValue)); 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); expr = new CastExpression(ConvertType(type), expr);
} }
@ -1722,10 +1791,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case SymbolKind.Event: case SymbolKind.Event:
return ConvertEvent((IEvent)entity); return ConvertEvent((IEvent)entity);
case SymbolKind.Method: case SymbolKind.Method:
if (entity.Name.Contains(".op_"))
{
goto case SymbolKind.Operator;
}
return ConvertMethod((IMethod)entity); return ConvertMethod((IMethod)entity);
case SymbolKind.Operator: case SymbolKind.Operator:
return ConvertOperator((IMethod)entity); return ConvertOperator((IMethod)entity);
@ -1747,7 +1812,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
Modifiers modifiers = Modifiers.None; Modifiers modifiers = Modifiers.None;
if (this.ShowAccessibility) if (this.ShowAccessibility)
{ {
modifiers |= ModifierFromAccessibility(typeDefinition.Accessibility); modifiers |= ModifierFromAccessibility(typeDefinition.Accessibility, UsePrivateProtectedAccessibility);
} }
if (this.ShowModifiers) if (this.ShowModifiers)
{ {
@ -2015,7 +2080,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
} }
if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility) if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility)
decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility); decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility, UsePrivateProtectedAccessibility);
if (this.ShowModifiers && accessor.HasReadonlyModifier()) if (this.ShowModifiers && accessor.HasReadonlyModifier())
decl.Modifiers |= Modifiers.Readonly; decl.Modifiers |= Modifiers.Readonly;
TokenRole keywordRole = kind switch { TokenRole keywordRole = kind switch {
@ -2284,7 +2349,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
#endregion #endregion
#region Convert Modifiers #region Convert Modifiers
public static Modifiers ModifierFromAccessibility(Accessibility accessibility) public static Modifiers ModifierFromAccessibility(Accessibility accessibility, bool usePrivateProtected)
{ {
switch (accessibility) switch (accessibility)
{ {
@ -2299,7 +2364,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case Accessibility.ProtectedOrInternal: case Accessibility.ProtectedOrInternal:
return Modifiers.Protected | Modifiers.Internal; return Modifiers.Protected | Modifiers.Internal;
case Accessibility.ProtectedAndInternal: case Accessibility.ProtectedAndInternal:
return Modifiers.Private | Modifiers.Protected; return usePrivateProtected ? Modifiers.Private | Modifiers.Protected : Modifiers.Protected;
default: default:
return Modifiers.None; return Modifiers.None;
} }
@ -2330,7 +2395,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
Modifiers m = Modifiers.None; Modifiers m = Modifiers.None;
if (this.ShowAccessibility && NeedsAccessibility(member)) if (this.ShowAccessibility && NeedsAccessibility(member))
{ {
m |= ModifierFromAccessibility(member.Accessibility); m |= ModifierFromAccessibility(member.Accessibility, UsePrivateProtectedAccessibility);
} }
if (this.ShowModifiers) if (this.ShowModifiers)
{ {

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

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

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

@ -172,13 +172,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
"Microsoft.CodeAnalysis.EmbeddedAttribute", "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) public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration)
{ {
var typeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition; var typeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition;
if (typeDefinition == null || !attributeNames.Contains(typeDefinition.FullName)) if (typeDefinition == null)
return; return;
if (!typeDefinition.HasAttribute(KnownAttribute.Embedded))
if (attributeNames.Contains(typeDefinition.FullName))
{
if (!typeDefinition.HasAttribute(KnownAttribute.Embedded))
return;
}
else if (!nonEmbeddedAttributeNames.Contains(typeDefinition.FullName))
return; return;
if (typeDeclaration.Parent is NamespaceDeclaration ns && ns.Members.Count == 1) if (typeDeclaration.Parent is NamespaceDeclaration ns && ns.Members.Count == 1)
ns.Remove(); ns.Remove();
else else

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

@ -17,10 +17,10 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.IL;
namespace ICSharpCode.Decompiler.CSharp.Transforms namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
@ -54,13 +54,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
while (IsDegenerateQuery(innerQuery)) while (IsDegenerateQuery(innerQuery))
{ {
QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First(); QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
if (fromClause.Identifier != innerFromClause.Identifier) ILVariable innerVariable = innerFromClause.Annotation<ILVariableResolveResult>()?.Variable;
break; ILVariable rangeVariable = fromClause.Annotation<ILVariableResolveResult>()?.Variable;
// Replace the fromClause with all clauses from the inner query // Replace the fromClause with all clauses from the inner query
fromClause.Remove(); fromClause.Remove();
QueryClause insertionPos = null; QueryClause insertionPos = null;
foreach (var clause in innerQuery.Clauses) foreach (var clause in innerQuery.Clauses)
{ {
CombineRangeVariables(clause, innerVariable, rangeVariable);
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
} }
fromClause = innerFromClause; fromClause = innerFromClause;
@ -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) bool IsDegenerateQuery(QueryExpression query)
{ {
if (query == null) if (query == null)

13
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -1,4 +1,4 @@
// Copyright (c) 2014 Daniel Grunwald // Copyright (c) 2014 Daniel Grunwald
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of this // 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 // software and associated documentation files (the "Software"), to deal in the Software
@ -731,6 +731,17 @@ namespace ICSharpCode.Decompiler.CSharp
.WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual, .WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual,
this.ResolveResult, nullRef.ResolveResult)); 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 else
{ {
var zero = new PrimitiveExpression(0) var zero = new PrimitiveExpression(0)

2
ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs

@ -92,7 +92,7 @@ namespace ICSharpCode.Decompiler.DebugInfo
string ns = settings.UseNestedDirectoriesForNamespaces string ns = settings.UseNestedDirectoriesForNamespaces
? WholeProjectDecompiler.CleanUpPath(typeName.Namespace) ? WholeProjectDecompiler.CleanUpPath(typeName.Namespace)
: WholeProjectDecompiler.CleanUpDirectoryName(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(); 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
introduceRefModifiersOnStructs = false; introduceRefModifiersOnStructs = false;
nonTrailingNamedArguments = false; nonTrailingNamedArguments = false;
refExtensionMethods = false; refExtensionMethods = false;
introducePrivateProtectedAccessibilty = false;
} }
if (languageVersion < CSharp.LanguageVersion.CSharp7_3) if (languageVersion < CSharp.LanguageVersion.CSharp7_3)
{ {
@ -185,7 +186,7 @@ namespace ICSharpCode.Decompiler
|| patternBasedFixedStatement) || patternBasedFixedStatement)
return CSharp.LanguageVersion.CSharp7_3; return CSharp.LanguageVersion.CSharp7_3;
if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers
|| nonTrailingNamedArguments || refExtensionMethods) || nonTrailingNamedArguments || refExtensionMethods || introducePrivateProtectedAccessibilty)
return CSharp.LanguageVersion.CSharp7_2; return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing // C# 7.1 missing
if (outVariables || throwExpressions || tupleTypes || tupleConversions if (outVariables || throwExpressions || tupleTypes || tupleConversions
@ -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; bool readOnlyMethods = true;
[Category("C# 8.0 / VS 2019")] [Category("C# 8.0 / VS 2019")]
@ -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; CSharpFormattingOptions csharpFormattingOptions;
[Browsable(false)] [Browsable(false)]

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

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

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

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

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

@ -21,11 +21,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util; using ICSharpCode.Decompiler.Util;
@ -225,6 +222,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
goto default; 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: default:
// User code - abort analysis // User code - abort analysis
if (mode == StateRangeAnalysisMode.IteratorDispose && !(inst is Leave l && l.IsLeavingFunction)) 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
function.MoveNextMethod = moveNextFunction.Method; function.MoveNextMethod = moveNextFunction.Method;
function.SequencePointCandidates = moveNextFunction.SequencePointCandidates; function.SequencePointCandidates = moveNextFunction.SequencePointCandidates;
function.CodeSize = moveNextFunction.CodeSize; function.CodeSize = moveNextFunction.CodeSize;
function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength;
// Copy-propagate temporaries holding a copy of 'this'. // 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. // 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
} }
// We can't use MatchStLoc like above since the doFinallyBodies variable is split by SplitVariables. // We can't use MatchStLoc like above since the doFinallyBodies variable is split by SplitVariables.
// This occurs for the Legacy VBC compiler. // 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)) if (!value.MatchLdcI4(0))
{ {

4
ICSharpCode.Decompiler/IL/ILReader.cs

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

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

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

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

@ -18,6 +18,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -25,6 +26,7 @@ using System.Reflection.Metadata;
using Humanizer.Inflections; using Humanizer.Inflections;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -32,7 +34,7 @@ using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms 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> { static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> {
{ "System.Boolean", "flag" }, { "System.Boolean", "flag" },
@ -53,52 +55,83 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}; };
ILTransformContext context; ILTransformContext context;
List<string> currentLowerCaseTypeOrMemberNames;
Dictionary<string, int> reservedVariableNames;
HashSet<ILVariable> loopCounters;
const char maxLoopVariableName = 'n'; 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>(); // if this is the root scope, we also collect all lower-case type and member names
currentLowerCaseTypeOrMemberNames = new List<string>(); // and fixed parameter names to avoid conflicts when naming local variables.
var currentLowerCaseMemberNames = CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition); if (parentScope == null)
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 (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(); var lastParameter = function.Method.Parameters.Last();
switch (f.Method.AccessorOwner) switch (function.Method.AccessorOwner)
{ {
case IProperty prop: case IProperty prop:
if (f.Method.AccessorKind == MethodSemanticsAttributes.Setter) if (function.Method.AccessorKind == MethodSemanticsAttributes.Setter)
{ {
if (prop.Parameters.Any(p => p.Name == "value")) 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; break;
} }
var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
&& v.Kind == VariableKind.Parameter && v.Kind == VariableKind.Parameter
&& v.Index == f.Method.Parameters.Count - 1); && v.Index == function.Method.Parameters.Count - 1);
if (variableForLastParameter == null) if (variableForLastParameter == null)
{ {
AddExistingName(reservedVariableNames, lastParameter.Name); AddExistingName(reservedVariableNames, lastParameter.Name);
@ -114,11 +147,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
break; break;
case IEvent ev: 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.Kind == VariableKind.Parameter
&& v.Index == f.Method.Parameters.Count - 1); && v.Index == function.Method.Parameters.Count - 1);
if (variableForLastParameter == null) if (variableForLastParameter == null)
{ {
AddExistingName(reservedVariableNames, lastParameter.Name); AddExistingName(reservedVariableNames, lastParameter.Name);
@ -140,183 +173,425 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
else else
{ {
foreach (var p in f.Method.Parameters) var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0).ToDictionary(v => v.Index);
AddExistingName(reservedVariableNames, p.Name); 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 else
{ {
foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter)) this.currentLowerCaseTypeOrMemberNames = parentScope.currentLowerCaseTypeOrMemberNames;
AddExistingName(reservedVariableNames, p.Name); 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) public void Add(MethodDefinitionHandle localFunction, string name)
{ {
foreach (var item in type.GetMembers(m => IsLowerCase(m.Name))) this.localFunctions[localFunction] = name;
yield return item.Name; }
}
static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type) public string TryGetExistingName(MethodDefinitionHandle localFunction)
{
var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
foreach (var item in ns.Types)
{ {
if (IsLowerCase(item.Name)) if (localFunctions.TryGetValue(localFunction, out var name))
yield return item.Name; return name;
return parentScope?.TryGetExistingName(localFunction);
} }
}
static bool IsLowerCase(string name) public string TryGetExistingName(ILVariable v)
{ {
return name.Length > 0 && char.ToLower(name[0]) == name[0]; if (variableMapping.TryGetValue(v, out var name))
} return name;
return parentScope?.TryGetExistingName(v);
}
bool IsSetOrEventAccessor(IMethod method) public string TryGetExistingName(ILFunction function, int index)
{
switch (method.AccessorKind)
{ {
case MethodSemanticsAttributes.Setter: if (this.function == function)
case MethodSemanticsAttributes.Adder: {
case MethodSemanticsAttributes.Remover: 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; return true;
default: return parentScope?.IsReservedVariableName(name, out index) ?? false;
return false;
} }
}
void PerformAssignment(ILFunction function) public void ReserveVariableName(string name, int index = 1)
{ {
var localFunctionMapping = new Dictionary<MethodDefinitionHandle, string>(); reservedVariableNames[name] = index;
var variableMapping = new Dictionary<ILVariable, string>(ILVariableEqualityComparer.Instance); }
var assignedLocalSignatureIndices = new Dictionary<(ILFunction, int), string>();
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 var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName)) .Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
newName = null; .Where(arg => !string.IsNullOrWhiteSpace(arg))
if (newName == null) .Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForAddress.Count > 0)
{ {
string nameWithoutNumber = "f"; proposedName = proposedNameForAddress[0];
if (!reservedVariableNames.TryGetValue(nameWithoutNumber, out int currentIndex)) }
}
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; else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
reservedVariableNames[nameWithoutNumber] = count;
if (count > 1)
{ {
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; if (proposedNameForStores.Count == 1)
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))
{ {
v.Name = name; proposedName = proposedNameForStores.Single();
continue;
} }
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: proposedName = proposedNameForLoads[0];
// 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;
} }
} }
else if (inst is (Call or LdFtn) and IInstructionWithMethodOperand m) if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
{ {
// update references to local functions var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
if (m.Method is LocalFunctionMethod lf .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
&& localFunctionMapping.TryGetValue((MethodDefinitionHandle)lf.MetadataToken, out var name)) .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 foreach (var child in inst.Children)
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 child.AcceptVisitor(this, context);
// 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); return default;
if (reservedVariableNames.TryGetValue(nameWithoutNumber, out int lastUsedIndex)) }
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, v.Name = name;
// we don't want them to be named i, i2, ..., but i, j, ... return VisitChildren(inst, context);
newName = GenerateNameForVariable(v); }
nameWithoutNumber = newName;
newIndex = 1; 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 // assign names to local functions
if (newIndex > lastUsedIndex) 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 p.Name = nestedContext.AssignNameIfUnassigned(p);
newIndex = lastUsedIndex + 1;
} }
// resolve conflicts by appending the index to the new name:
newName = nameWithoutNumber + newIndex.ToString();
} }
// update the last used index
reservedVariableNames[nameWithoutNumber] = newIndex; return default;
variableMapping.Add(v, newName); }
return newName;
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> /// <remarks>
@ -351,118 +626,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; 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) static string GetNameFromInstruction(ILInstruction inst)
{ {
switch (inst) switch (inst)
@ -566,22 +729,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType); type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType);
} }
string name = type.Kind switch { string name;
TypeKind.Array => "array",
TypeKind.Pointer => "ptr",
TypeKind.TypeParameter => "val",
TypeKind.Unknown => "val",
TypeKind.Dynamic => "val",
TypeKind.ByReference => "reference",
TypeKind.Tuple => "tuple",
TypeKind.NInt => "num",
TypeKind.NUInt => "num",
_ => null
};
if (name != null)
{
return name;
}
if (type.IsAnonymousType()) if (type.IsAnonymousType())
{ {
name = "anon"; name = "anon";
@ -590,13 +738,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
name = "ex"; name = "ex";
} }
else if (type.Name.EndsWith("EventArgs", StringComparison.Ordinal))
{
name = "e";
}
else if (type.IsCSharpNativeIntegerType()) else if (type.IsCSharpNativeIntegerType())
{ {
name = "num"; name = "num";
} }
else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) 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 // remove the 'I' for interfaces
if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2])) if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2]))
name = name.Substring(1); name = name.Substring(1);
@ -657,11 +820,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (name.Length == 0) if (name.Length == 0)
return "obj"; return "obj";
else string lowerCaseName = char.ToLower(name[0]) + name.Substring(1);
return 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)) if (!variableType.IsKnownType(KnownTypeCode.Object))
return variableType; return variableType;

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

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

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

@ -448,7 +448,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return inst != ldloca && inst.Parent is LdObj; 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) if (ldloca.Parent is not CallInstruction call)
{ {

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

@ -142,9 +142,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
DetermineCaptureAndDeclarationScope(info, useSite); 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
switch (useSite) switch (useSite)
{ {
case CallInstruction call: 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; int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1;
for (int i = call.Arguments.Count - 1; i >= firstArgumentIndex; i--) 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
if (!FixCasesWithoutValue(sections, stringValues)) if (!FixCasesWithoutValue(sections, stringValues))
return false; return false;
// switch contains case null: // switch contains case null:
if (nullValueCaseBlock != defaultBlock) if (nullValueCaseBlock != null && nullValueCaseBlock != defaultBlock)
{ {
if (!AddNullSection(sections, stringValues, nullValueCaseBlock)) if (!AddNullSection(sections, stringValues, nullValueCaseBlock))
{ {
return false; return false;
} }
} }
else if (leaveContainer != null && !defaultBlockJump.MatchLeave(leaveContainer))
{
if (!AddNullSection(sections, stringValues, (Leave)exitBlockJump))
{
return false;
}
}
context.Step(nameof(MatchLegacySwitchOnStringWithDict), instructions[i]); context.Step(nameof(MatchLegacySwitchOnStringWithDict), instructions[i]);
bool keepAssignmentBefore = false; bool keepAssignmentBefore = false;
if (switchValueVar.LoadCount > 2 || switchValue == null) if (switchValueVar.LoadCount > 2 || switchValue == null)
@ -741,6 +748,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
bool AddNullSection(List<SwitchSection> sections, List<(string Value, int Index)> stringValues, Block nullValueCaseBlock) 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 label = new LongSet(stringValues.Max(item => item.Index) + 1);
var possibleConflicts = sections.Where(sec => sec.Labels.Overlaps(label)).ToArray(); var possibleConflicts = sections.Where(sec => sec.Labels.Overlaps(label)).ToArray();
@ -753,7 +765,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
possibleConflicts[0].Labels = possibleConflicts[0].Labels.ExceptWith(label); possibleConflicts[0].Labels = possibleConflicts[0].Labels.ExceptWith(label);
} }
stringValues.Add((null, (int)label.Values.First())); 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; return true;
} }

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

@ -119,28 +119,57 @@ namespace ICSharpCode.Decompiler.IL.Transforms
replacement = null; replacement = null;
if (!context.Settings.ArrayInitializers) if (!context.Settings.ArrayInitializers)
return false; 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)) return new LdStrUtf8(text);
{
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 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) private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text)
@ -153,9 +182,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
byte val = blob.CurrentPointer[i]; byte val = blob.CurrentPointer[i];
// If the string has control characters, it's probably binary data and not a string. if (val == 0 && i == size - 1 && size > 1)
if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t')) {
// 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; text = null;
return false; return false;
} }
@ -187,6 +220,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; 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) bool DoTransformMultiDim(ILFunction function, Block body, int pos)
{ {
if (pos >= body.Instructions.Count - 2) if (pos >= body.Instructions.Count - 2)
@ -334,7 +386,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
bool MatchGetStaticFieldAddress(ILInstruction input, out IField field) static bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
{ {
if (input.MatchLdsFlda(out field)) if (input.MatchLdsFlda(out field))
return true; return true;
@ -357,7 +409,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return field != null; 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) 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
instType = newObjInst.Method.DeclaringType; instType = newObjInst.Method.DeclaringType;
break; break;
case DefaultValue defaultVal: 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; instType = defaultVal.Type;
break; break;
case Call c when c.Method.FullNameIs("System.Activator", "CreateInstance") && c.Method.TypeArguments.Count == 1: case Call c when c.Method.FullNameIs("System.Activator", "CreateInstance") && c.Method.TypeArguments.Count == 1:
@ -103,6 +95,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return; return;
} }
int initializerItemsCount = 0; int initializerItemsCount = 0;
bool initializerContainsInitOnlyItems = false;
possibleIndexVariables.Clear(); possibleIndexVariables.Clear();
currentPath.Clear(); currentPath.Clear();
isCollection = false; isCollection = false;
@ -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 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 // 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 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++; initializerItemsCount++;
} }
// Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable // Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable
// directly after the possible initializer. // directly after the possible initializer.
if (IsMethodCallOnVariable(block.Instructions[pos + initializerItemsCount + 1], v)) if (!initializerContainsInitOnlyItems && IsMethodCallOnVariable(block.Instructions[pos + initializerItemsCount + 1], v))
return; return;
// Calculate the correct number of statements inside the initializer: // Calculate the correct number of statements inside the initializer:
// All index variables that were used in the initializer have Index set to -1. // All index variables that were used in the initializer have Index set to -1.
@ -200,7 +193,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool isCollection; bool isCollection;
readonly Stack<HashSet<AccessPathElement>> pathStack = new Stack<HashSet<AccessPathElement>>(); 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 // Include any stores to local variables that are single-assigned and do not reference the initializer-variable
// in the list of possible index variables. // in the list of possible index variables.
@ -255,6 +248,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer) if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer)
blockKind = BlockKind.ObjectInitializer; blockKind = BlockKind.ObjectInitializer;
initializerContainsInitOnlyItems |= lastElement.Member is IProperty { Setter.IsInitOnly: true };
return true; return true;
default: default:
return false; return false;

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

@ -89,7 +89,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally))) if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally)))
return false; 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; return false;
if (storeInst.Variable.StoreInstructions.Count > 1) if (storeInst.Variable.StoreInstructions.Count > 1)
return false; return false;
@ -104,6 +104,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike
}.WithILRange(storeInst); }.WithILRange(storeInst);
return true; 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> /// <summary>

5
ICSharpCode.Decompiler/Output/IAmbience.cs

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

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

@ -96,6 +96,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
this.symbolKind = SymbolKind.Destructor; 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 this.IsExtensionMethod = (attributes & MethodAttributes.Static) == MethodAttributes.Static
&& (module.TypeSystemOptions & TypeSystemOptions.ExtensionMethods) == TypeSystemOptions.ExtensionMethods && (module.TypeSystemOptions & TypeSystemOptions.ExtensionMethods) == TypeSystemOptions.ExtensionMethods
&& def.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.Extension); && def.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.Extension);

2
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -225,7 +225,7 @@ namespace ICSharpCode.Decompiler.Util
yield return func(index++, element); 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; int index = 0;
foreach (var item in source) foreach (var item in source)

39
ICSharpCode.Decompiler/Util/DelegateComparer.cs

@ -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
public void Generate() public void Generate()
{ {
if (writer == null)
InitWriter();
if (written) if (written)
throw new InvalidOperationException("The resource is already generated."); throw new InvalidOperationException("The resource is already generated.");

2
ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs

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

8
ILSpy/Analyzers/AnalyzerEntityTreeNode.cs

@ -22,8 +22,8 @@ using System.Windows;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
namespace ICSharpCode.ILSpy.Analyzers 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))); 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) public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies)
{ {
if (Member == null)
{
return true;
}
foreach (LoadedAssembly asm in removedAssemblies) foreach (LoadedAssembly asm in removedAssemblies)
{ {
if (this.Member.ParentModule.MetadataFile == asm.GetMetadataFileOrNull()) if (this.Member.ParentModule.MetadataFile == asm.GetMetadataFileOrNull())

24
ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs

@ -17,9 +17,12 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.Windows; using System.Windows;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.TreeView;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
namespace ICSharpCode.ILSpy.Analyzers.TreeNodes namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
@ -38,6 +41,8 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
public override object Text => analyzedModule.AssemblyName; public override object Text => analyzedModule.AssemblyName;
public override object ToolTip => analyzedModule.MetadataFile?.FileName;
protected override void LoadChildren() protected override void LoadChildren()
{ {
foreach (var lazy in Analyzers) foreach (var lazy in Analyzers)
@ -62,5 +67,24 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
} }
public override IEntity Member => null; 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 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:styles="urn:TomsToolbox.Wpf.Styles" xmlns:styles="urn:TomsToolbox.Wpf.Styles"
xmlns:toms="urn:TomsToolbox" 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> <Application.Resources>
<Style x:Key="DialogWindow" TargetType="{x:Type Window}"> <Style x:Key="DialogWindow" TargetType="{x:Type Window}">
<Setter Property="ShowInTaskbar" Value="False" /> <Setter Property="ShowInTaskbar" Value="False" />
@ -24,7 +26,13 @@
</Style> </Style>
<Style TargetType="ScrollViewer"> <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> </Style>
</Application.Resources> </Application.Resources>

2
ILSpy/App.xaml.cs

@ -69,6 +69,8 @@ namespace ICSharpCode.ILSpy
var cmdArgs = Environment.GetCommandLineArgs().Skip(1); var cmdArgs = Environment.GetCommandLineArgs().Skip(1);
CommandLineArguments = CommandLineArguments.Create(cmdArgs); 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(); var settingsService = new SettingsService();
bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true) bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true)

2
ILSpy/AssemblyTree/AssemblyTreeModel.cs

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

2
ILSpy/Commands/GeneratePdbContextMenuEntry.cs

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

2
ILSpy/Commands/SelectPdbContextMenuEntry.cs

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

1
ILSpy/Controls/MainToolBar.xaml

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

12
ILSpy/Controls/ZoomScrollViewer.xaml

@ -1,10 +1,18 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:ICSharpCode.ILSpy.Controls" 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}"> <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 Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:ZoomScrollViewer}"> <ControlTemplate TargetType="{x:Type Controls:ZoomScrollViewer}">

8
ILSpy/ExtensionMethods.cs

@ -76,11 +76,15 @@ namespace ICSharpCode.ILSpy
return result; 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 return file
.GetLoadedAssembly() .GetLoadedAssembly()
.GetTypeSystemOrNull(DecompilerTypeSystem.GetOptions(settingsService.DecompilerSettings)); .GetTypeSystemOrNull(DecompilerTypeSystem.GetOptions(decompilerSettings));
} }
#region DPI independence #region DPI independence

1
ILSpy/ILSpy.csproj

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

25
ILSpy/Languages/CSharpLanguage.cs

@ -578,10 +578,18 @@ namespace ICSharpCode.ILSpy
CSharpAmbience ambience = new CSharpAmbience(); CSharpAmbience ambience = new CSharpAmbience();
// Do not forget to update CSharpAmbienceTests.ILSpyMainTreeViewTypeFlags, if this ever changes. // Do not forget to update CSharpAmbienceTests.ILSpyMainTreeViewTypeFlags, if this ever changes.
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList | ConversionFlags.PlaceReturnTypeAfterParameterList; 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; ambience.ConversionFlags |= ConversionFlags.UseNullableSpecifierForValueTypes;
} }
if (decompilerSettings.IntroducePrivateProtectedAccessibility)
{
ambience.ConversionFlags |= ConversionFlags.UsePrivateProtectedAccessibility;
}
return ambience; return ambience;
} }
@ -781,7 +789,11 @@ namespace ICSharpCode.ILSpy
public override bool ShowMember(IEntity member) public override bool ShowMember(IEntity member)
{ {
MetadataFile assembly = member.ParentModule.MetadataFile; 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) public override RichText GetRichTextTooltip(IEntity entity)
@ -790,7 +802,10 @@ namespace ICSharpCode.ILSpy
var output = new StringWriter(); var output = new StringWriter();
var decoratedWriter = new TextWriterTokenWriter(output); var decoratedWriter = new TextWriterTokenWriter(output);
var writer = new CSharpHighlightingTokenWriter(TokenWriter.InsertRequiredSpaces(decoratedWriter), locatable: decoratedWriter); 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) if (!settings.LiftNullables)
{ {
flags &= ~ConversionFlags.UseNullableSpecifierForValueTypes; flags &= ~ConversionFlags.UseNullableSpecifierForValueTypes;
@ -815,6 +830,10 @@ namespace ICSharpCode.ILSpy
{ {
flags |= ConversionFlags.SupportInitAccessors; flags |= ConversionFlags.SupportInitAccessors;
} }
if (settings.IntroducePrivateProtectedAccessibility)
{
flags |= ConversionFlags.UsePrivateProtectedAccessibility;
}
if (entity is IMethod m && m.IsLocalFunction) if (entity is IMethod m && m.IsLocalFunction)
{ {
writer.WriteIdentifier(Identifier.Create("(local)")); writer.WriteIdentifier(Identifier.Create("(local)"));

2
ILSpy/Languages/LanguageService.cs

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

3
ILSpy/MainWindow.xaml.cs

@ -102,6 +102,9 @@ namespace ICSharpCode.ILSpy
MessageBus.Send(this, new ApplySessionSettingsEventArgs(sessionSettings)); MessageBus.Send(this, new ApplySessionSettingsEventArgs(sessionSettings));
sessionSettings.WindowBounds = this.RestoreBounds; 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)); sessionSettings.DockLayout.Serialize(new(DockManager));
snapshot.Save(); snapshot.Save();

2
ILSpy/Metadata/CorTables/EventTableTreeNode.cs

@ -94,7 +94,7 @@ namespace ICSharpCode.ILSpy.Metadata
IEntity IMemberTreeNode.Member { IEntity IMemberTreeNode.Member {
get { 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
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(fieldDef.Name):X} \"{Name}\""; 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)] [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(fieldDef.Signature); public int Signature => MetadataTokens.GetHeapOffset(fieldDef.Signature);

2
ILSpy/Metadata/CorTables/MethodTableTreeNode.cs

@ -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) public MethodDefEntry(MetadataFile metadataFile, MethodDefinitionHandle handle)
{ {

2
ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs

@ -92,7 +92,7 @@ namespace ICSharpCode.ILSpy.Metadata
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(propertyDef.Name):X} \"{Name}\""; 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)] [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(propertyDef.Signature); public int Signature => MetadataTokens.GetHeapOffset(propertyDef.Signature);

2
ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs

@ -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) public TypeDefEntry(MetadataFile metadataFile, TypeDefinitionHandle handle)
{ {

8
ILSpy/Options/DisplaySettings.cs

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

1
ILSpy/Options/DisplaySettingsPanel.xaml

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

37
ILSpy/Properties/Resources.Designer.cs generated

@ -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> /// <summary>
/// Looks up a localized string similar to Introduce static local functions. /// Looks up a localized string similar to Introduce static local functions.
/// </summary> /// </summary>
@ -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> /// <summary>
/// Looks up a localized string similar to Use nint/nuint types. /// Looks up a localized string similar to Use nint/nuint types.
/// </summary> /// </summary>
@ -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> /// <summary>
/// Looks up a localized string similar to Detect switch on integer even if IL code does not use a jump table. /// Looks up a localized string similar to Detect switch on integer even if IL code does not use a jump table.
/// </summary> /// </summary>
@ -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> /// <summary>
/// Looks up a localized string similar to Enable word wrap. /// Looks up a localized string similar to Enable word wrap.
/// </summary> /// </summary>

9
ILSpy/Properties/Resources.resx

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

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

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

10
ILSpy/Search/SearchPane.xaml.cs

@ -266,6 +266,7 @@ namespace ICSharpCode.ILSpy.Search
searchTerm, searchTerm,
(SearchMode)searchModeComboBox.SelectedIndex, (SearchMode)searchModeComboBox.SelectedIndex,
assemblyTreeModel.CurrentLanguage, assemblyTreeModel.CurrentLanguage,
assemblyTreeModel.CurrentLanguageVersion,
treeNodeFactory, treeNodeFactory,
settingsService); settingsService);
currentSearch = startedSearch; currentSearch = startedSearch;
@ -295,6 +296,7 @@ namespace ICSharpCode.ILSpy.Search
readonly SearchRequest searchRequest; readonly SearchRequest searchRequest;
readonly SearchMode searchMode; readonly SearchMode searchMode;
readonly Language language; readonly Language language;
readonly LanguageVersion languageVersion;
readonly ApiVisibility apiVisibility; readonly ApiVisibility apiVisibility;
readonly ITreeNodeFactory treeNodeFactory; readonly ITreeNodeFactory treeNodeFactory;
readonly SettingsService settingsService; readonly SettingsService settingsService;
@ -302,7 +304,7 @@ namespace ICSharpCode.ILSpy.Search
public IProducerConsumerCollection<SearchResult> ResultQueue { get; } = new ConcurrentQueue<SearchResult>(); public IProducerConsumerCollection<SearchResult> ResultQueue { get; } = new ConcurrentQueue<SearchResult>();
public RunningSearch(IList<LoadedAssembly> assemblies, string searchTerm, SearchMode searchMode, 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.assemblies = assemblies;
this.language = language; this.language = language;
@ -471,7 +473,11 @@ namespace ICSharpCode.ILSpy.Search
request.RegEx = regex; request.RegEx = regex;
request.SearchResultFactory = new SearchResultFactory(language); request.SearchResultFactory = new SearchResultFactory(language);
request.TreeNodeFactory = this.treeNodeFactory; 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; return request;
} }

6
ILSpy/Search/SearchPaneModel.cs

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

38
ILSpy/SolutionWriter.cs

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

25
ILSpy/TextView/DecompilerTextView.cs

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

2
ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs

@ -89,7 +89,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
var referencedModule = resolver.Resolve(r); var referencedModule = resolver.Resolve(r);
if (referencedModule != null) 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) foreach (var childRef in referencedModule.AssemblyReferences)
this.Children.Add(new AssemblyReferenceTreeNode(module, childRef, parentAssembly)); this.Children.Add(new AssemblyReferenceTreeNode(module, childRef, parentAssembly));
} }

2
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -475,7 +475,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
if (string.IsNullOrEmpty(language.ProjectFileExtension)) if (string.IsNullOrEmpty(language.ProjectFileExtension))
return false; return false;
SaveFileDialog dlg = new SaveFileDialog(); 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|*.*"; dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*";
if (dlg.ShowDialog() == true) if (dlg.ShowDialog() == true)
{ {

2
ILSpy/TreeNodes/EventTreeNode.cs

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

2
ILSpy/TreeNodes/FieldTreeNode.cs

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

2
ILSpy/TreeNodes/MethodTreeNode.cs

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

2
ILSpy/TreeNodes/PropertyTreeNode.cs

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

2
ILSpy/TreeNodes/ReferenceFolderTreeNode.cs

@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
protected override void LoadChildren() protected override void LoadChildren()
{ {
var metadata = module.Metadata; 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)) foreach (var r in module.AssemblyReferences.OrderBy(r => r.Name))
this.Children.Add(new AssemblyReferenceTreeNode(metadataModule, r, parentAssembly)); this.Children.Add(new AssemblyReferenceTreeNode(metadataModule, r, parentAssembly));
foreach (var r in metadata.GetModuleReferences().OrderBy(r => metadata.GetString(metadata.GetModuleReference(r).Name))) 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
{ {
return ((MetadataModule)ParentAssemblyNode.LoadedAssembly return ((MetadataModule)ParentAssemblyNode.LoadedAssembly
.GetMetadataFileOrNull() .GetMetadataFileOrNull()
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService) ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
?.MainModule)?.GetDefinition((SRM.TypeDefinitionHandle)TypeDefinition.MetadataToken); ?.MainModule)?.GetDefinition((SRM.TypeDefinitionHandle)TypeDefinition.MetadataToken);
} }

Loading…
Cancel
Save