Browse Source

Merge pull request #1561 from icsharpcode/mcs

Improved support for closures generated by Mono C# compiler
pull/1596/head
Siegfried Pammer 6 years ago committed by GitHub
parent
commit
132595ac76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  2. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 5
      ICSharpCode.Decompiler/IL/ILVariable.cs
  5. 202
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  6. 4
      ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs
  7. 4
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  8. 319
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
  9. 18
      ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

4
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -134,7 +134,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -134,7 +134,7 @@ namespace ICSharpCode.Decompiler.Tests
}
[Test]
public void DelegateConstruction([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
public void DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
@ -420,7 +420,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -420,7 +420,7 @@ namespace ICSharpCode.Decompiler.Tests
}
[Test]
public void YieldReturn([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
public void YieldReturn([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -156,6 +156,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -156,6 +156,7 @@ namespace ICSharpCode.Decompiler.CSharp
},
new ProxyCallReplacer(),
new DelegateConstruction(),
new TransformDisplayClassUsage(),
new HighLevelLoopTransform(),
new ReduceNestingTransform(),
new IntroduceDynamicTypeOnLocals(),

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -272,6 +272,7 @@ @@ -272,6 +272,7 @@
<Compile Include="IL\Transforms\DynamicIsEventAssignmentTransform.cs" />
<Compile Include="IL\Transforms\ReduceNestingTransform.cs" />
<Compile Include="IL\Transforms\LocalFunctionDecompiler.cs" />
<Compile Include="IL\Transforms\TransformDisplayClassUsage.cs" />
<Compile Include="IL\Transforms\UserDefinedLogicTransform.cs" />
<Compile Include="Metadata\AssemblyReferences.cs" />
<Compile Include="Metadata\CodeMappingInfo.cs" />

5
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -74,6 +74,11 @@ namespace ICSharpCode.Decompiler.IL @@ -74,6 +74,11 @@ namespace ICSharpCode.Decompiler.IL
static class VariableKindExtensions
{
public static bool IsThis(this ILVariable v)
{
return v.Kind == VariableKind.Parameter && v.Index < 0;
}
public static bool IsLocal(this VariableKind kind)
{
switch (kind) {

202
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -16,9 +16,7 @@ @@ -16,9 +16,7 @@
// 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 ICSharpCode.Decompiler.CSharp;
@ -37,9 +35,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -37,9 +35,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return;
this.context = context;
this.decompilationContext = new SimpleTypeResolveContext(function.Method);
var orphanedVariableInits = new List<ILInstruction>();
var targetsToReplace = new List<IInstructionWithVariableOperand>();
var translatedDisplayClasses = new HashSet<ITypeDefinition>();
var cancellationToken = context.CancellationToken;
foreach (var inst in function.Descendants) {
cancellationToken.ThrowIfCancellationRequested();
@ -50,49 +45,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -50,49 +45,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (instWithVar.Variable.Kind == VariableKind.Local) {
instWithVar.Variable.Kind = VariableKind.DisplayClassLocal;
}
targetsToReplace.Add(instWithVar);
var displayClassTypeDef = instWithVar.Variable.Type.GetDefinition();
if (instWithVar.Variable.IsSingleDefinition && instWithVar.Variable.StoreInstructions.SingleOrDefault() is StLoc store) {
if (store.Value is NewObj newObj) {
instWithVar.Variable.CaptureScope = BlockContainer.FindClosestContainer(store);
}
}
}
context.StepEndGroup();
}
if (inst.MatchStLoc(out ILVariable targetVariable, out ILInstruction value)) {
var newObj = value as NewObj;
// TODO : it is probably not a good idea to remove *all* display-classes
// is there a way to minimize the false-positives?
if (newObj != null && IsInSimpleDisplayClass(newObj.Method)) {
targetVariable.CaptureScope = BlockContainer.FindClosestContainer(inst);
targetsToReplace.Add((IInstructionWithVariableOperand)inst);
translatedDisplayClasses.Add(newObj.Method.DeclaringTypeDefinition);
}
}
}
foreach (var target in targetsToReplace.OrderByDescending(t => ((ILInstruction)t).StartILOffset)) {
context.Step($"TransformDisplayClassUsages {target.Variable}", (ILInstruction)target);
function.AcceptVisitor(new TransformDisplayClassUsages(function, target, target.Variable.CaptureScope, orphanedVariableInits, translatedDisplayClasses));
}
context.Step($"Remove orphanedVariableInits", function);
foreach (var store in orphanedVariableInits) {
if (store.Parent is Block containingBlock)
containingBlock.Instructions.Remove(store);
}
}
static bool IsInSimpleDisplayClass(IMethod method)
{
if (!method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false;
return IsSimpleDisplayClass(method.DeclaringType);
}
internal static bool IsSimpleDisplayClass(IType type)
{
if (!type.HasGeneratedName() || (!type.Name.Contains("DisplayClass") && !type.Name.Contains("AnonStorey")))
return false;
if (type.DirectBaseTypes.Any(t => !t.IsKnownType(KnownTypeCode.Object)))
return false;
return true;
}
#region TransformDelegateConstruction
internal static bool IsDelegateConstruction(NewObj inst, bool allowTransformed = false)
{
if (inst == null || inst.Arguments.Count != 2 || inst.Method.DeclaringType.Kind != TypeKind.Delegate)
@ -101,12 +65,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -101,12 +65,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn || (allowTransformed && opCode == OpCode.ILFunction);
}
internal static bool IsPotentialClosure(ILTransformContext context, NewObj inst)
{
var decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition);
}
static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod method)
{
@ -115,7 +73,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -115,7 +73,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!(method.HasGeneratedName()
|| method.Name.Contains("$")
|| method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()
|| IsPotentialClosure(decompiledTypeDefinition, method.DeclaringTypeDefinition)
|| TransformDisplayClassUsage.IsPotentialClosure(decompiledTypeDefinition, method.DeclaringTypeDefinition)
|| ContainsAnonymousType(method)))
return false;
return true;
@ -131,18 +89,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -131,18 +89,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
return false;
}
static bool IsPotentialClosure(ITypeDefinition decompiledTypeDefinition, ITypeDefinition potentialDisplayClass)
{
if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false;
while (potentialDisplayClass != decompiledTypeDefinition) {
potentialDisplayClass = potentialDisplayClass.DeclaringTypeDefinition;
if (potentialDisplayClass == null)
return false;
}
return true;
}
internal static GenericContext? GenericContextFromTypeArguments(TypeParameterSubstitution subst)
{
@ -259,139 +205,5 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -259,139 +205,5 @@ namespace ICSharpCode.Decompiler.IL.Transforms
base.VisitLdObj(inst);
}
}
/// <summary>
/// 1. Stores to display class fields are replaced with stores to local variables (in some
/// cases existing variables are used; otherwise fresh variables are added to the
/// ILFunction-container) and all usages of those fields are replaced with the local variable.
/// (see initValues)
/// 2. Usages of the display class container (or any copy) are removed.
/// </summary>
class TransformDisplayClassUsages : ILVisitor
{
ILFunction currentFunction;
BlockContainer captureScope;
readonly IInstructionWithVariableOperand targetLoad;
readonly List<ILVariable> targetAndCopies = new List<ILVariable>();
readonly List<ILInstruction> orphanedVariableInits;
readonly HashSet<ITypeDefinition> translatedDisplayClasses;
readonly Dictionary<IField, DisplayClassVariable> initValues = new Dictionary<IField, DisplayClassVariable>();
struct DisplayClassVariable
{
public ILVariable variable;
public ILInstruction value;
}
public TransformDisplayClassUsages(ILFunction function, IInstructionWithVariableOperand targetLoad, BlockContainer captureScope, List<ILInstruction> orphanedVariableInits, HashSet<ITypeDefinition> translatedDisplayClasses)
{
this.currentFunction = function;
this.targetLoad = targetLoad;
this.captureScope = captureScope;
this.orphanedVariableInits = orphanedVariableInits;
this.translatedDisplayClasses = translatedDisplayClasses;
this.targetAndCopies.Add(targetLoad.Variable);
}
protected override void Default(ILInstruction inst)
{
foreach (var child in inst.Children) {
child.AcceptVisitor(this);
}
}
protected internal override void VisitStLoc(StLoc inst)
{
base.VisitStLoc(inst);
if (targetLoad is ILInstruction instruction && instruction.MatchLdThis())
return;
if (inst.Variable == targetLoad.Variable)
orphanedVariableInits.Add(inst);
if (MatchesTargetOrCopyLoad(inst.Value)) {
targetAndCopies.Add(inst.Variable);
orphanedVariableInits.Add(inst);
}
}
bool MatchesTargetOrCopyLoad(ILInstruction inst)
{
return targetAndCopies.Any(v => inst.MatchLdLoc(v));
}
protected internal override void VisitStObj(StObj inst)
{
base.VisitStObj(inst);
if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field) || !MatchesTargetOrCopyLoad(target) || target.MatchLdThis())
return;
field = (IField)field.MemberDefinition;
ILInstruction value;
if (initValues.TryGetValue(field, out DisplayClassVariable info)) {
inst.ReplaceWith(new StLoc(info.variable, inst.Value).WithILRange(inst));
} else {
if (inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter && currentFunction == v.Function) {
// special case for parameters: remove copies of parameter values.
orphanedVariableInits.Add(inst);
value = inst.Value;
} else {
if (!translatedDisplayClasses.Contains(field.DeclaringTypeDefinition))
return;
v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name);
v.CaptureScope = captureScope;
inst.ReplaceWith(new StLoc(v, inst.Value).WithILRange(inst));
value = new LdLoc(v);
}
initValues.Add(field, new DisplayClassVariable { value = value, variable = v });
}
}
protected internal override void VisitLdObj(LdObj inst)
{
base.VisitLdObj(inst);
if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field))
return;
if (!initValues.TryGetValue((IField)field.MemberDefinition, out DisplayClassVariable info))
return;
var replacement = info.value.Clone();
replacement.SetILRange(inst);
inst.ReplaceWith(replacement);
}
protected internal override void VisitLdFlda(LdFlda inst)
{
base.VisitLdFlda(inst);
if (inst.Target.MatchLdThis() && inst.Field.Name == "$this"
&& inst.Field.MemberDefinition.ReflectionName.Contains("c__Iterator")) {
var variable = currentFunction.Variables.First((f) => f.Index == -1);
inst.ReplaceWith(new LdLoca(variable).WithILRange(inst));
}
if (inst.Parent is LdObj || inst.Parent is StObj)
return;
if (!MatchesTargetOrCopyLoad(inst.Target))
return;
var field = (IField)inst.Field.MemberDefinition;
if (!initValues.TryGetValue(field, out DisplayClassVariable info)) {
if (!translatedDisplayClasses.Contains(field.DeclaringTypeDefinition))
return;
var v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name);
v.CaptureScope = captureScope;
inst.ReplaceWith(new LdLoca(v).WithILRange(inst));
var value = new LdLoc(v);
initValues.Add(field, new DisplayClassVariable { value = value, variable = v });
} else if (info.value is LdLoc l) {
inst.ReplaceWith(new LdLoca(l.Variable).WithILRange(inst));
} else {
Debug.Fail("LdFlda pattern not supported!");
}
}
protected internal override void VisitNumericCompoundAssign(NumericCompoundAssign inst)
{
base.VisitNumericCompoundAssign(inst);
if (inst.Target.MatchLdLoc(out var v)) {
inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign).WithILRange(inst)));
}
}
}
#endregion
}
}

