diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 5863da05e..bb85153bf 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -156,6 +156,7 @@ namespace ICSharpCode.Decompiler.CSharp }, new ProxyCallReplacer(), new DelegateConstruction(), + new TransformDisplayClassUsage(), new HighLevelLoopTransform(), new ReduceNestingTransform(), new IntroduceDynamicTypeOnLocals(), diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 8ab2fbd69..3fca3af6e 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -272,6 +272,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index aa4148c33..021f34767 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -37,7 +37,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; this.context = context; this.decompilationContext = new SimpleTypeResolveContext(function.Method); - var orphanedVariableInits = new List(); var targetsToReplace = new List(); var translatedDisplayClasses = new HashSet(); var cancellationToken = context.CancellationToken; @@ -50,48 +49,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (instWithVar.Variable.Kind == VariableKind.Local) { instWithVar.Variable.Kind = VariableKind.DisplayClassLocal; } + 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); + } + } + if (displayClassTypeDef != null) + translatedDisplayClasses.Add(displayClassTypeDef); targetsToReplace.Add(instWithVar); } 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) { @@ -101,12 +73,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 +81,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 +97,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 +213,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms base.VisitLdObj(inst); } } - - /// - /// 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. - /// - class TransformDisplayClassUsages : ILVisitor - { - ILFunction currentFunction; - BlockContainer captureScope; - readonly IInstructionWithVariableOperand targetLoad; - readonly List targetAndCopies = new List(); - readonly List orphanedVariableInits; - readonly HashSet translatedDisplayClasses; - readonly Dictionary initValues = new Dictionary(); - - struct DisplayClassVariable - { - public ILVariable variable; - public ILInstruction value; - } - - public TransformDisplayClassUsages(ILFunction function, IInstructionWithVariableOperand targetLoad, BlockContainer captureScope, List orphanedVariableInits, HashSet 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 } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs index c982f5f35..14e939cfd 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -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. diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs new file mode 100644 index 000000000..5e26c5a1b --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Transforms closure fields to local variables. + /// + /// This is a post-processing step of and . + /// + class TransformDisplayClassUsage : ILVisitor, IILTransform + { + class DisplayClass + { + public ILInstruction Initializer; + public ILVariable Variable; + public ITypeDefinition Definition; + public Dictionary Variables; + public BlockContainer CaptureScope; + } + + struct DisplayClassVariable + { + public ILVariable Variable; + public ILInstruction Value; + } + + ILTransformContext context; + ILFunction currentFunction; + readonly Dictionary displayClasses = new Dictionary(); + readonly List orphanedVariableInits = new List(); + + 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; + // Traverse nested functions in post-order: + // Inner functions are transformed before outer functions + foreach (var f in function.Descendants.OfType()) { + foreach (var v in f.Variables) { + if (!(v.IsSingleDefinition && v.StoreInstructions.SingleOrDefault() is StLoc inst)) + continue; + if (IsClosureInit(inst, out var closureType)) { + displayClasses.Add(inst.Variable, new DisplayClass { + Initializer = inst, + Variable = v, + Definition = closureType, + Variables = new Dictionary(), + CaptureScope = inst.Variable.CaptureScope + }); + orphanedVariableInits.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 orphanedVariableInits", function); + foreach (var store in orphanedVariableInits) { + if (store.Parent is Block containingBlock) + containingBlock.Instructions.Remove(store); + } + } finally { + orphanedVariableInits.Clear(); + displayClasses.Clear(); + this.context = null; + this.currentFunction = null; + } + } + + 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; + orphanedVariableInits.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 (orphanedVariableInits.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; + // Skip assignments that reference fields of the outer class, this is not a closure assignment. + if (target.MatchLdThis()) + 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. + orphanedVariableInits.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); + 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")) { + 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))); + } + } + + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 89c92bb69..b3ac3e596 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -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 && 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 } } + 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";