Browse Source

Merge pull request #1730 from icsharpcode/async-streams

C# 8.0 Async streams
pull/1790/head
Daniel Grunwald 6 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() { @@ -53,7 +53,7 @@ function gitCommitHash() {
}
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";
}

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

@ -13,7 +13,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -13,7 +13,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
var section = (AttributeSection)attribute.Parent;
SimpleType type = attribute.Type as SimpleType;
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();
if (section.Attributes.Count == 0)

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

@ -55,6 +55,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -55,6 +55,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
UseRoslyn = 0x10,
UseMcs = 0x20,
ReferenceVisualBasic = 0x40,
ReferenceCore = 0x80,
}
[Flags]
@ -89,12 +90,12 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -89,12 +90,12 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
outputFile += ".exe";
otherOptions += "/exe ";
}
if (options.HasFlag(AssemblerOptions.UseDebug)) {
otherOptions += "/debug ";
}
ProcessStartInfo info = new ProcessStartInfo(ilasmPath);
info.Arguments = $"/nologo {otherOptions}/output=\"{outputFile}\" \"{sourceFileName}\"";
info.RedirectStandardError = true;
@ -115,7 +116,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -115,7 +116,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return outputFile;
}
public static string Disassemble(string sourceFileName, string outputFile, AssemblerOptions asmOptions)
{
if (asmOptions.HasFlag(AssemblerOptions.UseOwnDisassembler)) {
@ -141,7 +142,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -141,7 +142,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
}
string ildasmPath = SdkUtility.GetSdkPath("ildasm.exe");
ProcessStartInfo info = new ProcessStartInfo(ildasmPath);
info.Arguments = $"/nobar /utf8 /out=\"{outputFile}\" \"{sourceFileName}\"";
info.RedirectStandardError = true;
@ -182,8 +183,10 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -182,8 +183,10 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
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),
@"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 Lazy<IEnumerable<MetadataReference>> defaultReferences = new Lazy<IEnumerable<MetadataReference>>(delegate {
@ -202,6 +205,21 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -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 {
return new[] {
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "Microsoft.VisualBasic.dll"))
@ -217,6 +235,9 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -217,6 +235,9 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
if (flags.HasFlag(CompilerOptions.Optimize)) {
preprocessorSymbols.Add("OPT");
}
if (flags.HasFlag(CompilerOptions.ReferenceCore)) {
preprocessorSymbols.Add("NETCORE");
}
if (flags.HasFlag(CompilerOptions.UseRoslyn)) {
preprocessorSymbols.Add("ROSLYN");
preprocessorSymbols.Add("CS60");
@ -252,7 +273,15 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -252,7 +273,15 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
languageVersion: Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8
);
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)) {
references = references.Concat(visualBasic.Value);
}
@ -466,16 +495,16 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -466,16 +495,16 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return fileName;
}
}
public static void RunAndCompareOutput(string testFileName, string outputFile, string decompiledOutputFile, string decompiledCodeFile = null)
{
string output1, output2, error1, error2;
int result1 = Tester.Run(outputFile, out output1, out error1);
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, result2, "Exit code != 0; did the decompiled code crash?" + Environment.NewLine + error2);
if (output1 != output2 || error1 != error2) {
StringBuilder b = new StringBuilder();
b.AppendLine($"Test {testFileName} failed: output does not match.");

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

@ -81,6 +81,8 @@ @@ -81,6 +81,8 @@
<Compile Include="DisassemblerPrettyTestRunner.cs" />
<Compile Include="TestCases\Correctness\StringConcat.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\CustomTaskType.cs" />
<None Include="TestCases\Ugly\NoForEachStatement.Expected.cs" />

20
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

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

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

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{
@ -27,6 +28,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -27,6 +28,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
InterestingConstants();
TruncatedComp();
StringConcat();
LinqNullableMin();
LinqNullableMin(1, 2, 3);
}
static void Print<T>(T val)
@ -101,5 +104,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -101,5 +104,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
Print(string.Concat(1, 2));
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; @@ -14,14 +14,12 @@ using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyTitle("ConsoleApplication1")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConsoleApplication1")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.1", FrameworkDisplayName = ".NET Framework 4.6.1")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCopyright("Copyright © 2017")]

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

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

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

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

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