4
ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs

@ -53,6 +53,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -53,6 +53,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var v = variableQueue.Dequeue();
if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot)
continue;
// Skip variables that are captured in a mcs yield state-machine
// loads of these will only be visible after DelegateConstruction step.
if (function.StateMachineCompiledWithMono && v.StateMachineField != null)
continue;
if (v.LoadCount != 0 || v.AddressCount != 0)
continue;
foreach (var stloc in v.StoreInstructions.OfType<StLoc>().ToArray()) {

4
ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

@ -62,9 +62,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -62,9 +62,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
// Do not try to transform display class usages or delegate construction.
// DelegateConstruction transform cannot deal with this.
if (DelegateConstruction.IsSimpleDisplayClass(newObjInst.Method.DeclaringType))
if (TransformDisplayClassUsage.IsSimpleDisplayClass(newObjInst.Method.DeclaringType))
return false;
if (DelegateConstruction.IsDelegateConstruction(newObjInst) || DelegateConstruction.IsPotentialClosure(context, newObjInst))
if (DelegateConstruction.IsDelegateConstruction(newObjInst) || TransformDisplayClassUsage.IsPotentialClosure(context, newObjInst))
return false;
// Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression:s
// anon = new { A = 5 } { 3,4,5 } is invalid syntax.

319
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -0,0 +1,319 @@ @@ -0,0 +1,319 @@
// 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.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Transforms closure fields to local variables.
///
/// This is a post-processing step of <see cref="DelegateConstruction"/> and <see cref="TransformExpressionTrees"/>.
/// </summary>
class TransformDisplayClassUsage : ILVisitor, IILTransform
{
class DisplayClass
{
public bool IsMono;
public ILInstruction Initializer;
public ILVariable Variable;
public ITypeDefinition Definition;
public Dictionary<IField, DisplayClassVariable> Variables;
public BlockContainer CaptureScope;
}
struct DisplayClassVariable
{
public ILVariable Variable;
public ILInstruction Value;
}
ILTransformContext context;
ILFunction currentFunction;
readonly Dictionary<ILVariable, DisplayClass> displayClasses = new Dictionary<ILVariable, DisplayClass>();
readonly List<ILInstruction> instructionsToRemove = new List<ILInstruction>();
public void Run(ILFunction function, ILTransformContext context)
{
try {
if (this.context != null || this.currentFunction != null)
throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage));
this.context = context;
var decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
// Traverse nested functions in post-order:
// Inner functions are transformed before outer functions
foreach (var f in function.Descendants.OfType<ILFunction>()) {
foreach (var v in f.Variables.ToArray()) {
if (HandleMonoStateMachine(function, v, decompilationContext, f))
continue;
if (!(v.IsSingleDefinition && v.StoreInstructions.SingleOrDefault() is StLoc inst))
continue;
if (IsClosureInit(inst, out ITypeDefinition closureType)) {
// TODO : figure out whether it is a mono compiled closure, without relying on the type name
bool isMono = f.StateMachineCompiledWithMono || closureType.Name.Contains("AnonStorey");
displayClasses.Add(inst.Variable, new DisplayClass {
IsMono = isMono,
Initializer = inst,
Variable = v,
Definition = closureType,
Variables = new Dictionary<IField, DisplayClassVariable>(),
CaptureScope = isMono && IsMonoNestedCaptureScope(closureType) ? null : inst.Variable.CaptureScope
});
instructionsToRemove.Add(inst);
}
}
foreach (var displayClass in displayClasses.Values.OrderByDescending(d => d.Initializer.StartILOffset).ToArray()) {
context.Step($"Transform references to " + displayClass.Variable, displayClass.Initializer);
this.currentFunction = f;
VisitILFunction(f);
}
}
context.Step($"Remove instructions", function);
foreach (var store in instructionsToRemove) {
if (store.Parent is Block containingBlock)
containingBlock.Instructions.Remove(store);
}
} finally {
instructionsToRemove.Clear();
displayClasses.Clear();
this.context = null;
this.currentFunction = null;
}
}
bool IsOuterClosureReference(IField field)
{
return displayClasses.Values.Any(disp => disp.Definition == field.DeclaringTypeDefinition);
}
bool IsMonoNestedCaptureScope(ITypeDefinition closureType)
{
var decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
return closureType.Fields.Any(f => IsPotentialClosure(decompilationContext.CurrentTypeDefinition, f.ReturnType.GetDefinition()));
}
/// <summary>
/// mcs likes to optimize closures in yield state machines away by moving the captured variables' fields into the state machine type,
/// We construct a <see cref="DisplayClass"/> that spans the whole method body.
/// </summary>
bool HandleMonoStateMachine(ILFunction currentFunction, ILVariable thisVariable, SimpleTypeResolveContext decompilationContext, ILFunction nestedFunction)
{
if (!(nestedFunction.StateMachineCompiledWithMono && thisVariable.IsThis()))
return false;
// Special case for Mono-compiled yield state machines
ITypeDefinition closureType = thisVariable.Type.GetDefinition();
if (!(closureType != decompilationContext.CurrentTypeDefinition
&& IsPotentialClosure(decompilationContext.CurrentTypeDefinition, closureType)))
return false;
var displayClass = new DisplayClass {
IsMono = true,
Initializer = nestedFunction.Body,
Variable = thisVariable,
Definition = thisVariable.Type.GetDefinition(),
Variables = new Dictionary<IField, DisplayClassVariable>(),
CaptureScope = (BlockContainer)nestedFunction.Body
};
displayClasses.Add(thisVariable, displayClass);
foreach (var stateMachineVariable in nestedFunction.Variables) {
if (stateMachineVariable.StateMachineField == null)
continue;
displayClass.Variables.Add(stateMachineVariable.StateMachineField, new DisplayClassVariable {
Variable = stateMachineVariable,
Value = new LdLoc(stateMachineVariable)
});
}
if (!currentFunction.Method.IsStatic && FindThisField(out var thisField)) {
var thisVar = currentFunction.Variables
.FirstOrDefault(t => t.IsThis() && t.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition);
if (thisVar == null) {
thisVar = new ILVariable(VariableKind.Parameter, decompilationContext.CurrentTypeDefinition, -1) { Name = "this" };
currentFunction.Variables.Add(thisVar);
}
displayClass.Variables.Add(thisField, new DisplayClassVariable { Variable = thisVar, Value = new LdLoc(thisVar) });
}
return true;
bool FindThisField(out IField foundField)
{
foundField = null;
foreach (var field in closureType.GetFields(f2 => !f2.IsStatic && !displayClass.Variables.ContainsKey(f2) && f2.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition)) {
thisField = field;
return true;
}
return false;
}
}
internal static bool IsSimpleDisplayClass(IType type)
{
if (!type.HasGeneratedName() || (!type.Name.Contains("DisplayClass") && !type.Name.Contains("AnonStorey")))
return false;
if (type.DirectBaseTypes.Any(t => !t.IsKnownType(KnownTypeCode.Object)))
return false;
return true;
}
internal static bool IsPotentialClosure(ILTransformContext context, NewObj inst)
{
var decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition);
}
internal static bool IsPotentialClosure(ITypeDefinition decompiledTypeDefinition, ITypeDefinition potentialDisplayClass)
{
if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false;
while (potentialDisplayClass != decompiledTypeDefinition) {
potentialDisplayClass = potentialDisplayClass.DeclaringTypeDefinition;
if (potentialDisplayClass == null)
return false;
}
return true;
}
protected override void Default(ILInstruction inst)
{
foreach (var child in inst.Children) {
child.AcceptVisitor(this);
}
}
protected internal override void VisitStLoc(StLoc inst)
{
base.VisitStLoc(inst);
// Sometimes display class references are copied into other local variables.
// We remove the assignment and store the relationship between the display class and the variable in the
// displayClasses dictionary.
if (inst.Value.MatchLdLoc(out var closureVariable) && displayClasses.TryGetValue(closureVariable, out var displayClass)) {
displayClasses[inst.Variable] = displayClass;
instructionsToRemove.Add(inst);
}
}
bool IsClosureInit(StLoc inst, out ITypeDefinition closureType)
{
closureType = null;
if (!(inst.Value is NewObj newObj))
return false;
closureType = newObj.Method.DeclaringTypeDefinition;
return closureType != null && IsPotentialClosure(this.context, newObj);
}
protected internal override void VisitStObj(StObj inst)
{
base.VisitStObj(inst);
// This instruction has been marked deletable, do not transform it further
if (instructionsToRemove.Contains(inst))
return;
// The target of the store instruction must be a field reference
if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field))
return;
// Get display class info
if (!(target is LdLoc displayClassLoad && displayClasses.TryGetValue(displayClassLoad.Variable, out var displayClass)))
return;
field = (IField)field.MemberDefinition;
if (displayClass.Variables.TryGetValue(field, out DisplayClassVariable info)) {
// If the display class field was previously initialized, we use a simple assignment.
inst.ReplaceWith(new StLoc(info.Variable, inst.Value).WithILRange(inst));
} else {
// This is an uninitialized variable:
ILInstruction value;
if (inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter && currentFunction == v.Function) {
// Special case for parameters: remove copies of parameter values.
instructionsToRemove.Add(inst);
value = inst.Value;
} else {
Debug.Assert(displayClass.Definition == field.DeclaringTypeDefinition);
// Introduce a fresh variable for the display class field.
v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name);
if (displayClass.IsMono && displayClass.CaptureScope == null && !IsOuterClosureReference(field)) {
displayClass.CaptureScope = BlockContainer.FindClosestContainer(inst);
}
v.CaptureScope = displayClass.CaptureScope;
inst.ReplaceWith(new StLoc(v, inst.Value).WithILRange(inst));
value = new LdLoc(v);
}
displayClass.Variables.Add(field, new DisplayClassVariable { Value = value, Variable = v });
}
}
protected internal override void VisitLdObj(LdObj inst)
{
base.VisitLdObj(inst);
// The target of the store instruction must be a field reference
if (!inst.Target.MatchLdFlda(out var target, out IField field))
return;
// Get display class info
if (!(target is LdLoc displayClassLoad && displayClasses.TryGetValue(displayClassLoad.Variable, out var displayClass)))
return;
// Get display class variable info
if (!displayClass.Variables.TryGetValue((IField)field.MemberDefinition, out DisplayClassVariable info))
return;
// Replace usage of display class field with the variable.
var replacement = info.Value.Clone();
replacement.SetILRange(inst);
inst.ReplaceWith(replacement);
}
protected internal override void VisitLdFlda(LdFlda inst)
{
base.VisitLdFlda(inst);
// TODO : Figure out why this was added in https://github.com/icsharpcode/ILSpy/pull/1303
if (inst.Target.MatchLdThis() && inst.Field.Name == "$this"
&& inst.Field.MemberDefinition.ReflectionName.Contains("c__Iterator")) {
//Debug.Assert(false, "This should not be executed!");
var variable = currentFunction.Variables.First((f) => f.Index == -1);
inst.ReplaceWith(new LdLoca(variable).WithILRange(inst));
}
// Skip stfld/ldfld
if (inst.Parent is LdObj || inst.Parent is StObj)
return;
// Get display class info
if (!(inst.Target is LdLoc displayClassLoad && displayClasses.TryGetValue(displayClassLoad.Variable, out var displayClass)))
return;
var field = (IField)inst.Field.MemberDefinition;
if (!displayClass.Variables.TryGetValue(field, out DisplayClassVariable info)) {
// Introduce a fresh variable for the display class field.
Debug.Assert(displayClass.Definition == field.DeclaringTypeDefinition);
var v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name);
v.CaptureScope = displayClass.CaptureScope;
inst.ReplaceWith(new LdLoca(v).WithILRange(inst));
displayClass.Variables.Add(field, new DisplayClassVariable { Value = new LdLoc(v), Variable = v });
} else if (info.Value is LdLoc l) {
inst.ReplaceWith(new LdLoca(l.Variable).WithILRange(inst));
} else {
Debug.Fail("LdFlda pattern not supported!");
}
}
protected internal override void VisitNumericCompoundAssign(NumericCompoundAssign inst)
{
base.VisitNumericCompoundAssign(inst);
// NumericCompoundAssign is only valid when used with fields: -> replace it with a BinaryNumericInstruction.
if (inst.Target.MatchLdLoc(out var v)) {
inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign).WithILRange(inst)));
}
}
}
}

18
ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

@ -21,7 +21,6 @@ using System.Collections.Generic; @@ -21,7 +21,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -1071,6 +1070,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1071,6 +1070,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
&& v.StackType.IsIntegerType())
return new LdLoca(v);
return null;
} else if (IsClosureReference(ldloc.Variable)) {
if (ldloc.Variable.Kind == VariableKind.Local) {
ldloc.Variable.Kind = VariableKind.DisplayClassLocal;
}
if (ldloc.Variable.CaptureScope == null) {
ldloc.Variable.CaptureScope = BlockContainer.FindClosestContainer(context);
}
return ldloc;
} else {
return ldloc;
}
@ -1079,6 +1086,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1079,6 +1086,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
bool IsClosureReference(ILVariable variable)
{
if (!variable.IsSingleDefinition || !(variable.StoreInstructions.SingleOrDefault() is StLoc store))
return false;
if (!(store.Value is NewObj newObj))
return false;
return TransformDisplayClassUsage.IsPotentialClosure(this.context, newObj);
}
bool IsExpressionTreeParameter(ILVariable variable)
{
return variable.Type.FullName == "System.Linq.Expressions.ParameterExpression";

Loading…
Cancel
Save