// 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.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.IL;
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();
internal List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet Locals)> LocalScopes { get; } = 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(nameof(typeSystem));
this.currentImportScope = globalImportScope;
}
public void GenerateImportScopes(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);
}
}
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);
}
public override void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration)
{
if (!propertyDeclaration.ExpressionBody.IsNull)
{
HandleMethod(propertyDeclaration.ExpressionBody, propertyDeclaration.Annotation());
}
else
{
base.VisitPropertyDeclaration(propertyDeclaration);
}
}
public override void VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration)
{
if (!indexerDeclaration.ExpressionBody.IsNull)
{
HandleMethod(indexerDeclaration.ExpressionBody, indexerDeclaration.Annotation());
}
else
{
base.VisitIndexerDeclaration(indexerDeclaration);
}
}
public override void VisitQueryFromClause(QueryFromClause queryFromClause)
{
if (queryFromClause.Parent.FirstChild != queryFromClause)
{
HandleMethod(queryFromClause);
}
else
{
base.VisitQueryFromClause(queryFromClause);
}
}
public override void VisitQueryGroupClause(QueryGroupClause queryGroupClause)
{
var annotation = queryGroupClause.Annotation();
if (annotation == null)
{
base.VisitQueryGroupClause(queryGroupClause);
return;
}
HandleMethod(queryGroupClause.Projection, annotation.ProjectionLambda);
HandleMethod(queryGroupClause.Key, annotation.KeyLambda);
}
public override void VisitQueryJoinClause(QueryJoinClause queryJoinClause)
{
var annotation = queryJoinClause.Annotation();
if (annotation == null)
{
base.VisitQueryJoinClause(queryJoinClause);
return;
}
HandleMethod(queryJoinClause.OnExpression, annotation.OnLambda);
HandleMethod(queryJoinClause.EqualsExpression, annotation.EqualsLambda);
}
public override void VisitQueryLetClause(QueryLetClause queryLetClause)
{
HandleMethod(queryLetClause);
}
public override void VisitQueryOrdering(QueryOrdering queryOrdering)
{
HandleMethod(queryOrdering);
}
public override void VisitQuerySelectClause(QuerySelectClause querySelectClause)
{
HandleMethod(querySelectClause);
}
public override void VisitQueryWhereClause(QueryWhereClause queryWhereClause)
{
HandleMethod(queryWhereClause);
}
void HandleMethod(AstNode node)
{
HandleMethod(node, node.Annotation());
}
void HandleMethod(AstNode node, ILFunction function)
{
// Look into method body, e.g. in order to find lambdas
VisitChildren(node);
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.MetadataFile;
MethodDefinition md = file.Metadata.GetMethodDefinition(handle);
if (md.HasBody())
{
HandleMethodBody(function, file.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 && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(v.Type, types[v.Index.Value]));
#endif
localVariables.Add(v);
}
}
}
LocalScopes.Add(((MethodDefinitionHandle)method.MetadataToken, currentImportScope,
0, methodBody.GetCodeSize(), localVariables));
}
}
}