Browse Source

Merge pull request #1730 from icsharpcode/async-streams

C# 8.0 Async streams
pull/1790/head
Daniel Grunwald 7 years ago committed by GitHub
parent
commit
cb301bb0e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      BuildTools/update-assemblyinfo.ps1
  2. 2
      ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs
  3. 49
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  4. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  5. 20
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  6. 9
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/TrickyTypes.cs
  7. 2
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs
  8. 2
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs
  9. 2
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs
  10. 37
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs
  11. 60
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs
  12. 3
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs
  13. 8
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  14. 9
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  15. 4
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  16. 5
      ICSharpCode.Decompiler/CSharp/Resolver/LambdaResolveResult.cs
  17. 30
      ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs
  18. 22
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  19. 60
      ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs
  20. 2
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  21. 38
      ICSharpCode.Decompiler/DecompilerSettings.cs
  22. 477
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  23. 2
      ICSharpCode.Decompiler/IL/ControlFlow/AwaitInCatchTransform.cs
  24. 6
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  25. 16
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  26. 21
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  27. 8
      ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs
  28. 107
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
  29. 30
      ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs
  30. 34
      ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs
  31. 2
      ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs
  32. 2
      ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
  33. 2
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeParameter.cs
  34. 3
      ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs
  35. 23
      ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
  36. 3
      ICSharpCode.Decompiler/TypeSystem/TupleType.cs
  37. 17
      ICSharpCode.Decompiler/TypeSystem/TypeParameterSubstitution.cs
  38. 5
      ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
  39. 27
      ILSpy/Properties/Resources.Designer.cs
  40. 12
      ILSpy/Properties/Resources.resx

2
BuildTools/update-assemblyinfo.ps1