@ -0,0 +1,37 @@ @@ -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 @@ @@ -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 @@ @@ -16,9 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
// We can't test this because "error CS8701: Target runtime doesn't support default interface implementation."
#undef CS80
using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty

8
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

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

9
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -989,6 +989,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -989,6 +989,10 @@ namespace ICSharpCode.Decompiler.CSharp
foundMember = null;
bestCandidateIsExpandedForm = false;
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,
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(),
@ -1043,6 +1047,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1043,6 +1047,9 @@ namespace ICSharpCode.Decompiler.CSharp
bool IsUnambiguousAccess(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method,
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;
if (target == null) {
var result = resolver.ResolveSimpleName(method.AccessorOwner.Name,
@ -1404,6 +1411,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1404,6 +1411,8 @@ namespace ICSharpCode.Decompiler.CSharp
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 or = new OverloadResolution(resolver.Compilation,
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 @@ -1112,6 +1112,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
var opSymbol = UnaryOperatorExpression.GetOperatorRole(opType);
if (opType == UnaryOperatorType.Await) {
WriteKeyword(opSymbol);
Space();
} else if (!IsPostfixOperator(opType) && opSymbol != null) {
WriteToken(opSymbol);
}
@ -1863,6 +1864,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1863,6 +1864,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitUsingStatement(UsingStatement usingStatement)
{
StartNode(usingStatement);
if (usingStatement.IsAsync) {
WriteKeyword(UsingStatement.AwaitRole);
}
WriteKeyword(UsingStatement.UsingKeywordRole);
Space(policy.SpaceBeforeUsingParentheses);
LPar();

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

@ -152,7 +152,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -152,7 +152,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
if (this.Parameters.Count != parameterTypes.Length)
return Conversion.None;
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) {
// it's possible that different parameter types also lead to a valid conversion
return LambdaConversion.Instance;
@ -162,7 +162,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -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;
} else {
return Conversion.None;

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

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

22
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -412,14 +412,29 @@ namespace ICSharpCode.Decompiler.CSharp @@ -412,14 +412,29 @@ namespace ICSharpCode.Decompiler.CSharp
return transformed;
AstNode usingInit = resource;
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);
var.Kind = VariableKind.Local;
var disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IDisposable);
var disposeVariable = currentFunction.RegisterVariable(
VariableKind.Local, 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 {
new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(var).Expression, resource.Detach())),
new TryCatchStatement {
@ -428,7 +443,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 IfElseStatement {
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 @@ -442,6 +457,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
return new UsingStatement {
ResourceAcquisition = usingInit,
IsAsync = inst.IsAsync,
EmbeddedStatement = ConvertAsBlock(inst.Body)
};
}

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

@ -28,57 +28,67 @@ @@ -28,57 +28,67 @@
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
/// <summary>
/// using (ResourceAcquisition) EmbeddedStatement
/// [ await ] using (ResourceAcquisition) EmbeddedStatement
/// </summary>
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 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 {
get { return GetChildByRole (Roles.LPar); }
get { return GetChildByRole(Roles.LPar); }
}
/// <summary>
/// Either a VariableDeclarationStatement, or an Expression.
/// </summary>
public AstNode ResourceAcquisition {
get { return GetChildByRole (ResourceAcquisitionRole); }
set { SetChildByRole (ResourceAcquisitionRole, value); }
get { return GetChildByRole(ResourceAcquisitionRole); }
set { SetChildByRole(ResourceAcquisitionRole, value); }
}
public CSharpTokenNode RParToken {
get { return GetChildByRole (Roles.RPar); }
get { return GetChildByRole(Roles.RPar); }
}
public Statement EmbeddedStatement {
get { return GetChildByRole (Roles.EmbeddedStatement); }
set { SetChildByRole (Roles.EmbeddedStatement, value); }
get { return GetChildByRole(Roles.EmbeddedStatement); }
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)
{
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 @@ -487,7 +487,7 @@ namespace ICSharpCode.Decompiler.CSharp
return newTargetType.IsKnownType(KnownTypeCode.FormattableString)
|| newTargetType.IsKnownType(KnownTypeCode.IFormattable);
}
return oldTargetType.Equals(newTargetType);
return conversions.IdentityConversion(oldTargetType, newTargetType);
}
TranslatedExpression LdcI4(ICompilation compilation, int val)

38
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -108,12 +108,14 @@ namespace ICSharpCode.Decompiler @@ -108,12 +108,14 @@ namespace ICSharpCode.Decompiler
if (languageVersion < CSharp.LanguageVersion.CSharp8_0) {
nullableReferenceTypes = false;
readOnlyMethods = false;
asyncUsingAndForEachStatement = false;
asyncEnumerator = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (nullableReferenceTypes || readOnlyMethods)
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement)
return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers)
return CSharp.LanguageVersion.CSharp7_3;
@ -273,6 +275,24 @@ namespace ICSharpCode.Decompiler @@ -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;
/// <summary>
@ -846,7 +866,7 @@ namespace ICSharpCode.Decompiler @@ -846,7 +866,7 @@ namespace ICSharpCode.Decompiler
bool readOnlyMethods = true;
[Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.IsReadOnlyAttributeShouldBeReplacedWithReadonlyInModifiersOnStructsParameters")]
[Description("DecompilerSettings.ReadOnlyMethods")]
public bool ReadOnlyMethods {
get { return readOnlyMethods; }
set {
@ -857,6 +877,20 @@ namespace ICSharpCode.Decompiler @@ -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;
/// <summary>

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

@ -62,14 +62,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -62,14 +62,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
Void,
Task,
TaskOfT
TaskOfT,
AsyncEnumerator,
AsyncEnumerable
}
ILTransformContext context;
// These fields are set by MatchTaskCreationPattern()
IType taskType; // return type of the async method
IType underlyingReturnType; // return type of the method (only the "T" for Task{T})
// These fields are set by MatchTaskCreationPattern() or MatchEnumeratorCreationNewObj()
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}), for async enumerators this is the type being yielded
AsyncMethodType methodType;
ITypeDefinition stateMachineType;
ITypeDefinition builderType;
@ -78,15 +80,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -78,15 +80,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
int initialState;
Dictionary<IField, ILVariable> fieldToParameterMap = new Dictionary<IField, ILVariable>();
Dictionary<ILVariable, ILVariable> cachedFieldToParameterMap = new Dictionary<ILVariable, ILVariable>();
IField disposeModeField; // 'disposeMode' field (IAsyncEnumerable/IAsyncEnumerator only)
// These fields are set by AnalyzeMoveNext():
ILFunction moveNextFunction;
ILVariable cachedStateVar; // variable in MoveNext that caches the stateField.
TryCatch mainTryCatch;
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
bool finalStateKnown;
ILVariable resultVar; // the variable that gets returned by the setResultAndExitBlock
Block setResultYieldBlock; // block that is jumped to for 'yield return' statements
ILVariable doFinallyBodies;
// These fields are set by AnalyzeStateMachine():
@ -110,11 +115,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -110,11 +115,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
awaitBlocks.Clear();
awaitDebugInfos.Clear();
moveNextLeaves.Clear();
if (!MatchTaskCreationPattern(function))
if (!MatchTaskCreationPattern(function) && !MatchAsyncEnumeratorCreationPattern(function))
return;
try {
AnalyzeMoveNext();
ValidateCatchBlock();
AnalyzeDisposeAsync();
} catch (SymbolicAnalysisFailedException) {
return;
}
@ -133,7 +139,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -133,7 +139,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
TranslateCachedFieldsToLocals();
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,
// and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities.
@ -164,9 +174,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -164,9 +174,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
CopyPropagation.Propagate(stloc, 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>()) {
// Run inlining, but don't remove dead variables (they might get revived by TranslateFieldsToLocalAccess)
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();
}
@ -337,6 +352,160 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -337,6 +352,160 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
#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
/// <summary>
/// First peek into MoveNext(); analyzes everything outside the big try-catch.
@ -353,8 +522,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -353,8 +522,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
moveNextFunction = YieldReturnDecompiler.CreateILAst(moveNextMethod, context);
if (!(moveNextFunction.Body is BlockContainer blockContainer))
throw new SymbolicAnalysisFailedException();
if (blockContainer.Blocks.Count != 2 && blockContainer.Blocks.Count != 1)
throw new SymbolicAnalysisFailedException();
if (blockContainer.EntryPoint.IncomingEdgeCount != 1)
throw new SymbolicAnalysisFailedException();
cachedStateVar = null;
@ -385,12 +552,51 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -385,12 +552,51 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (((BlockContainer)mainTryCatch.TryBlock).EntryPoint.Instructions[0] is StLoc initDoFinallyBodies
&& initDoFinallyBodies.Variable.Kind == VariableKind.Local
&& initDoFinallyBodies.Variable.Type.IsKnownType(KnownTypeCode.Boolean)
&& initDoFinallyBodies.Value.MatchLdcI4(1))
{
&& initDoFinallyBodies.Value.MatchLdcI4(1)) {
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) {
// This block can be absent if the function never exits normally,
// but always throws an exception/loops infinitely.
@ -408,17 +614,29 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -408,17 +614,29 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
finalStateKnown = true;
if (!MatchCall(setResultAndExitBlock.Instructions[1], "SetResult", out var args))
throw new SymbolicAnalysisFailedException();
if (!IsBuilderFieldOnThis(args[0]))
if (!IsBuilderOrPromiseFieldOnThis(args[0]))
throw new SymbolicAnalysisFailedException();
if (methodType == AsyncMethodType.TaskOfT) {
if (args.Count != 2)
throw new SymbolicAnalysisFailedException();
if (!args[1].MatchLdLoc(out resultVar))
throw new SymbolicAnalysisFailedException();
} else {
resultVar = null;
if (args.Count != 1)
throw new SymbolicAnalysisFailedException();
switch (methodType) {
case AsyncMethodType.TaskOfT:
if (args.Count != 2)
throw new SymbolicAnalysisFailedException();
if (!args[1].MatchLdLoc(out resultVar))
throw new SymbolicAnalysisFailedException();
break;
case AsyncMethodType.Task:
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))
throw new SymbolicAnalysisFailedException();
@ -442,8 +660,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -442,8 +660,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (!handler.Filter.MatchLdcI4(1))
throw new SymbolicAnalysisFailedException();
var catchBlock = YieldReturnDecompiler.SingleBlock(handler.Body);
if (catchBlock == null)
throw new SymbolicAnalysisFailedException();
catchHandlerOffset = catchBlock.StartILOffset;
if (catchBlock?.Instructions.Count != 4)
if (catchBlock.Instructions.Count != 4)
throw new SymbolicAnalysisFailedException();
// stloc exception(ldloc E_143)
if (!(catchBlock.Instructions[0] is StLoc stloc))
@ -465,7 +685,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -465,7 +685,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
throw new SymbolicAnalysisFailedException();
if (args.Count != 2)
throw new SymbolicAnalysisFailedException();
if (!IsBuilderFieldOnThis(args[0]))
if (!IsBuilderOrPromiseFieldOnThis(args[0]))
throw new SymbolicAnalysisFailedException();
if (!args[1].MatchLdLoc(stloc.Variable))
throw new SymbolicAnalysisFailedException();
@ -490,19 +710,55 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -490,19 +710,55 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
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)
{
// stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(stateId))
if (inst.MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis()
&& StackSlotValue(target).MatchLdThis()
&& field.MemberDefinition == stateField
&& value.MatchLdcI4(out newState))
&& StackSlotValue(value).MatchLdcI4(out newState))
{
return true;
}
newState = 0;
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
#region InlineBodyOfMoveNext
@ -513,6 +769,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -513,6 +769,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
function.AsyncReturnType = underlyingReturnType;
function.MoveNextMethod = moveNextFunction.Method;
function.CodeSize = moveNextFunction.CodeSize;
function.IsIterator = IsAsyncEnumerator;
moveNextFunction.Variables.Clear();
moveNextFunction.ReleaseRef();
foreach (var branch in function.Descendants.OfType<Branch>()) {
@ -520,6 +777,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -520,6 +777,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
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>()) {
if (leave.TargetContainer == moveNextFunction.Body) {
leave.TargetContainer = (BlockContainer)function.Body;
@ -563,7 +824,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -563,7 +824,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary>
void AnalyzeStateMachine(ILFunction function)
{
context.Step("AnalyzeStateMachine()", function);
context.StepStartGroup("AnalyzeStateMachine()", function);
smallestAwaiterVarIndex = int.MaxValue;
foreach (var container in function.Descendants.OfType<BlockContainer>()) {
// Use a separate state range analysis per container.
@ -577,6 +838,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -577,6 +838,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.CancellationToken.ThrowIfCancellationRequested();
if (block.Instructions.Last() is Leave leave && moveNextLeaves.Contains(leave)) {
// 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)) {
block.Instructions.Add(new Await(new LdLoca(awaiterVar)));
Block targetBlock = stateToBlockMap.GetOrDefault(state);
@ -591,7 +853,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -591,7 +853,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
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
var entryPoint = stateToBlockMap.GetOrDefault(initialState);
@ -604,6 +884,89 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -604,6 +884,89 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
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)
@ -705,6 +1068,50 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -705,6 +1068,50 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
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
#region DetectAwaitPattern
@ -761,7 +1168,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -761,7 +1168,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// continue matching call get_IsCompleted(ldloca awaiterVar)
if (!MatchCall(condition, "get_IsCompleted", out var isCompletedArgs) || isCompletedArgs.Count != 1)
return;
if (!isCompletedArgs[0].MatchLdLocRef(awaiterVar))
if (!UnwrapConvUnknown(isCompletedArgs[0]).MatchLdLocRef(awaiterVar))
return;
// Check awaitBlock and resumeBlock:
if (!awaitBlocks.TryGetValue(awaitBlock, out var awaitBlockData))
@ -779,14 +1186,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -779,14 +1186,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return;
if (!MatchCall(getResultCall, "GetResult", out var getResultArgs) || getResultArgs.Count != 1)
return;
if (!getResultArgs[0].MatchLdLocRef(awaiterVar))
if (!UnwrapConvUnknown(getResultArgs[0]).MatchLdLocRef(awaiterVar))
return;
// All checks successful, let's transform.
context.Step("Transform await pattern", block);
block.Instructions.RemoveAt(block.Instructions.Count - 3); // remove getAwaiter call
block.Instructions.RemoveAt(block.Instructions.Count - 2); // remove if (isCompleted)
((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.GetAwaiterMethod = getAwaiterCall.Method;
getResultCall.ReplaceWith(awaitInst);
@ -798,7 +1205,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -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)
{
// awaitBlock:

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

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

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

@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.CancellationToken.ThrowIfCancellationRequested();
RemoveNopInstructions(block);
RemoveDeadStackStores(block, aggressive: context.Settings.RemoveDeadStores);
RemoveDeadStackStores(block, context);
InlineVariableInReturnBlock(block, context);
// 1st pass SimplifySwitchInstruction before SimplifyBranchChains()
@ -70,13 +70,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -70,13 +70,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
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;
// ideally the ILReader would already do this,
// for now do this here (even though it's not control-flow related).
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) {
context.Step($"Remove dead stack store {stloc.Variable.Name}", stloc);
if (aggressive ? SemanticHelper.IsPure(stloc.Value.Flags) : IsSimple(stloc.Value)) {
Debug.Assert(SemanticHelper.IsPure(stloc.Value.Flags));
block.Instructions.RemoveAt(i++);

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

@ -305,6 +305,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -305,6 +305,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary>
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))
if (!(inst is NewObj newObj))
return false;
@ -443,6 +453,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -443,6 +453,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
MethodDefinitionHandle getEnumeratorMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(
m => metadata.GetString(metadata.GetMethodDefinition(m).Name).StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal)
&& metadata.GetString(metadata.GetMethodDefinition(m).Name).EndsWith(".GetEnumerator", StringComparison.Ordinal));
ResolveIEnumerableIEnumeratorFieldMapping(getEnumeratorMethod, context, fieldToParameterMap);
}
internal static void ResolveIEnumerableIEnumeratorFieldMapping(MethodDefinitionHandle getEnumeratorMethod, ILTransformContext context,
Dictionary<IField, ILVariable> fieldToParameterMap)
{
if (getEnumeratorMethod.IsNil)
return; // no mappings (maybe it's just an IEnumerator implementation?)
var function = CreateILAst(getEnumeratorMethod, context);

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

@ -311,6 +311,27 @@ namespace ICSharpCode.Decompiler.IL @@ -311,6 +311,27 @@ namespace ICSharpCode.Decompiler.IL
return false;
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

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

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

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

@ -34,7 +34,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -34,7 +34,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!context.Settings.UsingStatement) return;
this.context = context;
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;
// This happens in some cases:
// Use correct index after transformation.
@ -167,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -167,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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;
if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1)
@ -180,17 +180,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -180,17 +180,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (castIndex > -1) {
if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst))
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;
if (!tempVar.IsSingleDefinition)
return false;
isReference = true;
if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck))
if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode))
return false;
if (tempVar.LoadCount != numObjVarLoadsInCheck)
return false;
} else {
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _))
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _, disposeMethodFullName, disposeTypeCode))
return false;
}
if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop())
@ -198,9 +198,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -198,9 +198,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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;
ILInstruction disposeInvocation;
CallVirt callVirt;
if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT)) {
if (checkInst.MatchIfInstruction(out var condition, out var disposeInst)) {
@ -208,22 +209,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -208,22 +209,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
return false;
callVirt = disposeBlock.Instructions[0] as CallVirt;
disposeInvocation = disposeBlock.Instructions[0];
} else if (checkInst.MatchNullableRewrap(out disposeInst)) {
callVirt = disposeInst as CallVirt;
disposeInvocation = disposeInst;
} else {
return false;
}
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref disposeInvocation))
return false;
}
callVirt = disposeInvocation as CallVirt;
if (callVirt == null)
return false;
if (callVirt.Method.FullName != "System.IDisposable.Dispose")
if (callVirt.Method.FullName != disposeMethodFullName)
return false;
if (callVirt.Method.Parameters.Count > 0)
return false;
if (callVirt.Arguments.Count != 1)
return false;
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 _))
return false;
return NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar)
@ -255,7 +261,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -255,7 +261,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
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;
target = cv.Arguments.FirstOrDefault();
if (target == null)
@ -264,6 +275,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -264,6 +275,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = newTarget;
callVirt = cv;
} else {
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref checkInst))
return false;
}
if (!(checkInst is CallVirt cv))
return false;
target = cv.Arguments.FirstOrDefault();
@ -275,7 +290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -275,7 +290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
callVirt = cv;
}
if (callVirt.Method.FullName != "System.IDisposable.Dispose")
if (callVirt.Method.FullName != disposeMethodFullName)
return false;
if (callVirt.Method.Parameters.Count > 0)
return false;
@ -286,8 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -286,8 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|| (usingNull && callVirt.Arguments[0].MatchLdNull())
|| (isReference && checkInst is NullableRewrap
&& 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 @@ -62,15 +62,18 @@ namespace ICSharpCode.Decompiler.Metadata
readonly Dictionary<string, DotNetCorePackageInfo> packages;
ISet<string> packageBasePaths = new HashSet<string>(StringComparer.Ordinal);
readonly string assemblyName;
readonly string basePath;
readonly Version version;
readonly string dotnetBasePath = FindDotNetExeDirectory();
public DotNetCorePathFinder(Version version)
{
this.version = version;
}
public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkId, Version version, ReferenceLoadInfo loadInfo = null)
{
this.assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName);
this.basePath = Path.GetDirectoryName(parentAssemblyFileName);
string assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName);
string basePath = Path.GetDirectoryName(parentAssemblyFileName);
this.version = version;
var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json");
@ -106,6 +109,25 @@ namespace ICSharpCode.Decompiler.Metadata @@ -106,6 +109,25 @@ namespace ICSharpCode.Decompiler.Metadata
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)
{
var dependencies = JsonReader.Parse(File.ReadAllText(depsJsonFileName));

34
ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs

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

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

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

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

@ -230,7 +230,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -230,7 +230,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
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 @@ -14,7 +14,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
internal NullabilityAnnotatedType(IType type, Nullability nullability)
: 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
// the NullabilityAnnotatedType wrapper only in some limited places.
Debug.Assert(type is ITypeDefinition

23
ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs

@ -115,10 +115,16 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -115,10 +115,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
Task,
/// <summary><c>System.Threading.Tasks.Task{T}</c></summary>
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>
NullableOfT,
/// <summary><c>System.IDisposable</c></summary>
IDisposable,
/// <summary><c>System.IAsyncDisposable</c></summary>
IAsyncDisposable,
/// <summary><c>System.Runtime.CompilerServices.INotifyCompletion</c></summary>
INotifyCompletion,
/// <summary><c>System.Runtime.CompilerServices.ICriticalNotifyCompletion</c></summary>
@ -137,15 +143,19 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -137,15 +143,19 @@ namespace ICSharpCode.Decompiler.TypeSystem
MemoryOfT,
/// <summary><c>System.Runtime.CompilerServices.Unsafe</c></summary>
Unsafe,
/// <summary><c>System.Collections.Generic.IAsyncEnumerable{T}</c></summary>
IAsyncEnumerableOfT,
/// <summary><c>System.Collections.Generic.IAsyncEnumerator{T}</c></summary>
IAsyncEnumeratorOfT,
}
/// <summary>
/// Contains well-known type references.
/// </summary>
[Serializable]
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] {
null, // None
@ -189,10 +199,13 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -189,10 +199,13 @@ namespace ICSharpCode.Decompiler.TypeSystem
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.Task, TypeKind.Class, "System.Threading.Tasks", "Task"),
new KnownTypeReference(KnownTypeCode.TaskOfT, TypeKind.Class, "System.Threading.Tasks", "Task", 1, baseType: KnownTypeCode.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.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.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.ICriticalNotifyCompletion, TypeKind.Interface, "System.Runtime.CompilerServices", "ICriticalNotifyCompletion"),
@ -203,6 +216,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -203,6 +216,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
new KnownTypeReference(KnownTypeCode.ReadOnlySpanOfT, TypeKind.Struct, "System", "ReadOnlySpan", 1),
new KnownTypeReference(KnownTypeCode.MemoryOfT, TypeKind.Struct, "System", "Memory", 1),
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>

