diff --git a/ICSharpCode.Decompiler/DebugInfo/AsyncDebugInfo.cs b/ICSharpCode.Decompiler/DebugInfo/AsyncDebugInfo.cs new file mode 100644 index 000000000..81dceabce --- /dev/null +++ b/ICSharpCode.Decompiler/DebugInfo/AsyncDebugInfo.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Immutable; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace ICSharpCode.Decompiler.DebugInfo +{ + readonly struct AsyncDebugInfo + { + public readonly int CatchHandlerOffset; + public readonly ImmutableArray Awaits; + + public AsyncDebugInfo(int catchHandlerOffset, ImmutableArray awaits) + { + this.CatchHandlerOffset = catchHandlerOffset; + this.Awaits = awaits; + } + + public readonly struct Await + { + public readonly int YieldOffset; + public readonly int ResumeOffset; + + public Await(int yieldOffset, int resumeOffset) + { + this.YieldOffset = yieldOffset; + this.ResumeOffset = resumeOffset; + } + } + + internal BlobBuilder BuildBlob(MethodDefinitionHandle moveNext) + { + BlobBuilder blob = new BlobBuilder(); + blob.WriteUInt32((uint)CatchHandlerOffset); + foreach (var await in Awaits) { + blob.WriteUInt32((uint)await.YieldOffset); + blob.WriteUInt32((uint)await.ResumeOffset); + blob.WriteCompressedInteger(MetadataTokens.GetToken(moveNext)); + } + return blob; + } + } +} diff --git a/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs b/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs new file mode 100644 index 000000000..a5f7864bb --- /dev/null +++ b/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs @@ -0,0 +1,192 @@ +// Copyright (c) 2018 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Text; +using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.Decompiler.DebugInfo +{ + /// + /// Visitor that generates debug information. + /// + /// The intended usage is to create a new instance for each source file, + /// and call syntaxTree.AcceptVisitor(debugInfoGenerator) to fill the internal debug info tables. + /// This can happen concurrently for multiple source files. + /// Then the main thread calls Generate() to write out the results into the PDB. + /// + class DebugInfoGenerator : DepthFirstAstVisitor + { + static readonly KeyComparer ILVariableKeyComparer = new KeyComparer(l => l.Index.Value, Comparer.Default, EqualityComparer.Default); + + IDecompilerTypeSystem typeSystem; + readonly ImportScopeInfo globalImportScope = new ImportScopeInfo(); + ImportScopeInfo currentImportScope; + List importScopes = new List(); + List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet Locals)> localScopes = new List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet Locals)>(); + List functions = new List(); + + /// + /// Gets all functions with bodies that were seen by the visitor so far. + /// + public IReadOnlyList Functions { + get => functions; + } + + public DebugInfoGenerator(IDecompilerTypeSystem typeSystem) + { + this.typeSystem = typeSystem ?? throw new ArgumentNullException("typeSystem"); + this.currentImportScope = globalImportScope; + } + + public void Generate(MetadataBuilder metadata, ImportScopeHandle globalImportScope) + { + foreach (var scope in importScopes) { + var blob = EncodeImports(metadata, scope); + scope.Handle = metadata.AddImportScope(scope.Parent == null ? globalImportScope : scope.Parent.Handle, blob); + } + + foreach (var localScope in localScopes) { + int nextRow = metadata.GetRowCount(TableIndex.LocalVariable) + 1; + var firstLocalVariable = MetadataTokens.LocalVariableHandle(nextRow); + + foreach (var local in localScope.Locals.OrderBy(l => l.Index)) { + var name = local.Name != null ? metadata.GetOrAddString(local.Name) : default; + metadata.AddLocalVariable(LocalVariableAttributes.None, local.Index.Value, name); + } + + metadata.AddLocalScope(localScope.Method, localScope.Import.Handle, firstLocalVariable, + default, localScope.Offset, localScope.Length); + } + } + + static BlobHandle EncodeImports(MetadataBuilder metadata, ImportScopeInfo scope) + { + var writer = new BlobBuilder(); + + foreach (var import in scope.Imports) { + writer.WriteByte((byte)ImportDefinitionKind.ImportNamespace); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(metadata.GetOrAddBlobUTF8(import))); + } + + return metadata.GetOrAddBlob(writer); + } + + public override void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration) + { + var parentImportScope = currentImportScope; + currentImportScope = new ImportScopeInfo(parentImportScope); + importScopes.Add(currentImportScope); + base.VisitNamespaceDeclaration(namespaceDeclaration); + currentImportScope = parentImportScope; + } + + public override void VisitUsingDeclaration(UsingDeclaration usingDeclaration) + { + currentImportScope.Imports.Add(usingDeclaration.Namespace); + } + + public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration) + { + HandleMethod(methodDeclaration); + } + + public override void VisitAccessor(Accessor accessor) + { + HandleMethod(accessor); + } + + public override void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration) + { + HandleMethod(constructorDeclaration); + } + + public override void VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration) + { + HandleMethod(destructorDeclaration); + } + + public override void VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration) + { + HandleMethod(operatorDeclaration); + } + + public override void VisitLambdaExpression(LambdaExpression lambdaExpression) + { + HandleMethod(lambdaExpression); + } + + public override void VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression) + { + HandleMethod(anonymousMethodExpression); + } + + void HandleMethod(AstNode node) + { + // Look into method body, e.g. in order to find lambdas + VisitChildren(node); + + var function = node.Annotation(); + if (function == null || function.Method == null || function.Method.MetadataToken.IsNil) + return; + this.functions.Add(function); + var method = function.MoveNextMethod ?? function.Method; + MethodDefinitionHandle handle = (MethodDefinitionHandle)method.MetadataToken; + var file = typeSystem.MainModule.PEFile; + MethodDefinition md = file.Metadata.GetMethodDefinition(handle); + if (md.HasBody()) { + HandleMethodBody(function, file.Reader.GetMethodBody(md.RelativeVirtualAddress)); + } + } + + void HandleMethodBody(ILFunction function, MethodBodyBlock methodBody) + { + var method = function.MoveNextMethod ?? function.Method; + var localVariables = new HashSet(ILVariableKeyComparer); + + if (!methodBody.LocalSignature.IsNil) { +#if DEBUG + var types = typeSystem.MainModule.DecodeLocalSignature(methodBody.LocalSignature, + new TypeSystem.GenericContext(method)); +#endif + + foreach (var v in function.Variables) { + if (v.Index != null && v.Kind.IsLocal()) { +#if DEBUG + Debug.Assert(v.Index < types.Length && v.Type.Equals(types[v.Index.Value])); +#endif + localVariables.Add(v); + } + } + } + + localScopes.Add(((MethodDefinitionHandle)method.MetadataToken, currentImportScope, + 0, methodBody.GetCodeSize(), localVariables)); + } + } +} diff --git a/ICSharpCode.Decompiler/DebugInfo/ImportScopeInfo.cs b/ICSharpCode.Decompiler/DebugInfo/ImportScopeInfo.cs index f4019aa11..c6625350d 100644 --- a/ICSharpCode.Decompiler/DebugInfo/ImportScopeInfo.cs +++ b/ICSharpCode.Decompiler/DebugInfo/ImportScopeInfo.cs @@ -25,7 +25,6 @@ namespace ICSharpCode.Decompiler.DebugInfo { public readonly ImportScopeInfo Parent; public ImportScopeHandle Handle; - public readonly HashSet MethodDefinitions = new HashSet(); public readonly HashSet Imports = new HashSet(); public ImportScopeInfo() diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs index 064ea4e83..11193e39f 100644 --- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs +++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs @@ -41,12 +41,13 @@ namespace ICSharpCode.Decompiler.DebugInfo { public class PortablePdbWriter { - public static readonly Guid CSharpLanguageGuid = new Guid("3f5162f8-07c6-11d3-9053-00c04fa302a1"); + static readonly Guid CSharpLanguageGuid = new Guid("3f5162f8-07c6-11d3-9053-00c04fa302a1"); - public static readonly Guid DebugInfoEmbeddedSource = new Guid("0e8a571b-6926-466e-b4ad-8ab04611f5fe"); + static readonly Guid DebugInfoEmbeddedSource = new Guid("0e8a571b-6926-466e-b4ad-8ab04611f5fe"); + static readonly Guid MethodSteppingInformationBlobId = new Guid("54FD2AC5-E925-401A-9C2A-F94F171072F8"); - public static readonly Guid HashAlgorithmSHA1 = new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460"); - public static readonly Guid HashAlgorithmSHA256 = new Guid("8829d00f-11b8-4213-878b-770e8597ac16"); + static readonly Guid HashAlgorithmSHA1 = new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460"); + static readonly Guid HashAlgorithmSHA256 = new Guid("8829d00f-11b8-4213-878b-770e8597ac16"); static readonly FileVersionInfo decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location); static readonly SHA256 hasher = SHA256.Create(); @@ -63,6 +64,9 @@ namespace ICSharpCode.Decompiler.DebugInfo var sequencePointBlobs = new Dictionary(); var emptyList = new List(); + var stateMachineMethods = new List<(MethodDefinitionHandle MoveNextMethod, MethodDefinitionHandle KickoffMethod)>(); + var customDocumentDebugInfo = new List<(DocumentHandle Parent, GuidHandle Guid, BlobHandle Blob)>(); + var customMethodDebugInfo = new List<(MethodDefinitionHandle Parent, GuidHandle Guid, BlobHandle Blob)>(); var globalImportScope = metadata.AddImportScope(default, default); foreach (var handle in reader.GetTopLevelTypeDefinitions()) { @@ -74,36 +78,50 @@ namespace ICSharpCode.Decompiler.DebugInfo continue; // Generate source and checksum - var name = metadata.GetOrAddDocumentName(type.GetFullTypeName(reader).ReflectionName.Replace('.', Path.DirectorySeparatorChar) + ".cs"); - if (!noLogo) syntaxTree.InsertChildAfter(null, new Comment(" PDB and source generated by ICSharpCode.Decompiler " + decompilerVersion.FileVersion), Roles.Comment); var sourceText = SyntaxTreeToString(syntaxTree, settings); - var sourceBlob = WriteSourceToBlob(metadata, sourceText, out var sourceCheckSum); - - // Create Document(Handle) - var document = metadata.AddDocument(name, - hashAlgorithm: metadata.GetOrAddGuid(HashAlgorithmSHA256), - hash: metadata.GetOrAddBlob(sourceCheckSum), - language: metadata.GetOrAddGuid(CSharpLanguageGuid)); - - // Add embedded source to the PDB - metadata.AddCustomDebugInformation(document, metadata.GetOrAddGuid(DebugInfoEmbeddedSource), sourceBlob); - - ScopesGenerator.Generate(decompiler.TypeSystem, metadata, globalImportScope, syntaxTree); // Generate sequence points for the syntax tree - var sequencePoints = decompiler.CreateSequencePoints(syntaxTree).ToDictionary(sp => (MethodDefinitionHandle)sp.Key.Method.MetadataToken, sp => sp.Value); - - foreach (var method in type.GetMethods()) { - ProcessMethod(method, document, sequencePoints, syntaxTree); - } - - foreach (var nestedTypeHandle in type.GetNestedTypes()) { - var nestedType = reader.GetTypeDefinition(nestedTypeHandle); - - foreach (var method in nestedType.GetMethods()) { - ProcessMethod(method, document, sequencePoints, syntaxTree); + var sequencePoints = decompiler.CreateSequencePoints(syntaxTree); + + // Generate other debug information + var debugInfoGen = new DebugInfoGenerator(decompiler.TypeSystem); + syntaxTree.AcceptVisitor(debugInfoGen); + + lock (metadata) { + var sourceBlob = WriteSourceToBlob(metadata, sourceText, out var sourceCheckSum); + var name = metadata.GetOrAddDocumentName(type.GetFullTypeName(reader).ReflectionName.Replace('.', Path.DirectorySeparatorChar) + ".cs"); + + // Create Document(Handle) + var document = metadata.AddDocument(name, + hashAlgorithm: metadata.GetOrAddGuid(HashAlgorithmSHA256), + hash: metadata.GetOrAddBlob(sourceCheckSum), + language: metadata.GetOrAddGuid(CSharpLanguageGuid)); + + // Add embedded source to the PDB + customDocumentDebugInfo.Add((document, + metadata.GetOrAddGuid(DebugInfoEmbeddedSource), + sourceBlob)); + + debugInfoGen.Generate(metadata, globalImportScope); + + foreach (var function in debugInfoGen.Functions) { + var method = function.MoveNextMethod ?? function.Method; + var methodHandle = (MethodDefinitionHandle)method.MetadataToken; + sequencePoints.TryGetValue(function, out var points); + ProcessMethod(methodHandle, document, points, syntaxTree); + if (function.MoveNextMethod != null) { + stateMachineMethods.Add(( + (MethodDefinitionHandle)function.MoveNextMethod.MetadataToken, + (MethodDefinitionHandle)function.Method.MetadataToken + )); + } + if (function.IsAsync) { + customMethodDebugInfo.Add((methodHandle, + metadata.GetOrAddGuid(MethodSteppingInformationBlobId), + metadata.GetOrAddBlob(function.AsyncDebugInfo.BuildBlob(methodHandle)))); + } } } } @@ -118,6 +136,19 @@ namespace ICSharpCode.Decompiler.DebugInfo } } + stateMachineMethods.SortBy(row => row.MoveNextMethod); + foreach (var row in stateMachineMethods) { + metadata.AddStateMachineMethod(row.MoveNextMethod, row.KickoffMethod); + } + customMethodDebugInfo.SortBy(row => row.Parent); + foreach (var row in customMethodDebugInfo) { + metadata.AddCustomDebugInformation(row.Parent, row.Guid, row.Blob); + } + customDocumentDebugInfo.SortBy(row => row.Parent); + foreach (var row in customDocumentDebugInfo) { + metadata.AddCustomDebugInformation(row.Parent, row.Guid, row.Blob); + } + var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView); var portable = file.Reader.ReadCodeViewDebugDirectoryData(debugDir); var contentId = new BlobContentId(portable.Guid, debugDir.Stamp); @@ -127,7 +158,7 @@ namespace ICSharpCode.Decompiler.DebugInfo blobBuilder.WriteContentTo(targetStream); void ProcessMethod(MethodDefinitionHandle method, DocumentHandle document, - Dictionary> sequencePoints, SyntaxTree syntaxTree) + List sequencePoints, SyntaxTree syntaxTree) { var methodDef = reader.GetMethodDefinition(method); int localSignatureRowId; @@ -139,13 +170,10 @@ namespace ICSharpCode.Decompiler.DebugInfo methodBody = null; localSignatureRowId = 0; } - if (!sequencePoints.TryGetValue(method, out var points)) - points = emptyList; - if (points.Count == 0) - sequencePointBlobs.Add(method, (default, default)); + if (sequencePoints?.Count > 0) + sequencePointBlobs.Add(method, (document, EncodeSequencePoints(metadata, localSignatureRowId, sequencePoints))); else - sequencePointBlobs.Add(method, (document, EncodeSequencePoints(metadata, localSignatureRowId, points))); - + sequencePointBlobs.Add(method, (default, default)); } } diff --git a/ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs b/ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs deleted file mode 100644 index 99d7f6370..000000000 --- a/ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2018 Siegfried Pammer -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Text; -using ICSharpCode.Decompiler.CSharp; -using ICSharpCode.Decompiler.CSharp.Syntax; -using ICSharpCode.Decompiler.IL; -using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.Decompiler.Util; - -namespace ICSharpCode.Decompiler.DebugInfo -{ - class ScopesGenerator : DepthFirstAstVisitor - { - static readonly KeyComparer ILVariableKeyComparer = new KeyComparer(l => l.Index.Value, Comparer.Default, EqualityComparer.Default); - - IDecompilerTypeSystem typeSystem; - ImportScopeInfo currentImportScope; - List importScopes = new List(); - List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet Locals)> localScopes = new List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet Locals)>(); - - private ScopesGenerator() - { - } - - public static void Generate(IDecompilerTypeSystem typeSystem, MetadataBuilder metadata, ImportScopeHandle globalImportScope, SyntaxTree syntaxTree) - { - var generator = new ScopesGenerator(); - generator.typeSystem = typeSystem; - syntaxTree.AcceptVisitor(generator, new ImportScopeInfo()); - foreach (var scope in generator.importScopes) { - var blob = EncodeImports(metadata, scope); - scope.Handle = metadata.AddImportScope(scope.Parent == null ? globalImportScope : scope.Parent.Handle, blob); - } - - foreach (var localScope in generator.localScopes) { - int nextRow = metadata.GetRowCount(TableIndex.LocalVariable) + 1; - var firstLocalVariable = MetadataTokens.LocalVariableHandle(nextRow); - - foreach (var local in localScope.Locals.OrderBy(l => l.Index)) { - var name = local.Name != null ? metadata.GetOrAddString(local.Name) : default; - metadata.AddLocalVariable(LocalVariableAttributes.None, local.Index.Value, name); - } - - metadata.AddLocalScope(localScope.Method, localScope.Import.Handle, firstLocalVariable, - default, localScope.Offset, localScope.Length); - } - } - - static BlobHandle EncodeImports(MetadataBuilder metadata, ImportScopeInfo scope) - { - var writer = new BlobBuilder(); - - foreach (var import in scope.Imports) { - writer.WriteByte((byte)ImportDefinitionKind.ImportNamespace); - writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(metadata.GetOrAddBlobUTF8(import))); - } - - return metadata.GetOrAddBlob(writer); - } - - public override ImportScopeInfo VisitSyntaxTree(SyntaxTree unit, ImportScopeInfo data) - { - importScopes.Add(data); - currentImportScope = data; - base.VisitSyntaxTree(unit, data); - currentImportScope = data; - return data; - } - - public override ImportScopeInfo VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, ImportScopeInfo data) - { - ImportScopeInfo scope = new ImportScopeInfo(data); - importScopes.Add(scope); - currentImportScope = scope; - base.VisitNamespaceDeclaration(namespaceDeclaration, scope); - currentImportScope = data; - return data; - } - - public override ImportScopeInfo VisitUsingDeclaration(UsingDeclaration usingDeclaration, ImportScopeInfo data) - { - data.Imports.Add(usingDeclaration.Namespace); - return data; - } - - public override ImportScopeInfo VisitMethodDeclaration(MethodDeclaration methodDeclaration, ImportScopeInfo data) - { - HandleMethod(methodDeclaration, data); - return data; - } - - public override ImportScopeInfo VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, ImportScopeInfo data) - { - var symbol = propertyDeclaration.GetSymbol(); - if (symbol is IProperty property && !property.MetadataToken.IsNil) { - if (property.CanGet && !property.Getter.MetadataToken.IsNil) - data.MethodDefinitions.Add((MethodDefinitionHandle)property.Getter.MetadataToken); - if (property.CanSet && !property.Setter.MetadataToken.IsNil) - data.MethodDefinitions.Add((MethodDefinitionHandle)property.Setter.MetadataToken); - } - return data; - } - - public override ImportScopeInfo VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration, ImportScopeInfo data) - { - HandleEvent(eventDeclaration, data); - return data; - } - - void HandleEvent(AstNode node, ImportScopeInfo data) - { - var symbol = node.GetSymbol(); - if (symbol is IEvent @event && !@event.MetadataToken.IsNil) { - if (@event.CanAdd && !@event.AddAccessor.MetadataToken.IsNil) - data.MethodDefinitions.Add((MethodDefinitionHandle)@event.AddAccessor.MetadataToken); - if (@event.CanRemove && !@event.RemoveAccessor.MetadataToken.IsNil) - data.MethodDefinitions.Add((MethodDefinitionHandle)@event.RemoveAccessor.MetadataToken); - if (@event.CanInvoke && !@event.InvokeAccessor.MetadataToken.IsNil) - data.MethodDefinitions.Add((MethodDefinitionHandle)@event.InvokeAccessor.MetadataToken); - } - } - - public override ImportScopeInfo VisitEventDeclaration(EventDeclaration eventDeclaration, ImportScopeInfo data) - { - HandleEvent(eventDeclaration, data); - return data; - } - - public override ImportScopeInfo VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, ImportScopeInfo data) - { - HandleMethod(constructorDeclaration, data); - return data; - } - - public override ImportScopeInfo VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration, ImportScopeInfo data) - { - HandleMethod(destructorDeclaration, data); - return data; - } - - void HandleMethod(AstNode node, ImportScopeInfo data) - { - var symbol = node.GetSymbol(); - var function = node.Annotations.OfType().FirstOrDefault(); - if (function != null && symbol is IMethod method && !method.MetadataToken.IsNil) { - MethodDefinitionHandle handle = (MethodDefinitionHandle)method.MetadataToken; - data.MethodDefinitions.Add(handle); - var file = typeSystem.MainModule.PEFile; - MethodDefinition md = file.Metadata.GetMethodDefinition(handle); - if (md.HasBody()) { - HandleMethodBody(function, file.Reader.GetMethodBody(md.RelativeVirtualAddress)); - } - } - } - - void HandleMethodBody(ILFunction function, MethodBodyBlock methodBody) - { - var localVariables = new HashSet(ILVariableKeyComparer); - - if (!methodBody.LocalSignature.IsNil) { - var types = typeSystem.MainModule.DecodeLocalSignature(methodBody.LocalSignature, - new TypeSystem.GenericContext(function.Method)); - - foreach (var v in function.Variables) { - if (v.Index != null && v.Kind.IsLocal()) { - Debug.Assert(v.Index < types.Length && v.Type.Equals(types[v.Index.Value])); - localVariables.Add(v); - } - } - } - - localScopes.Add(((MethodDefinitionHandle)function.Method.MetadataToken, currentImportScope, - 0, methodBody.GetCodeSize(), localVariables)); - } - } -} diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index dc3c1c204..08bb6424b 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -240,8 +240,9 @@ + - + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index e9ad73153..9e8ccbebb 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -17,11 +17,13 @@ // DEALINGS IN THE SOFTWARE. using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; @@ -94,6 +96,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // across the yield point. Dictionary awaitBlocks = new Dictionary(); + int catchHandlerOffset; + List awaitDebugInfos = new List(); + public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AsyncAwait) @@ -102,6 +107,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow fieldToParameterMap.Clear(); cachedFieldToParameterMap.Clear(); awaitBlocks.Clear(); + awaitDebugInfos.Clear(); moveNextLeaves.Clear(); if (!MatchTaskCreationPattern(function)) return; @@ -135,6 +141,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow AwaitInCatchTransform.Run(function, context); AwaitInFinallyTransform.Run(function, context); + + awaitDebugInfos.SortBy(row => row.YieldOffset); + function.AsyncDebugInfo = new AsyncDebugInfo(catchHandlerOffset, awaitDebugInfos.ToImmutableArray()); } private void CleanUpBodyOfMoveNext(ILFunction function) @@ -427,6 +436,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (!handler.Filter.MatchLdcI4(1)) throw new SymbolicAnalysisFailedException(); var catchBlock = YieldReturnDecompiler.SingleBlock(handler.Body); + catchHandlerOffset = catchBlock.ILRange.Start; if (catchBlock?.Instructions.Count != 4) throw new SymbolicAnalysisFailedException(); // stloc exception(ldloc E_143) @@ -488,6 +498,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow context.Step("Inline body of MoveNext()", function); function.Body = mainTryCatch.TryBlock; function.AsyncReturnType = underlyingReturnType; + function.MoveNextMethod = moveNextFunction.Method; + function.CodeSize = moveNextFunction.CodeSize; moveNextFunction.Variables.Clear(); moveNextFunction.ReleaseRef(); foreach (var branch in function.Descendants.OfType()) { @@ -555,10 +567,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow context.CancellationToken.ThrowIfCancellationRequested(); if (block.Instructions.Last() is Leave leave && moveNextLeaves.Contains(leave)) { // This is likely an 'await' block - if (AnalyzeAwaitBlock(block, out var awaiterVar, out var awaiterField, out var state)) { + 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); if (targetBlock != null) { + awaitDebugInfos.Add(new AsyncDebugInfo.Await(yieldOffset, targetBlock.ILRange.Start)); block.Instructions.Add(new Branch(targetBlock)); } else { block.Instructions.Add(new InvalidBranch("Could not find block for state " + state)); @@ -583,11 +596,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } } - bool AnalyzeAwaitBlock(Block block, out ILVariable awaiter, out IField awaiterField, out int state) + bool AnalyzeAwaitBlock(Block block, out ILVariable awaiter, out IField awaiterField, out int state, out int yieldOffset) { awaiter = null; awaiterField = null; state = 0; + yieldOffset = -1; int pos = block.Instructions.Count - 2; if (pos >= 0 && doFinallyBodies != null && block.Instructions[pos] is StLoc storeDoFinallyBodies) { if (!(storeDoFinallyBodies.Variable.Kind == VariableKind.Local @@ -638,6 +652,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (!value.MatchLdLoc(awaiter)) return false; pos--; + // Store IL offset for debug info: + yieldOffset = block.Instructions[pos].ILRange.End; + // stloc S_10(ldloc this) // stloc S_11(ldc.i4 0) // stloc cachedStateVar(ldloc S_11) diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index 9da35d02f..b12654d6c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -30,7 +30,11 @@ namespace ICSharpCode.Decompiler.IL { public readonly IMethod Method; public readonly GenericContext GenericContext; - public readonly int CodeSize; + /// + /// Size of the IL code in this function. + /// Note: after async/await transform, this is the code size of the MoveNext function. + /// + public int CodeSize; public readonly ILVariableCollection Variables; /// @@ -61,6 +65,13 @@ namespace ICSharpCode.Decompiler.IL /// public IType AsyncReturnType; + /// + /// If this function is an iterator/async, this field stores the compiler-generated MoveNext() method. + /// + public IMethod MoveNextMethod; + + internal DebugInfo.AsyncDebugInfo AsyncDebugInfo; + /// /// If this is an expression tree or delegate, returns the expression tree type Expression{T} or T. /// T is the delegate type that matches the signature of this method. diff --git a/ICSharpCode.Decompiler/Util/KeyComparer.cs b/ICSharpCode.Decompiler/Util/KeyComparer.cs index 3191c1ac1..43e26e9f9 100644 --- a/ICSharpCode.Decompiler/Util/KeyComparer.cs +++ b/ICSharpCode.Decompiler/Util/KeyComparer.cs @@ -42,6 +42,11 @@ namespace ICSharpCode.Decompiler.Util { return new KeyComparer(keySelector, Comparer.Default, equalityComparer); } + + public static void SortBy(this List list, Func keySelector) + { + list.Sort(Create(keySelector)); + } } public class KeyComparer : IComparer, IEqualityComparer diff --git a/ILSpy/Languages/CSharpILMixedLanguage.cs b/ILSpy/Languages/CSharpILMixedLanguage.cs index c5d37efe0..855dd8b99 100644 --- a/ILSpy/Languages/CSharpILMixedLanguage.cs +++ b/ILSpy/Languages/CSharpILMixedLanguage.cs @@ -91,7 +91,7 @@ namespace ICSharpCode.ILSpy CSharpDecompiler decompiler = CreateDecompiler(module, options); var st = decompiler.Decompile(handle); WriteCode(csharpOutput, options.DecompilerSettings, st, decompiler.TypeSystem); - var mapping = decompiler.CreateSequencePoints(st).FirstOrDefault(kvp => kvp.Key.Method.MetadataToken == handle); + var mapping = decompiler.CreateSequencePoints(st).FirstOrDefault(kvp => (kvp.Key.MoveNextMethod ?? kvp.Key.Method).MetadataToken == handle); this.sequencePoints = mapping.Value ?? (IList)EmptyList.Instance; this.codeLines = csharpOutput.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); base.Disassemble(module, handle); diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index df1507ebd..669fb6a3d 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -92,7 +92,7 @@ -