@ -53,7 +53,7 @@ function gitCommitHash() {
} }
function gitBranch() { function gitBranch() {
if (-not ((Test-Dir ".git") -and (Find-Git))) { if (-not ((Test-Dir ".git" -or Test-File ".git") -and (Find-Git))) {
return "no-branch"; return "no-branch";
} }

2
ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs

@ -13,7 +13,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
var section = (AttributeSection)attribute.Parent; var section = (AttributeSection)attribute.Parent;
SimpleType type = attribute.Type as SimpleType; SimpleType type = attribute.Type as SimpleType;
if (section.AttributeTarget == "assembly" && if (section.AttributeTarget == "assembly" &&
(type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "PermissionSet" || type.Identifier == "AssemblyVersion" || type.Identifier == "Debuggable")) (type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "PermissionSet" || type.Identifier == "AssemblyVersion" || type.Identifier == "Debuggable" || type.Identifier == "TargetFramework"))
{ {
attribute.Remove(); attribute.Remove();
if (section.Attributes.Count == 0) if (section.Attributes.Count == 0)

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

@ -55,6 +55,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
UseRoslyn = 0x10, UseRoslyn = 0x10,
UseMcs = 0x20, UseMcs = 0x20,
ReferenceVisualBasic = 0x40, ReferenceVisualBasic = 0x40,
ReferenceCore = 0x80,
} }
[Flags] [Flags]
@ -89,12 +90,12 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
outputFile += ".exe"; outputFile += ".exe";
otherOptions += "/exe "; otherOptions += "/exe ";
} }
if (options.HasFlag(AssemblerOptions.UseDebug)) { if (options.HasFlag(AssemblerOptions.UseDebug)) {
otherOptions += "/debug "; otherOptions += "/debug ";
} }
ProcessStartInfo info = new ProcessStartInfo(ilasmPath); ProcessStartInfo info = new ProcessStartInfo(ilasmPath);
info.Arguments = $"/nologo {otherOptions}/output=\"{outputFile}\" \"{sourceFileName}\""; info.Arguments = $"/nologo {otherOptions}/output=\"{outputFile}\" \"{sourceFileName}\"";
info.RedirectStandardError = true; info.RedirectStandardError = true;
@ -115,7 +116,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return outputFile; return outputFile;
} }
public static string Disassemble(string sourceFileName, string outputFile, AssemblerOptions asmOptions) public static string Disassemble(string sourceFileName, string outputFile, AssemblerOptions asmOptions)
{ {
if (asmOptions.HasFlag(AssemblerOptions.UseOwnDisassembler)) { if (asmOptions.HasFlag(AssemblerOptions.UseOwnDisassembler)) {
@ -141,7 +142,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
} }
string ildasmPath = SdkUtility.GetSdkPath("ildasm.exe"); string ildasmPath = SdkUtility.GetSdkPath("ildasm.exe");
ProcessStartInfo info = new ProcessStartInfo(ildasmPath); ProcessStartInfo info = new ProcessStartInfo(ildasmPath);
info.Arguments = $"/nobar /utf8 /out=\"{outputFile}\" \"{sourceFileName}\""; info.Arguments = $"/nobar /utf8 /out=\"{outputFile}\" \"{sourceFileName}\"";
info.RedirectStandardError = true; info.RedirectStandardError = true;
@ -182,8 +183,10 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return Regex.Replace(il, @"'<PrivateImplementationDetails>\{[0-9A-F-]+\}'", "'<PrivateImplementationDetails>'"); return Regex.Replace(il, @"'<PrivateImplementationDetails>\{[0-9A-F-]+\}'", "'<PrivateImplementationDetails>'");
} }
static readonly string coreRefAsmPath = new DotNetCorePathFinder(new Version(3, 0)).GetReferenceAssemblyPath(".NETCoreApp, Version = v3.0");
static readonly string refAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), static readonly string refAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
@"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2"); @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2");
static readonly string thisAsmPath = Path.GetDirectoryName(typeof(Tester).Assembly.Location); static readonly string thisAsmPath = Path.GetDirectoryName(typeof(Tester).Assembly.Location);
static readonly Lazy<IEnumerable<MetadataReference>> defaultReferences = new Lazy<IEnumerable<MetadataReference>>(delegate { static readonly Lazy<IEnumerable<MetadataReference>> defaultReferences = new Lazy<IEnumerable<MetadataReference>>(delegate {
@ -202,6 +205,21 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
}; };
}); });
static readonly Lazy<IEnumerable<MetadataReference>> coreDefaultReferences = new Lazy<IEnumerable<MetadataReference>>(GetDefaultReferences);
const string targetFrameworkAttributeSnippet = @"
[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp, Version = v3.0"", FrameworkDisplayName = """")]
";
static IEnumerable<MetadataReference> GetDefaultReferences()
{
foreach (var reference in Directory.EnumerateFiles(coreRefAsmPath, "*.dll")) {
yield return MetadataReference.CreateFromFile(reference);
}
}
static readonly Lazy<IEnumerable<MetadataReference>> visualBasic = new Lazy<IEnumerable<MetadataReference>>(delegate { static readonly Lazy<IEnumerable<MetadataReference>> visualBasic = new Lazy<IEnumerable<MetadataReference>>(delegate {
return new[] { return new[] {
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "Microsoft.VisualBasic.dll")) MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "Microsoft.VisualBasic.dll"))
@ -217,6 +235,9 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
if (flags.HasFlag(CompilerOptions.Optimize)) { if (flags.HasFlag(CompilerOptions.Optimize)) {
preprocessorSymbols.Add("OPT"); preprocessorSymbols.Add("OPT");
} }
if (flags.HasFlag(CompilerOptions.ReferenceCore)) {
preprocessorSymbols.Add("NETCORE");
}
if (flags.HasFlag(CompilerOptions.UseRoslyn)) { if (flags.HasFlag(CompilerOptions.UseRoslyn)) {
preprocessorSymbols.Add("ROSLYN"); preprocessorSymbols.Add("ROSLYN");
preprocessorSymbols.Add("CS60"); preprocessorSymbols.Add("CS60");
@ -252,7 +273,15 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
languageVersion: Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8 languageVersion: Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8
); );
var syntaxTrees = sourceFileNames.Select(f => SyntaxFactory.ParseSyntaxTree(File.ReadAllText(f), parseOptions, path: f)); var syntaxTrees = sourceFileNames.Select(f => SyntaxFactory.ParseSyntaxTree(File.ReadAllText(f), parseOptions, path: f));
var references = defaultReferences.Value; if (flags.HasFlag(CompilerOptions.ReferenceCore)) {
syntaxTrees = syntaxTrees.Concat(new[] { SyntaxFactory.ParseSyntaxTree(targetFrameworkAttributeSnippet) });
}
IEnumerable<MetadataReference> references;
if (flags.HasFlag(CompilerOptions.ReferenceCore)) {
references = coreDefaultReferences.Value;
} else {
references = defaultReferences.Value;
}
if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) { if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) {
references = references.Concat(visualBasic.Value); references = references.Concat(visualBasic.Value);
} }
@ -466,16 +495,16 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return fileName; return fileName;
} }
} }
public static void RunAndCompareOutput(string testFileName, string outputFile, string decompiledOutputFile, string decompiledCodeFile = null) public static void RunAndCompareOutput(string testFileName, string outputFile, string decompiledOutputFile, string decompiledCodeFile = null)
{ {
string output1, output2, error1, error2; string output1, output2, error1, error2;
int result1 = Tester.Run(outputFile, out output1, out error1); int result1 = Tester.Run(outputFile, out output1, out error1);
int result2 = Tester.Run(decompiledOutputFile, out output2, out error2); int result2 = Tester.Run(decompiledOutputFile, out output2, out error2);
Assert.AreEqual(0, result1, "Exit code != 0; did the test case crash?" + Environment.NewLine + error1); Assert.AreEqual(0, result1, "Exit code != 0; did the test case crash?" + Environment.NewLine + error1);
Assert.AreEqual(0, result2, "Exit code != 0; did the decompiled code crash?" + Environment.NewLine + error2); Assert.AreEqual(0, result2, "Exit code != 0; did the decompiled code crash?" + Environment.NewLine + error2);
if (output1 != output2 || error1 != error2) { if (output1 != output2 || error1 != error2) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.AppendLine($"Test {testFileName} failed: output does not match."); b.AppendLine($"Test {testFileName} failed: output does not match.");

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -81,6 +81,8 @@
<Compile Include="DisassemblerPrettyTestRunner.cs" /> <Compile Include="DisassemblerPrettyTestRunner.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" /> <Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\ILPretty\ConstantBlobs.cs" /> <Compile Include="TestCases\ILPretty\ConstantBlobs.cs" />
<None Include="TestCases\Pretty\AsyncStreams.cs" />
<None Include="TestCases\Pretty\AsyncUsing.cs" />
<Compile Include="TestCases\Pretty\OutVariables.cs" /> <Compile Include="TestCases\Pretty\OutVariables.cs" />
<Compile Include="TestCases\Pretty\CustomTaskType.cs" /> <Compile Include="TestCases\Pretty\CustomTaskType.cs" />
<None Include="TestCases\Ugly\NoForEachStatement.Expected.cs" /> <None Include="TestCases\Ugly\NoForEachStatement.Expected.cs" />

20
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -59,6 +59,12 @@ namespace ICSharpCode.Decompiler.Tests
CompilerOptions.Optimize | CompilerOptions.UseRoslyn CompilerOptions.Optimize | CompilerOptions.UseRoslyn
}; };
static readonly CompilerOptions[] dotnetCoreOnlyOptions =
{
CompilerOptions.UseRoslyn | CompilerOptions.ReferenceCore,
CompilerOptions.Optimize | CompilerOptions.UseRoslyn | CompilerOptions.ReferenceCore
};
static readonly CompilerOptions[] defaultOptions = static readonly CompilerOptions[] defaultOptions =
{ {
CompilerOptions.None, CompilerOptions.None,
@ -301,6 +307,18 @@ namespace ICSharpCode.Decompiler.Tests
Run(cscOptions: cscOptions); Run(cscOptions: cscOptions);
} }
[Test]
public void AsyncStreams([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test]
public void AsyncUsing([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test] [Test]
public void CustomTaskType([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) public void CustomTaskType([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{ {
@ -436,7 +454,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public void InterfaceTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public void InterfaceTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {
RunForLibrary(cscOptions: cscOptions); RunForLibrary(cscOptions: cscOptions | CompilerOptions.ReferenceCore);
} }
[Test] [Test]

9
ICSharpCode.Decompiler.Tests/TestCases/Correctness/TrickyTypes.cs

@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Linq;
namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{ {
@ -27,6 +28,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
InterestingConstants(); InterestingConstants();
TruncatedComp(); TruncatedComp();
StringConcat(); StringConcat();
LinqNullableMin();
LinqNullableMin(1, 2, 3);
} }
static void Print<T>(T val) static void Print<T>(T val)
@ -101,5 +104,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
Print(string.Concat(1, 2)); Print(string.Concat(1, 2));
Print(string.Concat(1, 2, "str")); Print(string.Concat(1, 2, "str"));
} }
static void LinqNullableMin(params int[] arr)
{
Print(string.Format("LinqNullableMin {0}:", arr.Length));
Print(arr.Min(v => (int?)v));
}
} }
} }

2
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs

@ -14,14 +14,12 @@ using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
[assembly: FSharpInterfaceDataVersion(2, 0, 0)] [assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyTitle("ConsoleApplication1")] [assembly: AssemblyTitle("ConsoleApplication1")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConsoleApplication1")] [assembly: AssemblyProduct("ConsoleApplication1")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.1", FrameworkDisplayName = ".NET Framework 4.6.1")]
[assembly: AssemblyDescription("")] [assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyCopyright("Copyright © 2017")]

2
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs

@ -15,14 +15,12 @@ using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
[assembly: FSharpInterfaceDataVersion(2, 0, 0)] [assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyTitle("ConsoleApplication1")] [assembly: AssemblyTitle("ConsoleApplication1")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConsoleApplication1")] [assembly: AssemblyProduct("ConsoleApplication1")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.1", FrameworkDisplayName = ".NET Framework 4.6.1")]
[assembly: AssemblyDescription("")] [assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyCopyright("Copyright © 2017")]

2
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs

@ -6,10 +6,8 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
[assembly: Embedded] [assembly: Embedded]
[assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: TargetFramework(".NETCoreApp,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]

37
ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class AsyncStreams
{
public static async IAsyncEnumerable<int> CountTo(int until)
{
for (int i = 0; i < until; i++) {
yield return i;
await Task.Delay(10);
}
}
public static async IAsyncEnumerable<int> AlwaysThrow()
{
throw null;
yield break;
}
public static async IAsyncEnumerator<int> InfiniteLoop()
{
while (true) {
}
yield break;
}
public static async IAsyncEnumerable<int> InfiniteLoopWithAwait()
{
while (true) {
await Task.Delay(10);
}
yield break;
}
}
}

60
ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs

@ -0,0 +1,60 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal class AsyncUsing
{
internal class AsyncDisposableClass : IAsyncDisposable
{
public ValueTask DisposeAsync()
{
throw new NotImplementedException();
}
}
[StructLayout(LayoutKind.Sequential, Size = 1)]
internal struct AsyncDisposableStruct : IAsyncDisposable
{
public ValueTask DisposeAsync()
{
throw new NotImplementedException();
}
}
public static async void TestAsyncUsing(IAsyncDisposable disposable)
{
await using (disposable) {
Console.WriteLine("Hello");
}
}
public static async void TestAsyncUsingClass()
{
await using (AsyncDisposableClass test = new AsyncDisposableClass()) {
Use(test);
}
}
public static async void TestAsyncUsingStruct()
{
await using (AsyncDisposableStruct asyncDisposableStruct = default(AsyncDisposableStruct)) {
Use(asyncDisposableStruct);
}
}
public static async void TestAsyncUsingNullableStruct()
{
await using (AsyncDisposableStruct? asyncDisposableStruct = new AsyncDisposableStruct?(default(AsyncDisposableStruct))) {
Use(asyncDisposableStruct);
}
}
private static void Use(IAsyncDisposable test)
{
throw new NotImplementedException();
}
}
}

3
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs

@ -16,9 +16,6 @@
// 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.
// We can't test this because "error CS8701: Target runtime doesn't support default interface implementation."
#undef CS80
using System; using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty

8
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -305,6 +305,8 @@ namespace ICSharpCode.Decompiler.CSharp
return true; return true;
if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(typeHandle, metadata)) if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(typeHandle, metadata))
return true; return true;
if (settings.AsyncEnumerator && AsyncAwaitDecompiler.IsCompilerGeneratorAsyncEnumerator(typeHandle, metadata))
return true;
if (settings.FixedBuffers && name.StartsWith("<", StringComparison.Ordinal) && name.Contains("__FixedBuffer")) if (settings.FixedBuffers && name.StartsWith("<", StringComparison.Ordinal) && name.Contains("__FixedBuffer"))
return true; return true;
} else if (type.IsCompilerGenerated(metadata)) { } else if (type.IsCompilerGenerated(metadata)) {
@ -1343,7 +1345,11 @@ namespace ICSharpCode.Decompiler.CSharp
if (localSettings.DecompileMemberBodies && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) { if (localSettings.DecompileMemberBodies && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) {
body.Add(new YieldBreakStatement()); body.Add(new YieldBreakStatement());
} }
RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine); if (function.IsAsync) {
RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine);
} else {
RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine);
}
if (function.StateMachineCompiledWithMono) { if (function.StateMachineCompiledWithMono) {
RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden); RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden);
} }

9
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -989,6 +989,10 @@ namespace ICSharpCode.Decompiler.CSharp
foundMember = null; foundMember = null;
bestCandidateIsExpandedForm = false; bestCandidateIsExpandedForm = false;
var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule); var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule);
Log.WriteLine("IsUnambiguousCall: Performing overload resolution for " + method);
Log.WriteCollection(" Arguments: ", arguments.Select(a => a.ResolveResult));
var or = new OverloadResolution(resolver.Compilation, var or = new OverloadResolution(resolver.Compilation,
firstOptionalArgumentIndex < 0 ? arguments.SelectArray(a => a.ResolveResult) : arguments.Take(firstOptionalArgumentIndex).Select(a => a.ResolveResult).ToArray(), firstOptionalArgumentIndex < 0 ? arguments.SelectArray(a => a.ResolveResult) : arguments.Take(firstOptionalArgumentIndex).Select(a => a.ResolveResult).ToArray(),
argumentNames: firstOptionalArgumentIndex < 0 || argumentNames == null ? argumentNames : argumentNames.Take(firstOptionalArgumentIndex).ToArray(), argumentNames: firstOptionalArgumentIndex < 0 || argumentNames == null ? argumentNames : argumentNames.Take(firstOptionalArgumentIndex).ToArray(),
@ -1043,6 +1047,9 @@ namespace ICSharpCode.Decompiler.CSharp
bool IsUnambiguousAccess(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method, bool IsUnambiguousAccess(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method,
IList<TranslatedExpression> arguments, string[] argumentNames, out IMember foundMember) IList<TranslatedExpression> arguments, string[] argumentNames, out IMember foundMember)
{ {
Log.WriteLine("IsUnambiguousAccess: Performing overload resolution for " + method);
Log.WriteCollection(" Arguments: ", arguments.Select(a => a.ResolveResult));
foundMember = null; foundMember = null;
if (target == null) { if (target == null) {
var result = resolver.ResolveSimpleName(method.AccessorOwner.Name, var result = resolver.ResolveSimpleName(method.AccessorOwner.Name,
@ -1404,6 +1411,8 @@ namespace ICSharpCode.Decompiler.CSharp
bool IsUnambiguousMethodReference(ExpectedTargetDetails expectedTargetDetails, IMethod method, ResolveResult target, IReadOnlyList<IType> typeArguments, out ResolveResult result) bool IsUnambiguousMethodReference(ExpectedTargetDetails expectedTargetDetails, IMethod method, ResolveResult target, IReadOnlyList<IType> typeArguments, out ResolveResult result)
{ {
Log.WriteLine("IsUnambiguousMethodReference: Performing overload resolution for " + method);
var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule); var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule);
var or = new OverloadResolution(resolver.Compilation, var or = new OverloadResolution(resolver.Compilation,
arguments: method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type)), // there are no arguments, use parameter types arguments: method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type)), // there are no arguments, use parameter types

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

@ -1112,6 +1112,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
var opSymbol = UnaryOperatorExpression.GetOperatorRole(opType); var opSymbol = UnaryOperatorExpression.GetOperatorRole(opType);
if (opType == UnaryOperatorType.Await) { if (opType == UnaryOperatorType.Await) {
WriteKeyword(opSymbol); WriteKeyword(opSymbol);
Space();
} else if (!IsPostfixOperator(opType) && opSymbol != null) { } else if (!IsPostfixOperator(opType) && opSymbol != null) {
WriteToken(opSymbol); WriteToken(opSymbol);
} }
@ -1863,6 +1864,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitUsingStatement(UsingStatement usingStatement) public virtual void VisitUsingStatement(UsingStatement usingStatement)
{ {
StartNode(usingStatement); StartNode(usingStatement);
if (usingStatement.IsAsync) {
WriteKeyword(UsingStatement.AwaitRole);
}
WriteKeyword(UsingStatement.UsingKeywordRole); WriteKeyword(UsingStatement.UsingKeywordRole);
Space(policy.SpaceBeforeUsingParentheses); Space(policy.SpaceBeforeUsingParentheses);
LPar(); LPar();

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

@ -152,7 +152,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
if (this.Parameters.Count != parameterTypes.Length) if (this.Parameters.Count != parameterTypes.Length)
return Conversion.None; return Conversion.None;
for (int i = 0; i < parameterTypes.Length; ++i) { for (int i = 0; i < parameterTypes.Length; ++i) {
if (!parameterTypes[i].Equals(this.Parameters[i].Type)) { if (!conversions.IdentityConversion(parameterTypes[i], this.Parameters[i].Type)) {
if (IsImplicitlyTyped) { if (IsImplicitlyTyped) {
// it's possible that different parameter types also lead to a valid conversion // it's possible that different parameter types also lead to a valid conversion
return LambdaConversion.Instance; return LambdaConversion.Instance;
@ -162,7 +162,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
} }
} }
} }
if (returnType.Equals(this.ReturnType)) { if (conversions.IdentityConversion(this.ReturnType, returnType)
|| conversions.ImplicitConversion(this.InferredReturnType, returnType).IsValid) {
return LambdaConversion.Instance; return LambdaConversion.Instance;
} else { } else {
return Conversion.None; return Conversion.None;

30
ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs

@ -393,11 +393,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
static IMethod GetDelegateOrExpressionTreeSignature(IType t) static IMethod GetDelegateOrExpressionTreeSignature(IType t)
{ {
ParameterizedType pt = t as ParameterizedType; if (t.TypeParameterCount == 1 && t.Name == "Expression"
if (pt != null && pt.TypeParameterCount == 1 && pt.Name == "Expression" && t.Namespace == "System.Linq.Expressions")
&& pt.Namespace == "System.Linq.Expressions")
{ {
t = pt.GetTypeArgument(0); t = t.TypeArguments[0];
} }
return t.GetDelegateInvokeMethod(); return t.GetDelegateInvokeMethod();
} }
@ -569,8 +568,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
{ {
Log.WriteLine("MakeExactInference from " + U + " to " + V); Log.WriteLine("MakeExactInference from " + U + " to " + V);
if (V is NullabilityAnnotatedTypeParameter nullableTP) { if (U.Nullability == V.Nullability) {
V = nullableTP.OriginalTypeParameter; U = U.WithoutNullability();
V = V.WithoutNullability();
} }
// If V is one of the unfixed Xi then U is added to the set of bounds for Xi. // If V is one of the unfixed Xi then U is added to the set of bounds for Xi.
@ -611,8 +611,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
TP GetTPForType(IType v) TP GetTPForType(IType v)
{ {
ITypeParameter p = v as ITypeParameter; if (v is NullabilityAnnotatedTypeParameter natp) {
if (p != null) { v = natp.OriginalTypeParameter;
}
if (v is ITypeParameter p) {
int index = p.Index; int index = p.Index;
if (index < typeParameters.Length && typeParameters[index].TypeParameter == p) if (index < typeParameters.Length && typeParameters[index].TypeParameter == p)
return typeParameters[index]; return typeParameters[index];
@ -629,7 +631,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
void MakeLowerBoundInference(IType U, IType V) void MakeLowerBoundInference(IType U, IType V)
{ {
Log.WriteLine(" MakeLowerBoundInference from " + U + " to " + V); Log.WriteLine(" MakeLowerBoundInference from " + U + " to " + V);
if (U.Nullability == V.Nullability) {
U = U.WithoutNullability();
V = V.WithoutNullability();
}
// If V is one of the unfixed Xi then U is added to the set of bounds for Xi. // If V is one of the unfixed Xi then U is added to the set of bounds for Xi.
TP tp = GetTPForType(V); TP tp = GetTPForType(V);
if (tp != null && tp.IsFixed == false) { if (tp != null && tp.IsFixed == false) {
@ -726,7 +732,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
void MakeUpperBoundInference(IType U, IType V) void MakeUpperBoundInference(IType U, IType V)
{ {
Log.WriteLine(" MakeUpperBoundInference from " + U + " to " + V); Log.WriteLine(" MakeUpperBoundInference from " + U + " to " + V);
if (U.Nullability == V.Nullability) {
U = U.WithoutNullability();
V = V.WithoutNullability();
}
// If V is one of the unfixed Xi then U is added to the set of bounds for Xi. // If V is one of the unfixed Xi then U is added to the set of bounds for Xi.
TP tp = GetTPForType(V); TP tp = GetTPForType(V);
if (tp != null && tp.IsFixed == false) { if (tp != null && tp.IsFixed == false) {

22
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -412,14 +412,29 @@ namespace ICSharpCode.Decompiler.CSharp
return transformed; return transformed;
AstNode usingInit = resource; AstNode usingInit = resource;
var var = inst.Variable; var var = inst.Variable;
if (!inst.ResourceExpression.MatchLdNull() && !NullableType.GetUnderlyingType(var.Type).GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IDisposable))) { KnownTypeCode knownTypeCode;
IType disposeType;
string disposeTypeMethodName;
if (inst.IsAsync) {
knownTypeCode = KnownTypeCode.IAsyncDisposable;
disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IAsyncDisposable);
disposeTypeMethodName = "DisposeAsync";
} else {
knownTypeCode = KnownTypeCode.IDisposable;
disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IDisposable);
disposeTypeMethodName = "Dispose";
}
if (!inst.ResourceExpression.MatchLdNull() && !NullableType.GetUnderlyingType(var.Type).GetAllBaseTypes().Any(b => b.IsKnownType(knownTypeCode))) {
Debug.Assert(var.Kind == VariableKind.UsingLocal); Debug.Assert(var.Kind == VariableKind.UsingLocal);
var.Kind = VariableKind.Local; var.Kind = VariableKind.Local;
var disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IDisposable);
var disposeVariable = currentFunction.RegisterVariable( var disposeVariable = currentFunction.RegisterVariable(
VariableKind.Local, disposeType, VariableKind.Local, disposeType,
AssignVariableNames.GenerateVariableName(currentFunction, disposeType) AssignVariableNames.GenerateVariableName(currentFunction, disposeType)
); );
Expression disposeInvocation = new InvocationExpression(new MemberReferenceExpression(exprBuilder.ConvertVariable(disposeVariable).Expression, disposeTypeMethodName));
if (inst.IsAsync) {
disposeInvocation = new UnaryOperatorExpression { Expression = disposeInvocation, Operator = UnaryOperatorType.Await };
}
return new BlockStatement { return new BlockStatement {
new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(var).Expression, resource.Detach())), new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(var).Expression, resource.Detach())),
new TryCatchStatement { new TryCatchStatement {
@ -428,7 +443,7 @@ namespace ICSharpCode.Decompiler.CSharp
new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(disposeVariable).Expression, new AsExpression(exprBuilder.ConvertVariable(var).Expression, exprBuilder.ConvertType(disposeType)))), new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(disposeVariable).Expression, new AsExpression(exprBuilder.ConvertVariable(var).Expression, exprBuilder.ConvertType(disposeType)))),
new IfElseStatement { new IfElseStatement {
Condition = new BinaryOperatorExpression(exprBuilder.ConvertVariable(disposeVariable), BinaryOperatorType.InEquality, new NullReferenceExpression()), Condition = new BinaryOperatorExpression(exprBuilder.ConvertVariable(disposeVariable), BinaryOperatorType.InEquality, new NullReferenceExpression()),
TrueStatement = new ExpressionStatement(new InvocationExpression(new MemberReferenceExpression(exprBuilder.ConvertVariable(disposeVariable).Expression, "Dispose"))) TrueStatement = new ExpressionStatement(disposeInvocation)
} }
} }
}, },
@ -442,6 +457,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
return new UsingStatement { return new UsingStatement {
ResourceAcquisition = usingInit, ResourceAcquisition = usingInit,
IsAsync = inst.IsAsync,
EmbeddedStatement = ConvertAsBlock(inst.Body) EmbeddedStatement = ConvertAsBlock(inst.Body)
}; };
} }

60
ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs

@ -28,57 +28,67 @@
namespace ICSharpCode.Decompiler.CSharp.Syntax namespace ICSharpCode.Decompiler.CSharp.Syntax
{ {
/// <summary> /// <summary>
/// using (ResourceAcquisition) EmbeddedStatement /// [ await ] using (ResourceAcquisition) EmbeddedStatement
/// </summary> /// </summary>
public class UsingStatement : Statement public class UsingStatement : Statement
{ {
public static readonly TokenRole UsingKeywordRole = new TokenRole ("using"); public static readonly TokenRole UsingKeywordRole = new TokenRole("using");
public static readonly TokenRole AwaitRole = UnaryOperatorExpression.AwaitRole;
public static readonly Role<AstNode> ResourceAcquisitionRole = new Role<AstNode>("ResourceAcquisition", AstNode.Null); public static readonly Role<AstNode> ResourceAcquisitionRole = new Role<AstNode>("ResourceAcquisition", AstNode.Null);
public CSharpTokenNode UsingToken { public CSharpTokenNode UsingToken {
get { return GetChildByRole (UsingKeywordRole); } get { return GetChildByRole(UsingKeywordRole); }
}
public CSharpTokenNode AwaitToken {
get { return GetChildByRole(AwaitRole); }
} }
public bool IsAsync {
get { return !GetChildByRole(AwaitRole).IsNull; }
set { SetChildByRole(AwaitRole, value ? new CSharpTokenNode(TextLocation.Empty, null) : null); }
}
public CSharpTokenNode LParToken { public CSharpTokenNode LParToken {
get { return GetChildByRole (Roles.LPar); } get { return GetChildByRole(Roles.LPar); }
} }
/// <summary> /// <summary>
/// Either a VariableDeclarationStatement, or an Expression. /// Either a VariableDeclarationStatement, or an Expression.
/// </summary> /// </summary>
public AstNode ResourceAcquisition { public AstNode ResourceAcquisition {
get { return GetChildByRole (ResourceAcquisitionRole); } get { return GetChildByRole(ResourceAcquisitionRole); }
set { SetChildByRole (ResourceAcquisitionRole, value); } set { SetChildByRole(ResourceAcquisitionRole, value); }
} }
public CSharpTokenNode RParToken { public CSharpTokenNode RParToken {
get { return GetChildByRole (Roles.RPar); } get { return GetChildByRole(Roles.RPar); }
} }
public Statement EmbeddedStatement { public Statement EmbeddedStatement {
get { return GetChildByRole (Roles.EmbeddedStatement); } get { return GetChildByRole(Roles.EmbeddedStatement); }
set { SetChildByRole (Roles.EmbeddedStatement, value); } set { SetChildByRole(Roles.EmbeddedStatement, value); }
} }
public override void AcceptVisitor (IAstVisitor visitor) public override void AcceptVisitor(IAstVisitor visitor)
{ {
visitor.VisitUsingStatement (this); visitor.VisitUsingStatement(this);
} }
public override T AcceptVisitor<T> (IAstVisitor<T> visitor) public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{ {
return visitor.VisitUsingStatement (this); return visitor.VisitUsingStatement(this);
} }
public override S AcceptVisitor<T, S> (IAstVisitor<T, S> visitor, T data) public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{ {
return visitor.VisitUsingStatement (this, data); return visitor.VisitUsingStatement(this, data);
} }
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{ {
UsingStatement o = other as UsingStatement; UsingStatement o = other as UsingStatement;
return o != null && this.ResourceAcquisition.DoMatch(o.ResourceAcquisition, match) && this.EmbeddedStatement.DoMatch(o.EmbeddedStatement, match); return o != null && this.IsAsync == o.IsAsync && this.ResourceAcquisition.DoMatch(o.ResourceAcquisition, match) && this.EmbeddedStatement.DoMatch(o.EmbeddedStatement, match);
} }
} }
} }

2
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -487,7 +487,7 @@ namespace ICSharpCode.Decompiler.CSharp
return newTargetType.IsKnownType(KnownTypeCode.FormattableString) return newTargetType.IsKnownType(KnownTypeCode.FormattableString)
|| newTargetType.IsKnownType(KnownTypeCode.IFormattable); || newTargetType.IsKnownType(KnownTypeCode.IFormattable);
} }
return oldTargetType.Equals(newTargetType); return conversions.IdentityConversion(oldTargetType, newTargetType);
} }
TranslatedExpression LdcI4(ICompilation compilation, int val) TranslatedExpression LdcI4(ICompilation compilation, int val)

38
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -108,12 +108,14 @@ namespace ICSharpCode.Decompiler
if (languageVersion < CSharp.LanguageVersion.CSharp8_0) { if (languageVersion < CSharp.LanguageVersion.CSharp8_0) {
nullableReferenceTypes = false; nullableReferenceTypes = false;
readOnlyMethods = false; readOnlyMethods = false;
asyncUsingAndForEachStatement = false;
asyncEnumerator = false;
} }
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (nullableReferenceTypes || readOnlyMethods) if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement)
return CSharp.LanguageVersion.CSharp8_0; return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers) if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers)
return CSharp.LanguageVersion.CSharp7_3; return CSharp.LanguageVersion.CSharp7_3;
@ -273,6 +275,24 @@ namespace ICSharpCode.Decompiler
} }
} }
bool asyncEnumerator = true;
/// <summary>
/// Decompile IAsyncEnumerator/IAsyncEnumerable.
/// Only has an effect if <see cref="AsyncAwait"/> is enabled.
/// </summary>
[Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.AsyncEnumerator")]
public bool AsyncEnumerator {
get { return asyncEnumerator; }
set {
if (asyncEnumerator != value) {
asyncEnumerator = value;
OnPropertyChanged();
}
}
}
bool decimalConstants = true; bool decimalConstants = true;
/// <summary> /// <summary>
@ -846,7 +866,7 @@ namespace ICSharpCode.Decompiler
bool readOnlyMethods = true; bool readOnlyMethods = true;
[Category("C# 8.0 / VS 2019")] [Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.IsReadOnlyAttributeShouldBeReplacedWithReadonlyInModifiersOnStructsParameters")] [Description("DecompilerSettings.ReadOnlyMethods")]
public bool ReadOnlyMethods { public bool ReadOnlyMethods {
get { return readOnlyMethods; } get { return readOnlyMethods; }
set { set {
@ -857,6 +877,20 @@ namespace ICSharpCode.Decompiler
} }
} }
bool asyncUsingAndForEachStatement = true;
[Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.DetectAsyncUsingAndForeachStatements")]
public bool AsyncUsingAndForEachStatement {
get { return asyncUsingAndForEachStatement; }
set {
if (asyncUsingAndForEachStatement != value) {
asyncUsingAndForEachStatement = value;
OnPropertyChanged();
}
}
}
bool introduceUnmanagedConstraint = true; bool introduceUnmanagedConstraint = true;
/// <summary> /// <summary>

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

@ -62,14 +62,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{ {
Void, Void,
Task, Task,
TaskOfT TaskOfT,
AsyncEnumerator,
AsyncEnumerable
} }
ILTransformContext context; ILTransformContext context;
// These fields are set by MatchTaskCreationPattern() // These fields are set by MatchTaskCreationPattern() or MatchEnumeratorCreationNewObj()
IType taskType; // return type of the async method IType taskType; // return type of the async method; or IAsyncEnumerable{T}/IAsyncEnumerator{T}
IType underlyingReturnType; // return type of the method (only the "T" for Task{T}) IType underlyingReturnType; // return type of the method (only the "T" for Task{T}), for async enumerators this is the type being yielded
AsyncMethodType methodType; AsyncMethodType methodType;
ITypeDefinition stateMachineType; ITypeDefinition stateMachineType;
ITypeDefinition builderType; ITypeDefinition builderType;
@ -78,15 +80,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
int initialState; int initialState;
Dictionary<IField, ILVariable> fieldToParameterMap = new Dictionary<IField, ILVariable>(); Dictionary<IField, ILVariable> fieldToParameterMap = new Dictionary<IField, ILVariable>();
Dictionary<ILVariable, ILVariable> cachedFieldToParameterMap = new Dictionary<ILVariable, ILVariable>(); Dictionary<ILVariable, ILVariable> cachedFieldToParameterMap = new Dictionary<ILVariable, ILVariable>();
IField disposeModeField; // 'disposeMode' field (IAsyncEnumerable/IAsyncEnumerator only)
// These fields are set by AnalyzeMoveNext(): // These fields are set by AnalyzeMoveNext():
ILFunction moveNextFunction; ILFunction moveNextFunction;
ILVariable cachedStateVar; // variable in MoveNext that caches the stateField. ILVariable cachedStateVar; // variable in MoveNext that caches the stateField.
TryCatch mainTryCatch; TryCatch mainTryCatch;
Block setResultAndExitBlock; // block that is jumped to for return statements Block setResultAndExitBlock; // block that is jumped to for return statements
// Note: for async enumerators, a jump to setResultAndExitBlock is a 'yield break;'
int finalState; // final state after the setResultAndExitBlock int finalState; // final state after the setResultAndExitBlock
bool finalStateKnown; bool finalStateKnown;
ILVariable resultVar; // the variable that gets returned by the setResultAndExitBlock ILVariable resultVar; // the variable that gets returned by the setResultAndExitBlock
Block setResultYieldBlock; // block that is jumped to for 'yield return' statements
ILVariable doFinallyBodies; ILVariable doFinallyBodies;
// These fields are set by AnalyzeStateMachine(): // These fields are set by AnalyzeStateMachine():
@ -110,11 +115,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
awaitBlocks.Clear(); awaitBlocks.Clear();
awaitDebugInfos.Clear(); awaitDebugInfos.Clear();
moveNextLeaves.Clear(); moveNextLeaves.Clear();
if (!MatchTaskCreationPattern(function)) if (!MatchTaskCreationPattern(function) && !MatchAsyncEnumeratorCreationPattern(function))
return; return;
try { try {
AnalyzeMoveNext(); AnalyzeMoveNext();
ValidateCatchBlock(); ValidateCatchBlock();
AnalyzeDisposeAsync();
} catch (SymbolicAnalysisFailedException) { } catch (SymbolicAnalysisFailedException) {
return; return;
} }
@ -133,7 +139,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
TranslateCachedFieldsToLocals(); TranslateCachedFieldsToLocals();
FinalizeInlineMoveNext(function); FinalizeInlineMoveNext(function);
((BlockContainer)function.Body).ExpectedResultType = underlyingReturnType.GetStackType(); if (methodType == AsyncMethodType.AsyncEnumerable || methodType == AsyncMethodType.AsyncEnumerator) {
((BlockContainer)function.Body).ExpectedResultType = StackType.Void;
} else {
((BlockContainer)function.Body).ExpectedResultType = underlyingReturnType.GetStackType();
}
// Re-run control flow simplification over the newly constructed set of gotos, // Re-run control flow simplification over the newly constructed set of gotos,
// and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities. // and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities.
@ -164,9 +174,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
CopyPropagation.Propagate(stloc, context); CopyPropagation.Propagate(stloc, context);
} }
new RemoveDeadVariableInit().Run(function, context); new RemoveDeadVariableInit().Run(function, context);
// Run inlining, but don't remove dead variables (they might get revived by TranslateFieldsToLocalAccess)
foreach (var block in function.Descendants.OfType<Block>()) { foreach (var block in function.Descendants.OfType<Block>()) {
// Run inlining, but don't remove dead variables (they might get revived by TranslateFieldsToLocalAccess)
ILInlining.InlineAllInBlock(function, block, context); ILInlining.InlineAllInBlock(function, block, context);
if (IsAsyncEnumerator) {
// Remove lone 'ldc.i4', those are sometimes left over after C# compiler
// optimizes out stores to the state variable.
block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.LdcI4);
}
} }
context.StepEndGroup(); context.StepEndGroup();
} }
@ -337,6 +352,160 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
#endregion #endregion
#region MatchAsyncEnumeratorCreationPattern
private bool MatchAsyncEnumeratorCreationPattern(ILFunction function)
{
if (!context.Settings.AsyncEnumerator)
return false;
taskType = function.ReturnType;
if (taskType.IsKnownType(KnownTypeCode.IAsyncEnumeratorOfT)) {
methodType = AsyncMethodType.AsyncEnumerator;
} else if (taskType.IsKnownType(KnownTypeCode.IAsyncEnumerableOfT)) {
methodType = AsyncMethodType.AsyncEnumerable;
} else {
return false;
}
underlyingReturnType = taskType.TypeArguments.Single();
if (!(function.Body is BlockContainer blockContainer))
return false;
if (blockContainer.Blocks.Count != 1)
return false;
var body = blockContainer.EntryPoint;
if (body.Instructions.Count == 1) {
// No parameters passed to enumerator (not even 'this'):
// ret(newobj(...))
if (!body.Instructions[0].MatchReturn(out var newObj))
return false;
if (MatchEnumeratorCreationNewObj(newObj, context, out initialState, out stateMachineType)) {
// HACK: the normal async/await logic expects 'initialState' to be the 'in progress' state
initialState = -1;
try {
AnalyzeEnumeratorCtor(((NewObj)newObj).Method, context, out builderField, out builderType, out stateField);
} catch (SymbolicAnalysisFailedException) {
return false;
}
return true;
} else {
return false;
}
} else {
// stloc v(newobj<CountUpSlowly> d__0..ctor(ldc.i4 - 2))
// stfld <>4__this(ldloc v, ldloc this)
// stfld <>3__otherParam(ldloc v, ldloc otherParam)
// leave IL_0000(ldloc v)
int pos = 0;
if (!body.Instructions[pos].MatchStLoc(out var v, out var newObj))
return false;
if (!MatchEnumeratorCreationNewObj(newObj, context, out initialState, out stateMachineType))
return false;
pos++;
while (MatchStFld(body.Instructions[pos], v, out var field, out var value)) {
if (!value.MatchLdLoc(out var p))
return false;
fieldToParameterMap[field] = p;
pos++;
}
if (!body.Instructions[pos].MatchReturn(out var returnValue))
return false;
if (!returnValue.MatchLdLoc(v))
return false;
// HACK: the normal async/await logic expects 'initialState' to be the 'in progress' state
initialState = -1;
try {
AnalyzeEnumeratorCtor(((NewObj)newObj).Method, context, out builderField, out builderType, out stateField);
if (methodType == AsyncMethodType.AsyncEnumerable) {
ResolveIEnumerableIEnumeratorFieldMapping();
}
} catch (SymbolicAnalysisFailedException) {
return false;
}
return true;
}
}
static bool MatchEnumeratorCreationNewObj(ILInstruction inst, ILTransformContext context,
out int initialState, out ITypeDefinition stateMachineType)
{
initialState = default;
stateMachineType = default;
// newobj(CurrentType/...::.ctor, ldc.i4(-2))
if (!(inst is NewObj newObj))
return false;
if (newObj.Arguments.Count != 1)
return false;
if (!newObj.Arguments[0].MatchLdcI4(out initialState))
return false;
stateMachineType = newObj.Method.DeclaringTypeDefinition;
if (stateMachineType == null)
return false;
if (stateMachineType.DeclaringTypeDefinition != context.Function.Method.DeclaringTypeDefinition)
return false;
return IsCompilerGeneratorAsyncEnumerator(
(TypeDefinitionHandle)stateMachineType.MetadataToken,
context.TypeSystem.MainModule.metadata);
}
public static bool IsCompilerGeneratorAsyncEnumerator(TypeDefinitionHandle type, MetadataReader metadata)
{
TypeDefinition td;
if (type.IsNil || !type.IsCompilerGeneratedOrIsInCompilerGeneratedClass(metadata) || (td = metadata.GetTypeDefinition(type)).GetDeclaringType().IsNil)
return false;
foreach (var i in td.GetInterfaceImplementations()) {
var tr = metadata.GetInterfaceImplementation(i).Interface.GetFullTypeName(metadata);
if (!tr.IsNested && tr.TopLevelTypeName.Namespace == "System.Collections.Generic" && tr.TopLevelTypeName.Name == "IAsyncEnumerator" && tr.TopLevelTypeName.TypeParameterCount == 1)
return true;
}
return false;
}
static void AnalyzeEnumeratorCtor(IMethod ctor, ILTransformContext context, out IField builderField, out ITypeDefinition builderType, out IField stateField)
{
builderField = null;
stateField = null;
var ctorHandle = (MethodDefinitionHandle)ctor.MetadataToken;
Block body = YieldReturnDecompiler.SingleBlock(YieldReturnDecompiler.CreateILAst(ctorHandle, context).Body);
if (body == null)
throw new SymbolicAnalysisFailedException("Missing enumeratorCtor.Body");
// Block IL_0000 (incoming: 1) {
// call Object..ctor(ldloc this)
// stfld <>1__state(ldloc this, ldloc <>1__state)
// stfld <>t__builder(ldloc this, call Create())
// leave IL_0000 (nop)
// }
foreach (var inst in body.Instructions) {
if (inst.MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis()
&& value.MatchLdLoc(out var arg)
&& arg.Kind == VariableKind.Parameter && arg.Index == 0) {
stateField = (IField)field.MemberDefinition;
}
if (inst.MatchStFld(out target, out field, out value)
&& target.MatchLdThis()
&& value is Call call && call.Method.Name == "Create") {
builderField = (IField)field.MemberDefinition;
}
}
if (stateField == null || builderField == null)
throw new SymbolicAnalysisFailedException();
builderType = builderField.Type.GetDefinition();
if (builderType == null)
throw new SymbolicAnalysisFailedException();
}
private void ResolveIEnumerableIEnumeratorFieldMapping()
{
var getAsyncEnumerator = stateMachineType.Methods.FirstOrDefault(m => m.Name.EndsWith(".GetAsyncEnumerator", StringComparison.Ordinal));
if (getAsyncEnumerator == null)
throw new SymbolicAnalysisFailedException();
YieldReturnDecompiler.ResolveIEnumerableIEnumeratorFieldMapping((MethodDefinitionHandle)getAsyncEnumerator.MetadataToken, context, fieldToParameterMap);
}
#endregion
#region AnalyzeMoveNext #region AnalyzeMoveNext
/// <summary> /// <summary>
/// First peek into MoveNext(); analyzes everything outside the big try-catch. /// First peek into MoveNext(); analyzes everything outside the big try-catch.
@ -353,8 +522,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
moveNextFunction = YieldReturnDecompiler.CreateILAst(moveNextMethod, context); moveNextFunction = YieldReturnDecompiler.CreateILAst(moveNextMethod, context);
if (!(moveNextFunction.Body is BlockContainer blockContainer)) if (!(moveNextFunction.Body is BlockContainer blockContainer))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
if (blockContainer.Blocks.Count != 2 && blockContainer.Blocks.Count != 1)
throw new SymbolicAnalysisFailedException();
if (blockContainer.EntryPoint.IncomingEdgeCount != 1) if (blockContainer.EntryPoint.IncomingEdgeCount != 1)
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
cachedStateVar = null; cachedStateVar = null;
@ -385,12 +552,51 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (((BlockContainer)mainTryCatch.TryBlock).EntryPoint.Instructions[0] is StLoc initDoFinallyBodies if (((BlockContainer)mainTryCatch.TryBlock).EntryPoint.Instructions[0] is StLoc initDoFinallyBodies
&& initDoFinallyBodies.Variable.Kind == VariableKind.Local && initDoFinallyBodies.Variable.Kind == VariableKind.Local
&& initDoFinallyBodies.Variable.Type.IsKnownType(KnownTypeCode.Boolean) && initDoFinallyBodies.Variable.Type.IsKnownType(KnownTypeCode.Boolean)
&& initDoFinallyBodies.Value.MatchLdcI4(1)) && initDoFinallyBodies.Value.MatchLdcI4(1)) {
{
doFinallyBodies = initDoFinallyBodies.Variable; doFinallyBodies = initDoFinallyBodies.Variable;
} }
setResultAndExitBlock = blockContainer.Blocks.ElementAtOrDefault(1); Debug.Assert(blockContainer.Blocks[0] == blockContainer.EntryPoint); // already checked this block
pos = 1;
if (MatchYieldBlock(blockContainer, pos)) {
setResultYieldBlock = blockContainer.Blocks[pos];
pos++;
} else {
setResultYieldBlock = null;
}
setResultAndExitBlock = blockContainer.Blocks.ElementAtOrDefault(pos);
CheckSetResultAndExitBlock(blockContainer);
if (pos + 1 < blockContainer.Blocks.Count)
throw new SymbolicAnalysisFailedException("too many blocks");
}
private bool IsAsyncEnumerator => methodType == AsyncMethodType.AsyncEnumerable || methodType == AsyncMethodType.AsyncEnumerator;
bool MatchYieldBlock(BlockContainer blockContainer, int pos)
{
if (!IsAsyncEnumerator)
return false;
var block = blockContainer.Blocks.ElementAtOrDefault(pos);
if (block == null)
return false;
// call SetResult(ldflda <>v__promiseOfValueOrEnd(ldloc this), ldc.i4 1)
// leave IL_0000(nop)
if (block.Instructions.Count != 2)
return false;
if (!MatchCall(block.Instructions[0], "SetResult", out var args))
return false;
if (args.Count != 2)
return false;
if (!IsBuilderOrPromiseFieldOnThis(args[0]))
return false;
if (!args[1].MatchLdcI4(1))
return false;
return block.Instructions[1].MatchLeave(blockContainer);
}
void CheckSetResultAndExitBlock(BlockContainer blockContainer)
{
if (setResultAndExitBlock == null) { if (setResultAndExitBlock == null) {
// This block can be absent if the function never exits normally, // This block can be absent if the function never exits normally,
// but always throws an exception/loops infinitely. // but always throws an exception/loops infinitely.
@ -408,17 +614,29 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
finalStateKnown = true; finalStateKnown = true;
if (!MatchCall(setResultAndExitBlock.Instructions[1], "SetResult", out var args)) if (!MatchCall(setResultAndExitBlock.Instructions[1], "SetResult", out var args))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
if (!IsBuilderFieldOnThis(args[0])) if (!IsBuilderOrPromiseFieldOnThis(args[0]))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
if (methodType == AsyncMethodType.TaskOfT) { switch (methodType) {
if (args.Count != 2) case AsyncMethodType.TaskOfT:
throw new SymbolicAnalysisFailedException(); if (args.Count != 2)
if (!args[1].MatchLdLoc(out resultVar)) throw new SymbolicAnalysisFailedException();
throw new SymbolicAnalysisFailedException(); if (!args[1].MatchLdLoc(out resultVar))
} else { throw new SymbolicAnalysisFailedException();
resultVar = null; break;
if (args.Count != 1) case AsyncMethodType.Task:
throw new SymbolicAnalysisFailedException(); case AsyncMethodType.Void:
resultVar = null;
if (args.Count != 1)
throw new SymbolicAnalysisFailedException();
break;
case AsyncMethodType.AsyncEnumerable:
case AsyncMethodType.AsyncEnumerator:
resultVar = null;
if (args.Count != 2)
throw new SymbolicAnalysisFailedException();
if (!args[1].MatchLdcI4(0))
throw new SymbolicAnalysisFailedException();
break;
} }
if (!setResultAndExitBlock.Instructions[2].MatchLeave(blockContainer)) if (!setResultAndExitBlock.Instructions[2].MatchLeave(blockContainer))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
@ -442,8 +660,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (!handler.Filter.MatchLdcI4(1)) if (!handler.Filter.MatchLdcI4(1))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
var catchBlock = YieldReturnDecompiler.SingleBlock(handler.Body); var catchBlock = YieldReturnDecompiler.SingleBlock(handler.Body);
if (catchBlock == null)
throw new SymbolicAnalysisFailedException();
catchHandlerOffset = catchBlock.StartILOffset; catchHandlerOffset = catchBlock.StartILOffset;
if (catchBlock?.Instructions.Count != 4) if (catchBlock.Instructions.Count != 4)
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
// stloc exception(ldloc E_143) // stloc exception(ldloc E_143)
if (!(catchBlock.Instructions[0] is StLoc stloc)) if (!(catchBlock.Instructions[0] is StLoc stloc))
@ -465,7 +685,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
if (args.Count != 2) if (args.Count != 2)
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
if (!IsBuilderFieldOnThis(args[0])) if (!IsBuilderOrPromiseFieldOnThis(args[0]))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
if (!args[1].MatchLdLoc(stloc.Variable)) if (!args[1].MatchLdLoc(stloc.Variable))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
@ -490,19 +710,55 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return target.MatchLdThis() && field.MemberDefinition == builderField; return target.MatchLdThis() && field.MemberDefinition == builderField;
} }
bool IsBuilderOrPromiseFieldOnThis(ILInstruction inst)
{
if (methodType == AsyncMethodType.AsyncEnumerable || methodType == AsyncMethodType.AsyncEnumerator) {
return true; // TODO: check against uses of promise fields in other methods?
} else {
return IsBuilderFieldOnThis(inst);
}
}
bool MatchStateAssignment(ILInstruction inst, out int newState) bool MatchStateAssignment(ILInstruction inst, out int newState)
{ {
// stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(stateId)) // stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(stateId))
if (inst.MatchStFld(out var target, out var field, out var value) if (inst.MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis() && StackSlotValue(target).MatchLdThis()
&& field.MemberDefinition == stateField && field.MemberDefinition == stateField
&& value.MatchLdcI4(out newState)) && StackSlotValue(value).MatchLdcI4(out newState))
{ {
return true; return true;
} }
newState = 0; newState = 0;
return false; return false;
} }
/// <summary>
/// Analyse the DisposeAsync() method in order to find the disposeModeField.
/// </summary>
private void AnalyzeDisposeAsync()
{
disposeModeField = null;
if (!IsAsyncEnumerator) {
return;
}
var disposeAsync = stateMachineType.Methods.FirstOrDefault(m => m.Name.EndsWith(".DisposeAsync", StringComparison.Ordinal));
if (disposeAsync == null)
throw new SymbolicAnalysisFailedException("Could not find DisposeAsync()");
var disposeAsyncHandle = (MethodDefinitionHandle)disposeAsync.MetadataToken;
var function = YieldReturnDecompiler.CreateILAst(disposeAsyncHandle, context);
foreach (var store in function.Descendants) {
if (!store.MatchStFld(out var target, out var field, out var value))
continue;
if (!target.MatchLdThis())
continue;
if (!value.MatchLdcI4(1))
throw new SymbolicAnalysisFailedException();
if (disposeModeField != null)
throw new SymbolicAnalysisFailedException("Multiple stores to disposeMode in DisposeAsync()");
disposeModeField = (IField)field.MemberDefinition;
}
}
#endregion #endregion
#region InlineBodyOfMoveNext #region InlineBodyOfMoveNext
@ -513,6 +769,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
function.AsyncReturnType = underlyingReturnType; function.AsyncReturnType = underlyingReturnType;
function.MoveNextMethod = moveNextFunction.Method; function.MoveNextMethod = moveNextFunction.Method;
function.CodeSize = moveNextFunction.CodeSize; function.CodeSize = moveNextFunction.CodeSize;
function.IsIterator = IsAsyncEnumerator;
moveNextFunction.Variables.Clear(); moveNextFunction.Variables.Clear();
moveNextFunction.ReleaseRef(); moveNextFunction.ReleaseRef();
foreach (var branch in function.Descendants.OfType<Branch>()) { foreach (var branch in function.Descendants.OfType<Branch>()) {
@ -520,6 +777,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
branch.ReplaceWith(new Leave((BlockContainer)function.Body, resultVar == null ? null : new LdLoc(resultVar)).WithILRange(branch)); branch.ReplaceWith(new Leave((BlockContainer)function.Body, resultVar == null ? null : new LdLoc(resultVar)).WithILRange(branch));
} }
} }
if (setResultYieldBlock != null) {
// We still might have branches to this block; and we can't quite yet get rid of it.
((BlockContainer)function.Body).Blocks.Add(setResultYieldBlock);
}
foreach (var leave in function.Descendants.OfType<Leave>()) { foreach (var leave in function.Descendants.OfType<Leave>()) {
if (leave.TargetContainer == moveNextFunction.Body) { if (leave.TargetContainer == moveNextFunction.Body) {
leave.TargetContainer = (BlockContainer)function.Body; leave.TargetContainer = (BlockContainer)function.Body;
@ -563,7 +824,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary> /// </summary>
void AnalyzeStateMachine(ILFunction function) void AnalyzeStateMachine(ILFunction function)
{ {
context.Step("AnalyzeStateMachine()", function); context.StepStartGroup("AnalyzeStateMachine()", function);
smallestAwaiterVarIndex = int.MaxValue; smallestAwaiterVarIndex = int.MaxValue;
foreach (var container in function.Descendants.OfType<BlockContainer>()) { foreach (var container in function.Descendants.OfType<BlockContainer>()) {
// Use a separate state range analysis per container. // Use a separate state range analysis per container.
@ -577,6 +838,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.CancellationToken.ThrowIfCancellationRequested(); context.CancellationToken.ThrowIfCancellationRequested();
if (block.Instructions.Last() is Leave leave && moveNextLeaves.Contains(leave)) { if (block.Instructions.Last() is Leave leave && moveNextLeaves.Contains(leave)) {
// This is likely an 'await' block // This is likely an 'await' block
context.Step($"AnalyzeAwaitBlock({block.StartILOffset:x4})", block);
if (AnalyzeAwaitBlock(block, out var awaiterVar, out var awaiterField, out int state, out int yieldOffset)) { if (AnalyzeAwaitBlock(block, out var awaiterVar, out var awaiterField, out int state, out int yieldOffset)) {
block.Instructions.Add(new Await(new LdLoca(awaiterVar))); block.Instructions.Add(new Await(new LdLoca(awaiterVar)));
Block targetBlock = stateToBlockMap.GetOrDefault(state); Block targetBlock = stateToBlockMap.GetOrDefault(state);
@ -591,7 +853,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
smallestAwaiterVarIndex = awaiterVar.Index.Value; smallestAwaiterVarIndex = awaiterVar.Index.Value;
} }
} }
} else if (block.Instructions.Last().MatchBranch(setResultYieldBlock)) {
// This is a 'yield return' in an async enumerator.
context.Step($"AnalyzeYieldReturn({block.StartILOffset:x4})", block);
if (AnalyzeYieldReturn(block, out var yieldValue, out int state)) {
block.Instructions.Add(new YieldReturn(yieldValue));
Block targetBlock = stateToBlockMap.GetOrDefault(state);
if (targetBlock != null) {
block.Instructions.Add(new Branch(targetBlock));
} else {
block.Instructions.Add(new InvalidBranch("Could not find block for state " + state));
}
} else {
block.Instructions.Add(new InvalidBranch("Could not detect 'yield return'"));
}
} }
TransformYieldBreak(block);
}
foreach (var block in container.Blocks) {
SimplifyIfDisposeMode(block);
} }
// Skip the state dispatcher and directly jump to the initial state // Skip the state dispatcher and directly jump to the initial state
var entryPoint = stateToBlockMap.GetOrDefault(initialState); var entryPoint = stateToBlockMap.GetOrDefault(initialState);
@ -604,6 +884,89 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
container.SortBlocks(deleteUnreachableBlocks: true); container.SortBlocks(deleteUnreachableBlocks: true);
} }
context.StepEndGroup();
}
private bool TransformYieldBreak(Block block)
{
// stfld disposeMode(ldloc this, ldc.i4 1)
// br nextBlock
if (block.Instructions.Count < 2)
return false;
if (!(block.Instructions.Last() is Branch branch))
return false;
if (!block.Instructions[block.Instructions.Count - 2].MatchStFld(out var target, out var field, out var value))
return false;
if (!target.MatchLdThis())
return false;
if (field.MemberDefinition != disposeModeField)
return false;
if (!value.MatchLdcI4(1))
return false;
// Detected a 'yield break;'
context.Step($"TransformYieldBreak({block.StartILOffset:x4})", block);
var breakTarget = FindYieldBreakTarget(branch.TargetBlock);
if (breakTarget is Block targetBlock) {
branch.TargetBlock = targetBlock;
} else {
Debug.Assert(breakTarget is BlockContainer);
branch.ReplaceWith(new Leave((BlockContainer)breakTarget).WithILRange(branch));
}
return true;
}
ILInstruction FindYieldBreakTarget(Block block)
{
// We'll follow the branch and evaluate the following instructions
// under the assumption that disposeModeField==1, which lets us follow a series of jumps
// to determine the final target.
var visited = new HashSet<Block>();
var evalContext = new SymbolicEvaluationContext(disposeModeField);
while (true) {
for (int i = 0; i < block.Instructions.Count; i++) {
ILInstruction inst = block.Instructions[i];
while (inst.MatchIfInstruction(out var condition, out var trueInst, out var falseInst)) {
var condVal = evalContext.Eval(condition).AsBool();
if (condVal.Type == SymbolicValueType.IntegerConstant) {
inst = condVal.Constant != 0 ? trueInst : falseInst;
} else if (condVal.Type == SymbolicValueType.StateInSet) {
inst = condVal.ValueSet.Contains(1) ? trueInst : falseInst;
} else {
return block;
}
}
if (inst.MatchBranch(out var targetBlock)) {
if (visited.Add(block)) {
block = targetBlock;
break; // continue with next block
} else {
return block; // infinite loop detected
}
} else if (inst is Leave leave && leave.Value.OpCode == OpCode.Nop) {
return leave.TargetContainer;
} else if (inst.OpCode == OpCode.Nop) {
continue; // continue with next instruction in this block
} else {
return block;
}
}
}
}
private bool SimplifyIfDisposeMode(Block block)
{
// if (logic.not(ldfld disposeMode(ldloc this))) br falseInst
// br trueInst
if (!block.MatchIfAtEndOfBlock(out var condition, out _, out var falseInst))
return false;
if (!condition.MatchLdFld(out var target, out var field))
return false;
if (!(target.MatchLdThis() && field.MemberDefinition == disposeModeField))
return false;
block.Instructions[block.Instructions.Count - 2] = falseInst;
block.Instructions.RemoveAt(block.Instructions.Count - 1);
return true;
} }
bool AnalyzeAwaitBlock(Block block, out ILVariable awaiter, out IField awaiterField, out int state, out int yieldOffset) bool AnalyzeAwaitBlock(Block block, out ILVariable awaiter, out IField awaiterField, out int state, out int yieldOffset)
@ -705,6 +1068,50 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
return inst; return inst;
} }
private bool AnalyzeYieldReturn(Block block, out ILInstruction yieldValue, out int newState)
{
yieldValue = default;
newState = default;
Debug.Assert(block.Instructions.Last().MatchBranch(setResultYieldBlock));
// stfld current(ldloc this, ldstr "yieldValue")
// stloc S_45(ldloc this)
// stloc S_46(ldc.i4 -5)
// stloc V_0(ldloc S_46)
// stfld stateField(ldloc S_45, ldloc S_46)
// br setResultYieldBlock
int pos = block.Instructions.Count - 2;
// Immediately before the 'yield return', there should be a state assignment:
if (pos < 0 || !MatchStateAssignment(block.Instructions[pos], out newState))
return false;
pos--;
if (pos >= 0 && block.Instructions[pos].MatchStLoc(cachedStateVar, out var cachedStateNewValue)) {
if (StackSlotValue(cachedStateNewValue).MatchLdcI4(newState)) {
pos--; // OK, ignore V_0 store
} else {
return false;
}
}
while (pos >= 0 && block.Instructions[pos] is StLoc stloc) {
if (stloc.Variable.Kind != VariableKind.StackSlot)
return false;
if (!SemanticHelper.IsPure(stloc.Value.Flags))
return false;
pos--;
}
if (pos < 0 || !block.Instructions[pos].MatchStFld(out var target, out var field, out yieldValue))
return false;
if (!StackSlotValue(target).MatchLdThis())
return false;
// TODO: check that we are accessing the current field (compare with get_Current)
block.Instructions.RemoveRange(pos, block.Instructions.Count - pos);
return true;
}
#endregion #endregion
#region DetectAwaitPattern #region DetectAwaitPattern
@ -761,7 +1168,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// continue matching call get_IsCompleted(ldloca awaiterVar) // continue matching call get_IsCompleted(ldloca awaiterVar)
if (!MatchCall(condition, "get_IsCompleted", out var isCompletedArgs) || isCompletedArgs.Count != 1) if (!MatchCall(condition, "get_IsCompleted", out var isCompletedArgs) || isCompletedArgs.Count != 1)
return; return;
if (!isCompletedArgs[0].MatchLdLocRef(awaiterVar)) if (!UnwrapConvUnknown(isCompletedArgs[0]).MatchLdLocRef(awaiterVar))
return; return;
// Check awaitBlock and resumeBlock: // Check awaitBlock and resumeBlock:
if (!awaitBlocks.TryGetValue(awaitBlock, out var awaitBlockData)) if (!awaitBlocks.TryGetValue(awaitBlock, out var awaitBlockData))
@ -779,14 +1186,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return; return;
if (!MatchCall(getResultCall, "GetResult", out var getResultArgs) || getResultArgs.Count != 1) if (!MatchCall(getResultCall, "GetResult", out var getResultArgs) || getResultArgs.Count != 1)
return; return;
if (!getResultArgs[0].MatchLdLocRef(awaiterVar)) if (!UnwrapConvUnknown(getResultArgs[0]).MatchLdLocRef(awaiterVar))
return; return;
// All checks successful, let's transform. // All checks successful, let's transform.
context.Step("Transform await pattern", block); context.Step("Transform await pattern", block);
block.Instructions.RemoveAt(block.Instructions.Count - 3); // remove getAwaiter call block.Instructions.RemoveAt(block.Instructions.Count - 3); // remove getAwaiter call
block.Instructions.RemoveAt(block.Instructions.Count - 2); // remove if (isCompleted) block.Instructions.RemoveAt(block.Instructions.Count - 2); // remove if (isCompleted)
((Branch)block.Instructions.Last()).TargetBlock = completedBlock; // instead, directly jump to completed block ((Branch)block.Instructions.Last()).TargetBlock = completedBlock; // instead, directly jump to completed block
Await awaitInst = new Await(getAwaiterCall.Arguments.Single()); Await awaitInst = new Await(UnwrapConvUnknown(getAwaiterCall.Arguments.Single()));
awaitInst.GetResultMethod = getResultCall.Method; awaitInst.GetResultMethod = getResultCall.Method;
awaitInst.GetAwaiterMethod = getAwaiterCall.Method; awaitInst.GetAwaiterMethod = getAwaiterCall.Method;
getResultCall.ReplaceWith(awaitInst); getResultCall.ReplaceWith(awaitInst);
@ -798,7 +1205,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
} }
static ILInstruction UnwrapConvUnknown(ILInstruction inst)
{
if (inst is Conv conv && conv.TargetType == PrimitiveType.Unknown) {
return conv.Argument;
}
return inst;
}
bool CheckAwaitBlock(Block block, out Block resumeBlock, out IField stackField) bool CheckAwaitBlock(Block block, out Block resumeBlock, out IField stackField)
{ {
// awaitBlock: // awaitBlock:

2
ICSharpCode.Decompiler/IL/ControlFlow/AwaitInCatchTransform.cs

@ -197,6 +197,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{ {
public static void Run(ILFunction function, ILTransformContext context) public static void Run(ILFunction function, ILTransformContext context)
{ {
if (!context.Settings.AwaitInCatchFinally)
return;
HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>(); HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
// analyze all try-catch statements in the function // analyze all try-catch statements in the function

6
ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs

@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.CancellationToken.ThrowIfCancellationRequested(); context.CancellationToken.ThrowIfCancellationRequested();
RemoveNopInstructions(block); RemoveNopInstructions(block);
RemoveDeadStackStores(block, aggressive: context.Settings.RemoveDeadStores); RemoveDeadStackStores(block, context);
InlineVariableInReturnBlock(block, context); InlineVariableInReturnBlock(block, context);
// 1st pass SimplifySwitchInstruction before SimplifyBranchChains() // 1st pass SimplifySwitchInstruction before SimplifyBranchChains()
@ -70,13 +70,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.Nop); block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.Nop);
} }
private void RemoveDeadStackStores(Block block, bool aggressive) private static void RemoveDeadStackStores(Block block, ILTransformContext context)
{ {
bool aggressive = context.Settings.RemoveDeadStores;
// Previously copy propagation did this; // Previously copy propagation did this;
// ideally the ILReader would already do this, // ideally the ILReader would already do this,
// for now do this here (even though it's not control-flow related). // for now do this here (even though it's not control-flow related).
for (int i = block.Instructions.Count - 1; i >= 0; i--) { for (int i = block.Instructions.Count - 1; i >= 0; i--) {
if (block.Instructions[i] is StLoc stloc && stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 0 && stloc.Variable.Kind == VariableKind.StackSlot) { if (block.Instructions[i] is StLoc stloc && stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 0 && stloc.Variable.Kind == VariableKind.StackSlot) {
context.Step($"Remove dead stack store {stloc.Variable.Name}", stloc);
if (aggressive ? SemanticHelper.IsPure(stloc.Value.Flags) : IsSimple(stloc.Value)) { if (aggressive ? SemanticHelper.IsPure(stloc.Value.Flags) : IsSimple(stloc.Value)) {
Debug.Assert(SemanticHelper.IsPure(stloc.Value.Flags)); Debug.Assert(SemanticHelper.IsPure(stloc.Value.Flags));
block.Instructions.RemoveAt(i++); block.Instructions.RemoveAt(i++);

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

@ -305,6 +305,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary> /// </summary>
bool MatchEnumeratorCreationNewObj(ILInstruction inst) bool MatchEnumeratorCreationNewObj(ILInstruction inst)
{ {
return MatchEnumeratorCreationNewObj(inst, metadata, currentType,
out enumeratorCtor, out enumeratorType);
}
internal static bool MatchEnumeratorCreationNewObj(ILInstruction inst,
MetadataReader metadata, TypeDefinitionHandle currentType,
out MethodDefinitionHandle enumeratorCtor, out TypeDefinitionHandle enumeratorType)
{
enumeratorCtor = default;
enumeratorType = default;
// newobj(CurrentType/...::.ctor, ldc.i4(-2)) // newobj(CurrentType/...::.ctor, ldc.i4(-2))
if (!(inst is NewObj newObj)) if (!(inst is NewObj newObj))
return false; return false;
@ -443,6 +453,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
MethodDefinitionHandle getEnumeratorMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault( MethodDefinitionHandle getEnumeratorMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(
m => metadata.GetString(metadata.GetMethodDefinition(m).Name).StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal) m => metadata.GetString(metadata.GetMethodDefinition(m).Name).StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal)
&& metadata.GetString(metadata.GetMethodDefinition(m).Name).EndsWith(".GetEnumerator", StringComparison.Ordinal)); && metadata.GetString(metadata.GetMethodDefinition(m).Name).EndsWith(".GetEnumerator", StringComparison.Ordinal));
ResolveIEnumerableIEnumeratorFieldMapping(getEnumeratorMethod, context, fieldToParameterMap);
}
internal static void ResolveIEnumerableIEnumeratorFieldMapping(MethodDefinitionHandle getEnumeratorMethod, ILTransformContext context,
Dictionary<IField, ILVariable> fieldToParameterMap)
{
if (getEnumeratorMethod.IsNil) if (getEnumeratorMethod.IsNil)
return; // no mappings (maybe it's just an IEnumerator implementation?) return; // no mappings (maybe it's just an IEnumerator implementation?)
var function = CreateILAst(getEnumeratorMethod, context); var function = CreateILAst(getEnumeratorMethod, context);

21
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -311,6 +311,27 @@ namespace ICSharpCode.Decompiler.IL
return false; return false;
return this.FinalInstruction.MatchLdLoc(tmp); return this.FinalInstruction.MatchLdLoc(tmp);
} }
public bool MatchIfAtEndOfBlock(out ILInstruction condition, out ILInstruction trueInst, out ILInstruction falseInst)
{
condition = null;
trueInst = null;
falseInst = null;
if (Instructions.Count < 2)
return false;
if (Instructions[Instructions.Count - 2].MatchIfInstruction(out condition, out trueInst)) {
// Swap trueInst<>falseInst for every logic.not in the condition.
falseInst = Instructions.Last();
while (condition.MatchLogicNot(out var arg)) {
condition = arg;
ILInstruction tmp = trueInst;
trueInst = falseInst;
falseInst = tmp;
}
return true;
}
return false;
}
} }
public enum BlockKind public enum BlockKind

8
ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs

@ -36,10 +36,16 @@ namespace ICSharpCode.Decompiler.IL
/// </remarks> /// </remarks>
partial class UsingInstruction partial class UsingInstruction
{ {
public bool IsAsync { get; set; }
public override void WriteTo(ITextOutput output, ILAstWritingOptions options) public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{ {
WriteILRange(output, options); WriteILRange(output, options);
output.Write("using ("); output.Write("using");
if (IsAsync) {
output.Write(".async");
}
output.Write(" (");
Variable.WriteTo(output); Variable.WriteTo(output);
output.Write(" = "); output.Write(" = ");
ResourceExpression.WriteTo(output, options); ResourceExpression.WriteTo(output, options);

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

@ -34,7 +34,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!context.Settings.UsingStatement) return; if (!context.Settings.UsingStatement) return;
this.context = context; this.context = context;
for (int i = block.Instructions.Count - 1; i >= 0; i--) { for (int i = block.Instructions.Count - 1; i >= 0; i--) {
if (!TransformUsing(block, i) && !TransformUsingVB(block, i)) if (!TransformUsing(block, i) && !TransformUsingVB(block, i) && !TransformAsyncUsing(block, i))
continue; continue;
// This happens in some cases: // This happens in some cases:
// Use correct index after transformation. // Use correct index after transformation.
@ -167,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull) bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull, in string disposeMethodFullName = "System.IDisposable.Dispose", KnownTypeCode disposeTypeCode = KnownTypeCode.IDisposable)
{ {
var entryPoint = container.EntryPoint; var entryPoint = container.EntryPoint;
if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1) if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1)
@ -180,17 +180,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (castIndex > -1) { if (castIndex > -1) {
if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst)) if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst))
return false; return false;
if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(KnownTypeCode.IDisposable)) if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(disposeTypeCode))
return false; return false;
if (!tempVar.IsSingleDefinition) if (!tempVar.IsSingleDefinition)
return false; return false;
isReference = true; isReference = true;
if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck)) if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode))
return false; return false;
if (tempVar.LoadCount != numObjVarLoadsInCheck) if (tempVar.LoadCount != numObjVarLoadsInCheck)
return false; return false;
} else { } else {
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _)) if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _, disposeMethodFullName, disposeTypeCode))
return false; return false;
} }
if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop()) if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop())
@ -198,9 +198,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck) bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck, in string disposeMethodFullName, KnownTypeCode disposeTypeCode)
{ {
numObjVarLoadsInCheck = 2; numObjVarLoadsInCheck = 2;
ILInstruction disposeInvocation;
CallVirt callVirt; CallVirt callVirt;
if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT)) { if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT)) {
if (checkInst.MatchIfInstruction(out var condition, out var disposeInst)) { if (checkInst.MatchIfInstruction(out var condition, out var disposeInst)) {
@ -208,22 +209,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
return false; return false;
callVirt = disposeBlock.Instructions[0] as CallVirt; disposeInvocation = disposeBlock.Instructions[0];
} else if (checkInst.MatchNullableRewrap(out disposeInst)) { } else if (checkInst.MatchNullableRewrap(out disposeInst)) {
callVirt = disposeInst as CallVirt; disposeInvocation = disposeInst;
} else { } else {
return false; return false;
} }
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref disposeInvocation))
return false;
}
callVirt = disposeInvocation as CallVirt;
if (callVirt == null) if (callVirt == null)
return false; return false;
if (callVirt.Method.FullName != "System.IDisposable.Dispose") if (callVirt.Method.FullName != disposeMethodFullName)
return false; return false;
if (callVirt.Method.Parameters.Count > 0) if (callVirt.Method.Parameters.Count > 0)
return false; return false;
if (callVirt.Arguments.Count != 1) if (callVirt.Arguments.Count != 1)
return false; return false;
var firstArg = callVirt.Arguments.FirstOrDefault(); var firstArg = callVirt.Arguments.FirstOrDefault();
if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(KnownTypeCode.IDisposable))) { if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(disposeTypeCode))) {
if (!firstArg.MatchAddressOf(out var innerArg2, out _)) if (!firstArg.MatchAddressOf(out var innerArg2, out _))
return false; return false;
return NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar) return NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar)
@ -255,7 +261,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
return false; return false;
if (!(disposeBlock.Instructions[0] is CallVirt cv)) disposeInvocation = disposeBlock.Instructions[0];
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref disposeInvocation))
return false;
}
if (!(disposeInvocation is CallVirt cv))
return false; return false;
target = cv.Arguments.FirstOrDefault(); target = cv.Arguments.FirstOrDefault();
if (target == null) if (target == null)
@ -264,6 +275,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = newTarget; target = newTarget;
callVirt = cv; callVirt = cv;
} else { } else {
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref checkInst))
return false;
}
if (!(checkInst is CallVirt cv)) if (!(checkInst is CallVirt cv))
return false; return false;
target = cv.Arguments.FirstOrDefault(); target = cv.Arguments.FirstOrDefault();
@ -275,7 +290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
callVirt = cv; callVirt = cv;
} }
if (callVirt.Method.FullName != "System.IDisposable.Dispose") if (callVirt.Method.FullName != disposeMethodFullName)
return false; return false;
if (callVirt.Method.Parameters.Count > 0) if (callVirt.Method.Parameters.Count > 0)
return false; return false;
@ -286,8 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|| (usingNull && callVirt.Arguments[0].MatchLdNull()) || (usingNull && callVirt.Arguments[0].MatchLdNull())
|| (isReference && checkInst is NullableRewrap || (isReference && checkInst is NullableRewrap
&& target.MatchIsInst(out var arg, out var type2) && target.MatchIsInst(out var arg, out var type2)
&& arg.MatchLdLoc(objVar) && type2.IsKnownType(KnownTypeCode.IDisposable)); && arg.MatchLdLoc(objVar) && type2.IsKnownType(disposeTypeCode));
} }
} }
/// <summary>
/// stloc test(resourceExpression)
/// .try BlockContainer {
/// Block IL_002b (incoming: 1) {
/// call Use(ldloc test)
/// leave IL_002b (nop)
/// }
///
/// } finally BlockContainer {
/// Block IL_0045 (incoming: 1) {
/// if (comp.o(ldloc test == ldnull)) leave IL_0045 (nop)
/// br IL_00ae
/// }
///
/// Block IL_00ae (incoming: 1) {
/// await(addressof System.Threading.Tasks.ValueTask(callvirt DisposeAsync(ldloc test)))
/// leave IL_0045 (nop)
/// }
///
/// }
/// </summary>
private bool TransformAsyncUsing(Block block, int i)
{
if (i < 1 || !context.Settings.AsyncUsingAndForEachStatement) return false;
if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst))
return false;
if (!CheckAsyncResourceType(storeInst.Variable.Type))
return false;
if (storeInst.Variable.Kind != VariableKind.Local)
return false;
if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally)))
return false;
if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la))))
return false;
if (storeInst.Variable.StoreInstructions.Count > 1)
return false;
if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, usingNull: false, "System.IAsyncDisposable.DisposeAsync", KnownTypeCode.IAsyncDisposable))
return false;
context.Step("AsyncUsingTransform", tryFinally);
storeInst.Variable.Kind = VariableKind.UsingLocal;
block.Instructions.RemoveAt(i);
block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) { IsAsync = true }
.WithILRange(storeInst);
return true;
}
bool CheckAsyncResourceType(IType type)
{
if (NullableType.GetUnderlyingType(type).GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IAsyncDisposable)))
return true;
return false;
}
bool UnwrapAwait(ref ILInstruction awaitInstruction)
{
if (awaitInstruction == null)
return false;
if (!awaitInstruction.MatchAwait(out var arg))
return false;
if (!arg.MatchAddressOf(out awaitInstruction, out var type))
return false;
if (!type.IsKnownType(KnownTypeCode.ValueTask))
return false;
return true;
}
} }
} }

30
ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs

@ -62,15 +62,18 @@ namespace ICSharpCode.Decompiler.Metadata
readonly Dictionary<string, DotNetCorePackageInfo> packages; readonly Dictionary<string, DotNetCorePackageInfo> packages;
ISet<string> packageBasePaths = new HashSet<string>(StringComparer.Ordinal); ISet<string> packageBasePaths = new HashSet<string>(StringComparer.Ordinal);
readonly string assemblyName;
readonly string basePath;
readonly Version version; readonly Version version;
readonly string dotnetBasePath = FindDotNetExeDirectory(); readonly string dotnetBasePath = FindDotNetExeDirectory();
public DotNetCorePathFinder(Version version)
{
this.version = version;
}
public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkId, Version version, ReferenceLoadInfo loadInfo = null) public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkId, Version version, ReferenceLoadInfo loadInfo = null)
{ {
this.assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName); string assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName);
this.basePath = Path.GetDirectoryName(parentAssemblyFileName); string basePath = Path.GetDirectoryName(parentAssemblyFileName);
this.version = version; this.version = version;
var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json"); var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json");
@ -106,6 +109,25 @@ namespace ICSharpCode.Decompiler.Metadata
return FallbackToDotNetSharedDirectory(name, version); return FallbackToDotNetSharedDirectory(name, version);
} }
internal string GetReferenceAssemblyPath(string targetFramework)
{
var (tfi, version) = UniversalAssemblyResolver.ParseTargetFramework(targetFramework);
string identifier, identifierExt;
switch (tfi) {
case TargetFrameworkIdentifier.NETCoreApp:
identifier = "Microsoft.NETCore.App";
identifierExt = "netcoreapp" + version.Major + "." + version.Minor;
break;
case TargetFrameworkIdentifier.NETStandard:
identifier = "NETStandard.Library";
identifierExt = "netstandard" + version.Major + "." + version.Minor;
break;
default:
throw new NotSupportedException();
}
return Path.Combine(dotnetBasePath, "packs", identifier + ".Ref", version.ToString(), "ref", identifierExt);
}
static IEnumerable<DotNetCorePackageInfo> LoadPackageInfos(string depsJsonFileName, string targetFramework) static IEnumerable<DotNetCorePackageInfo> LoadPackageInfos(string depsJsonFileName, string targetFramework)
{ {
var dependencies = JsonReader.Parse(File.ReadAllText(depsJsonFileName)); var dependencies = JsonReader.Parse(File.ReadAllText(depsJsonFileName));

34
ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs

@ -26,6 +26,21 @@ using System.Text;
namespace ICSharpCode.Decompiler.Metadata namespace ICSharpCode.Decompiler.Metadata
{ {
enum TargetFrameworkIdentifier
{
NETFramework,
NETCoreApp,
NETStandard,
Silverlight
}
enum DecompilerRuntime
{
NETFramework,
NETCoreApp,
Mono
}
// This is inspired by Mono.Cecil's BaseAssemblyResolver/DefaultAssemblyResolver. // This is inspired by Mono.Cecil's BaseAssemblyResolver/DefaultAssemblyResolver.
public class UniversalAssemblyResolver : IAssemblyResolver public class UniversalAssemblyResolver : IAssemblyResolver
{ {
@ -67,21 +82,6 @@ namespace ICSharpCode.Decompiler.Metadata
return directories.ToArray(); return directories.ToArray();
} }
enum TargetFrameworkIdentifier
{
NETFramework,
NETCoreApp,
NETStandard,
Silverlight
}
enum DecompilerRuntime
{
NETFramework,
NETCoreApp,
Mono
}
string targetFramework; string targetFramework;
TargetFrameworkIdentifier targetFrameworkIdentifier; TargetFrameworkIdentifier targetFrameworkIdentifier;
Version targetFrameworkVersion; Version targetFrameworkVersion;
@ -101,7 +101,7 @@ namespace ICSharpCode.Decompiler.Metadata
AddSearchDirectory(baseDirectory); AddSearchDirectory(baseDirectory);
} }
(TargetFrameworkIdentifier, Version) ParseTargetFramework(string targetFramework) internal static (TargetFrameworkIdentifier, Version) ParseTargetFramework(string targetFramework)
{ {
string[] tokens = targetFramework.Split(','); string[] tokens = targetFramework.Split(',');
TargetFrameworkIdentifier identifier; TargetFrameworkIdentifier identifier;
@ -131,7 +131,7 @@ namespace ICSharpCode.Decompiler.Metadata
switch (pair[0].Trim().ToUpperInvariant()) { switch (pair[0].Trim().ToUpperInvariant()) {
case "VERSION": case "VERSION":
var versionString = pair[1].TrimStart('v'); var versionString = pair[1].TrimStart('v', ' ', '\t');
if (identifier == TargetFrameworkIdentifier.NETCoreApp || if (identifier == TargetFrameworkIdentifier.NETCoreApp ||
identifier == TargetFrameworkIdentifier.NETStandard) identifier == TargetFrameworkIdentifier.NETStandard)
{ {

2
ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs

@ -370,7 +370,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
public override string ToString() public override string ToString()
{ {
return this.ReflectionName + " (owner=" + owner + ")"; return this.ReflectionName;
} }
} }
} }

2
ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs

@ -64,6 +64,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
IteratorStateMachine, IteratorStateMachine,
AsyncStateMachine, AsyncStateMachine,
AsyncMethodBuilder, AsyncMethodBuilder,
AsyncIteratorStateMachine,
// Field attributes: // Field attributes:
FieldOffset, FieldOffset,
@ -131,6 +132,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(IteratorStateMachineAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(IteratorStateMachineAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(AsyncStateMachineAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(AsyncStateMachineAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncMethodBuilderAttribute"), new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncMethodBuilderAttribute"),
new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute"),
// Field attributes: // Field attributes:
new TopLevelTypeName("System.Runtime.InteropServices", nameof(FieldOffsetAttribute)), new TopLevelTypeName("System.Runtime.InteropServices", nameof(FieldOffsetAttribute)),
new TopLevelTypeName("System", nameof(NonSerializedAttribute)), new TopLevelTypeName("System", nameof(NonSerializedAttribute)),

2
ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeParameter.cs

@ -230,7 +230,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
public override string ToString() public override string ToString()
{ {
return $"{MetadataTokens.GetToken(handle):X8} Index={Index} Owner={Owner}"; return $"{MetadataTokens.GetToken(handle):X8} {ReflectionName}";
} }
} }
} }

3
ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs

@ -14,7 +14,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
internal NullabilityAnnotatedType(IType type, Nullability nullability) internal NullabilityAnnotatedType(IType type, Nullability nullability)
: base(type) : base(type)
{ {
Debug.Assert(nullability != type.Nullability); Debug.Assert(type.Nullability == Nullability.Oblivious);
Debug.Assert(nullability != Nullability.Oblivious);
// Due to IType -> concrete type casts all over the type system, we can insert // Due to IType -> concrete type casts all over the type system, we can insert
// the NullabilityAnnotatedType wrapper only in some limited places. // the NullabilityAnnotatedType wrapper only in some limited places.
Debug.Assert(type is ITypeDefinition Debug.Assert(type is ITypeDefinition

23
ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs

@ -115,10 +115,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
Task, Task,
/// <summary><c>System.Threading.Tasks.Task{T}</c></summary> /// <summary><c>System.Threading.Tasks.Task{T}</c></summary>
TaskOfT, TaskOfT,
/// <summary><c>System.Threading.Tasks.ValueTask</c></summary>
ValueTask,
/// <summary><c>System.Threading.Tasks.ValueTask{T}</c></summary>
ValueTaskOfT,
/// <summary><c>System.Nullable{T}</c></summary> /// <summary><c>System.Nullable{T}</c></summary>
NullableOfT, NullableOfT,
/// <summary><c>System.IDisposable</c></summary> /// <summary><c>System.IDisposable</c></summary>
IDisposable, IDisposable,
/// <summary><c>System.IAsyncDisposable</c></summary>
IAsyncDisposable,
/// <summary><c>System.Runtime.CompilerServices.INotifyCompletion</c></summary> /// <summary><c>System.Runtime.CompilerServices.INotifyCompletion</c></summary>
INotifyCompletion, INotifyCompletion,
/// <summary><c>System.Runtime.CompilerServices.ICriticalNotifyCompletion</c></summary> /// <summary><c>System.Runtime.CompilerServices.ICriticalNotifyCompletion</c></summary>
@ -137,15 +143,19 @@ namespace ICSharpCode.Decompiler.TypeSystem
MemoryOfT, MemoryOfT,
/// <summary><c>System.Runtime.CompilerServices.Unsafe</c></summary> /// <summary><c>System.Runtime.CompilerServices.Unsafe</c></summary>
Unsafe, Unsafe,
/// <summary><c>System.Collections.Generic.IAsyncEnumerable{T}</c></summary>
IAsyncEnumerableOfT,
/// <summary><c>System.Collections.Generic.IAsyncEnumerator{T}</c></summary>
IAsyncEnumeratorOfT,
} }
/// <summary> /// <summary>
/// Contains well-known type references. /// Contains well-known type references.
/// </summary> /// </summary>
[Serializable] [Serializable]
public sealed class KnownTypeReference : ITypeReference public sealed class KnownTypeReference : ITypeReference
{ {
internal const int KnownTypeCodeCount = (int)KnownTypeCode.Unsafe + 1; internal const int KnownTypeCodeCount = (int)KnownTypeCode.IAsyncEnumeratorOfT + 1;
static readonly KnownTypeReference[] knownTypeReferences = new KnownTypeReference[KnownTypeCodeCount] { static readonly KnownTypeReference[] knownTypeReferences = new KnownTypeReference[KnownTypeCodeCount] {
null, // None null, // None
@ -189,10 +199,13 @@ namespace ICSharpCode.Decompiler.TypeSystem
new KnownTypeReference(KnownTypeCode.IReadOnlyCollectionOfT, TypeKind.Interface, "System.Collections.Generic", "IReadOnlyCollection", 1), new KnownTypeReference(KnownTypeCode.IReadOnlyCollectionOfT, TypeKind.Interface, "System.Collections.Generic", "IReadOnlyCollection", 1),
new KnownTypeReference(KnownTypeCode.IReadOnlyListOfT, TypeKind.Interface, "System.Collections.Generic", "IReadOnlyList", 1), new KnownTypeReference(KnownTypeCode.IReadOnlyListOfT, TypeKind.Interface, "System.Collections.Generic", "IReadOnlyList", 1),
new KnownTypeReference(KnownTypeCode.Task, TypeKind.Class, "System.Threading.Tasks", "Task"), new KnownTypeReference(KnownTypeCode.Task, TypeKind.Class, "System.Threading.Tasks", "Task"),
new KnownTypeReference(KnownTypeCode.TaskOfT, TypeKind.Class, "System.Threading.Tasks", "Task", 1, baseType: KnownTypeCode.Task), new KnownTypeReference(KnownTypeCode.TaskOfT, TypeKind.Class, "System.Threading.Tasks", "Task", 1, baseType: KnownTypeCode.Task),
new KnownTypeReference(KnownTypeCode.ValueTask, TypeKind.Struct, "System.Threading.Tasks", "ValueTask"),
new KnownTypeReference(KnownTypeCode.ValueTaskOfT, TypeKind.Struct, "System.Threading.Tasks", "ValueTask", 1),
new KnownTypeReference(KnownTypeCode.NullableOfT, TypeKind.Struct, "System", "Nullable", 1), new KnownTypeReference(KnownTypeCode.NullableOfT, TypeKind.Struct, "System", "Nullable", 1),
new KnownTypeReference(KnownTypeCode.IDisposable, TypeKind.Interface, "System", "IDisposable"), new KnownTypeReference(KnownTypeCode.IDisposable, TypeKind.Interface, "System", "IDisposable"),
new KnownTypeReference(KnownTypeCode.IAsyncDisposable, TypeKind.Interface, "System", "IAsyncDisposable"),
new KnownTypeReference(KnownTypeCode.INotifyCompletion, TypeKind.Interface, "System.Runtime.CompilerServices", "INotifyCompletion"), new KnownTypeReference(KnownTypeCode.INotifyCompletion, TypeKind.Interface, "System.Runtime.CompilerServices", "INotifyCompletion"),
new KnownTypeReference(KnownTypeCode.ICriticalNotifyCompletion, TypeKind.Interface, "System.Runtime.CompilerServices", "ICriticalNotifyCompletion"), new KnownTypeReference(KnownTypeCode.ICriticalNotifyCompletion, TypeKind.Interface, "System.Runtime.CompilerServices", "ICriticalNotifyCompletion"),
@ -203,6 +216,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
new KnownTypeReference(KnownTypeCode.ReadOnlySpanOfT, TypeKind.Struct, "System", "ReadOnlySpan", 1), new KnownTypeReference(KnownTypeCode.ReadOnlySpanOfT, TypeKind.Struct, "System", "ReadOnlySpan", 1),
new KnownTypeReference(KnownTypeCode.MemoryOfT, TypeKind.Struct, "System", "Memory", 1), new KnownTypeReference(KnownTypeCode.MemoryOfT, TypeKind.Struct, "System", "Memory", 1),
new KnownTypeReference(KnownTypeCode.Unsafe, TypeKind.Class, "System.Runtime.CompilerServices", "Unsafe", 0), new KnownTypeReference(KnownTypeCode.Unsafe, TypeKind.Class, "System.Runtime.CompilerServices", "Unsafe", 0),
new KnownTypeReference(KnownTypeCode.IAsyncEnumerableOfT, TypeKind.Interface, "System.Collections.Generic", "IAsyncEnumerable", 1),
new KnownTypeReference(KnownTypeCode.IAsyncEnumeratorOfT, TypeKind.Interface, "System.Collections.Generic", "IAsyncEnumerator", 1),
}; };
/// <summary> /// <summary>

3
ICSharpCode.Decompiler/TypeSystem/TupleType.cs

@ -373,7 +373,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
{ {
public static IType TupleUnderlyingTypeOrSelf(this IType type) public static IType TupleUnderlyingTypeOrSelf(this IType type)
{ {
return (type as TupleType)?.UnderlyingType ?? type; var t = (type as TupleType)?.UnderlyingType ?? type;
return t.WithoutNullability();
} }
} }
} }

17
ICSharpCode.Decompiler/TypeSystem/TypeParameterSubstitution.cs

@ -18,6 +18,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
namespace ICSharpCode.Decompiler.TypeSystem namespace ICSharpCode.Decompiler.TypeSystem
{ {
@ -186,7 +187,21 @@ namespace ICSharpCode.Decompiler.TypeSystem
return base.VisitTypeParameter(type); return base.VisitTypeParameter(type);
} }
} }
public override IType VisitNullabilityAnnotatedType(NullabilityAnnotatedType type)
{
if (type is NullabilityAnnotatedTypeParameter tp) {
if (tp.Nullability == Nullability.Nullable) {
return VisitTypeParameter(tp).ChangeNullability(Nullability.Nullable);
} else {
// T! substituted with T=oblivious string should result in oblivious string
return VisitTypeParameter(tp);
}
} else {
return base.VisitNullabilityAnnotatedType(type);
}
}
public override string ToString() public override string ToString()
{ {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();

5
ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs

@ -535,5 +535,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
{ {
return KnownAttributes.IsKnownAttributeType(type); return KnownAttributes.IsKnownAttributeType(type);
} }
public static IType WithoutNullability(this IType type)
{
return type.ChangeNullability(Nullability.Oblivious);
}
} }
} }

27
ILSpy/Properties/Resources.Designer.cs generated

@ -556,6 +556,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Decompile async IAsyncEnumerator methods.
/// </summary>
public static string DecompilerSettings_AsyncEnumerator {
get {
return ResourceManager.GetString("DecompilerSettings.AsyncEnumerator", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Decompile ?. and ?[] operators. /// Looks up a localized string similar to Decompile ?. and ?[] operators.
/// </summary> /// </summary>
@ -664,6 +673,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Detect awaited using and foreach statements.
/// </summary>
public static string DecompilerSettings_DetectAsyncUsingAndForeachStatements {
get {
return ResourceManager.GetString("DecompilerSettings.DetectAsyncUsingAndForeachStatements", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Detect foreach statements. /// Looks up a localized string similar to Detect foreach statements.
/// </summary> /// </summary>
@ -810,6 +828,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Read-only methods.
/// </summary>
public static string DecompilerSettings_ReadOnlyMethods {
get {
return ResourceManager.GetString("DecompilerSettings.ReadOnlyMethods", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Remove dead and side effect free code (use with caution!). /// Looks up a localized string similar to Remove dead and side effect free code (use with caution!).
/// </summary> /// </summary>

12
ILSpy/Properties/Resources.resx

@ -447,9 +447,6 @@
<data name="Version" xml:space="preserve"> <data name="Version" xml:space="preserve">
<value>Version</value> <value>Version</value>
</data> </data>
<data name="Culture" xml:space="preserve">
<value>Culture</value>
</data>
<data name="PublicToken" xml:space="preserve"> <data name="PublicToken" xml:space="preserve">
<value>Public Key Token</value> <value>Public Key Token</value>
</data> </data>
@ -769,4 +766,13 @@ Are you sure you want to continue?</value>
<data name="DecompileToNewPanel" xml:space="preserve"> <data name="DecompileToNewPanel" xml:space="preserve">
<value>Decompile to new tab</value> <value>Decompile to new tab</value>
</data> </data>
<data name="DecompilerSettings.AsyncEnumerator" xml:space="preserve">
<value>Decompile async IAsyncEnumerator methods</value>
</data>
<data name="DecompilerSettings.ReadOnlyMethods" xml:space="preserve">
<value>Read-only methods</value>
</data>
<data name="DecompilerSettings.DetectAsyncUsingAndForeachStatements" xml:space="preserve">
<value>Detect awaited using and foreach statements</value>
</data>
</root> </root>
Loading…
Cancel
Save