3
ICSharpCode.Decompiler/TypeSystem/TupleType.cs

@ -373,7 +373,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -373,7 +373,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
{
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 @@ @@ -18,6 +18,7 @@
using System.Collections.Generic;
using System.Text;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
namespace ICSharpCode.Decompiler.TypeSystem
{
@ -186,7 +187,21 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -186,7 +187,21 @@ namespace ICSharpCode.Decompiler.TypeSystem
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()
{
StringBuilder b = new StringBuilder();

5
ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs

@ -535,5 +535,10 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -535,5 +535,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
{
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 { @@ -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>
/// Looks up a localized string similar to Decompile ?. and ?[] operators.
/// </summary>
@ -664,6 +673,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -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>
/// Looks up a localized string similar to Detect foreach statements.
/// </summary>
@ -810,6 +828,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -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>
/// Looks up a localized string similar to Remove dead and side effect free code (use with caution!).
/// </summary>

12
ILSpy/Properties/Resources.resx

@ -447,9 +447,6 @@ @@ -447,9 +447,6 @@
<data name="Version" xml:space="preserve">
<value>Version</value>
</data>
<data name="Culture" xml:space="preserve">
<value>Culture</value>
</data>
<data name="PublicToken" xml:space="preserve">
<value>Public Key Token</value>
</data>
@ -769,4 +766,13 @@ Are you sure you want to continue?</value> @@ -769,4 +766,13 @@ Are you sure you want to continue?</value>
<data name="DecompileToNewPanel" xml:space="preserve">
<value>Decompile to new tab</value>
</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>
Loading…
Cancel
Save