.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

241 lines
8.8 KiB

// Copyright (c) 2019 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.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
using System.Text.RegularExpressions;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
class LocalFunctionDecompiler : IILTransform
{
ILTransformContext context;
ITypeResolveContext decompilationContext;
public void Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.LocalFunctions)
return;
this.context = context;
this.decompilationContext = new SimpleTypeResolveContext(function.Method);
var localFunctions = new Dictionary<IMethod, List<Call>>();
var cancellationToken = context.CancellationToken;
// Find use-sites
foreach (var inst in function.Descendants) {
cancellationToken.ThrowIfCancellationRequested();
if (inst is Call call && IsLocalFunctionMethod(call.Method)) {
context.StepStartGroup($"LocalFunctionDecompiler {call.StartILOffset}", call);
if (!localFunctions.TryGetValue(call.Method, out var info)) {
info = new List<Call>() { call };
localFunctions.Add(call.Method, info);
} else {
info.Add(call);
}
context.StepEndGroup();
}
}
foreach (var (method, useSites) in localFunctions) {
var insertionPoint = FindInsertionPoint(useSites);
ILFunction localFunction = TransformLocalFunction(method, (Block)insertionPoint.Parent, insertionPoint.ChildIndex + 1);
if (localFunction == null)
continue;
function.LocalFunctions.Add(localFunction.Method, (localFunction.Method.Name, localFunction));
}
}
static ILInstruction FindInsertionPoint(List<Call> useSites)
{
ILInstruction insertionPoint = null;
foreach (var call in useSites) {
if (insertionPoint == null) {
insertionPoint = GetStatement(call);
continue;
}
var ancestor = FindCommonAncestorInstruction(insertionPoint, GetStatement(call));
if (ancestor == null)
return null;
insertionPoint = ancestor;
}
switch (insertionPoint) {
case BlockContainer bc:
return insertionPoint;
case Block b:
return insertionPoint;
default:
return insertionPoint;
}
}
static ILInstruction FindCommonAncestorInstruction(ILInstruction a, ILInstruction b)
{
var ancestorsOfB = new HashSet<ILInstruction>(b.Ancestors);
return a.Ancestors.FirstOrDefault(ancestorsOfB.Contains);
}
internal static bool IsClosureParameter(IParameter parameter)
{
return parameter.IsRef
&& ((ByReferenceType)parameter.Type).ElementType.GetDefinition()?.IsCompilerGenerated() == true;
}
internal static ILInstruction GetStatement(ILInstruction inst)
{
while (inst.Parent != null) {
if (inst.Parent is Block)
return inst;
inst = inst.Parent;
}
return inst;
}
private ILFunction TransformLocalFunction(IMethod targetMethod, Block parent, int insertionPoint)
{
var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken);
if (!methodDefinition.HasBody())
return null;
var genericContext = DelegateConstruction.GenericContextFromTypeArguments(targetMethod.Substitution);
if (genericContext == null)
return null;
var ilReader = context.CreateILReader();
var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress);
var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.LocalFunction, context.CancellationToken);
// Embed the local function into the parent function's ILAst, so that "Show steps" can show
// how the local function body is being transformed.
parent.Instructions.Insert(insertionPoint, function);
function.CheckInvariant(ILPhase.Normal);
var nestedContext = new ILTransformContext(context, function);
function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext);
return function;
}
public static bool IsLocalFunctionMethod(IMethod method)
{
return IsLocalFunctionMethod(method.ParentModule.PEFile, (MethodDefinitionHandle)method.MetadataToken);
}
public static bool IsLocalFunctionMethod(PEFile module, MethodDefinitionHandle methodHandle)
{
var metadata = module.Metadata;
var method = metadata.GetMethodDefinition(methodHandle);
var declaringType = method.GetDeclaringType();
if ((method.Attributes & MethodAttributes.Assembly) == 0 || !(method.IsCompilerGenerated(metadata) || declaringType.IsCompilerGenerated(metadata)))
return false;
if (!ParseLocalFunctionName(metadata.GetString(method.Name), out _, out _))
return false;
return true;
}
public static bool IsLocalFunctionDisplayClass(PEFile module, TypeDefinitionHandle typeHandle)
{
var metadata = module.Metadata;
var type = metadata.GetTypeDefinition(typeHandle);
if ((type.Attributes & TypeAttributes.NestedPrivate) == 0)
return false;
if (!type.HasGeneratedName(metadata))
return false;
var declaringTypeHandle = type.GetDeclaringType();
var declaringType = metadata.GetTypeDefinition(declaringTypeHandle);
foreach (var method in declaringType.GetMethods()) {
if (!IsLocalFunctionMethod(module, method))
continue;
var md = metadata.GetMethodDefinition(method);
if (md.DecodeSignature(new FindTypeDecoder(typeHandle), default).ParameterTypes.Any())
return true;
}
return false;
}
/// <summary>
/// Newer Roslyn versions use the format "&ltcallerName&gtg__functionName|x_y"
/// Older versions use "&ltcallerName&gtg__functionNamex_y"
/// </summary>
static readonly Regex functionNameRegex = new Regex(@"^<(.*)>g__([^\|]*)\|{0,1}\d+(_\d+)?$", RegexOptions.Compiled);
internal static bool ParseLocalFunctionName(string name, out string callerName, out string functionName)
{
callerName = null;
functionName = null;
if (string.IsNullOrWhiteSpace(name))
return false;
var match = functionNameRegex.Match(name);
callerName = match.Groups[1].Value;
functionName = match.Groups[2].Value;
return match.Success;
}
struct FindTypeDecoder : ISignatureTypeProvider<bool, Unit>
{
TypeDefinitionHandle handle;
public FindTypeDecoder(TypeDefinitionHandle handle)
{
this.handle = handle;
}
public bool GetArrayType(bool elementType, ArrayShape shape) => elementType;
public bool GetByReferenceType(bool elementType) => elementType;
public bool GetFunctionPointerType(MethodSignature<bool> signature) => false;
public bool GetGenericInstantiation(bool genericType, ImmutableArray<bool> typeArguments) => genericType;
public bool GetGenericMethodParameter(Unit genericContext, int index) => false;
public bool GetGenericTypeParameter(Unit genericContext, int index) => false;
public bool GetModifiedType(bool modifier, bool unmodifiedType, bool isRequired) => unmodifiedType;
public bool GetPinnedType(bool elementType) => elementType;
public bool GetPointerType(bool elementType) => elementType;
public bool GetPrimitiveType(PrimitiveTypeCode typeCode) => false;
public bool GetSZArrayType(bool elementType) => false;
public bool GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
{
return this.handle == handle;
}
public bool GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
{
return false;
}
public bool GetTypeFromSpecification(MetadataReader reader, Unit genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
{
return reader.GetTypeSpecification(handle).DecodeSignature(this, genericContext);
}
}
}
}