Example #1
0
 private ILVariable ResolveAncestorScopeReference(ILInstruction inst)
 {
     if (!inst.MatchLdFld(out var target, out var field))
     {
         return(null);
     }
     if (field.Type.Kind != TypeKind.Class)
     {
         return(null);
     }
     if (!(TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition()) || context.Function.Method.DeclaringType.Equals(field.Type)))
     {
         return(null);
     }
     foreach (var v in context.Function.Descendants.OfType <ILFunction>().SelectMany(f => f.Variables))
     {
         if (v.Kind != VariableKind.Local && v.Kind != VariableKind.DisplayClassLocal && v.Kind != VariableKind.StackSlot)
         {
             if (!(v.Kind == VariableKind.Parameter && v.Index == -1))
             {
                 continue;
             }
             if (v.Type.Equals(field.Type))
             {
                 return(v);
             }
         }
         if (!(TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type)))
         {
             continue;
         }
         return(v);
     }
     return(null);
 }
Example #2
0
 static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod method)
 {
     if (method == null)
     {
         return(false);
     }
     if (!(method.HasGeneratedName() ||
           method.Name.Contains("$") ||
           method.IsCompilerGeneratedOrIsInCompilerGeneratedClass() ||
           TransformDisplayClassUsage.IsPotentialClosure(decompiledTypeDefinition, method.DeclaringTypeDefinition) ||
           ContainsAnonymousType(method)))
     {
         return(false);
     }
     return(true);
 }
        bool DoTransform(Block body, int pos)
        {
            ILInstruction inst = body.Instructions[pos];

            // Match stloc(v, newobj)
            if (inst.MatchStLoc(out var v, out var initInst) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot))
            {
                IType instType;
                switch (initInst)
                {
                case NewObj newObjInst:
                    if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor && !context.Function.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
                    {
                        // on statement level (no other expressions on IL stack),
                        // prefer to keep local variables (but not stack slots),
                        // unless we are in a constructor (where inlining object initializers might be critical
                        // for the base ctor call) or a compiler-generated delegate method, which might be used in a query expression.
                        return(false);
                    }
                    // Do not try to transform delegate construction.
                    // DelegateConstruction transform cannot deal with this.
                    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.
                    if (newObjInst.Method.DeclaringType.ContainsAnonymousType())
                    {
                        return(false);
                    }
                    instType = newObjInst.Method.DeclaringType;
                    break;

                case DefaultValue defaultVal:
                    if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor)
                    {
                        // on statement level (no other expressions on IL stack),
                        // prefer to keep local variables (but not stack slots),
                        // unless we are in a constructor (where inlining object initializers might be critical
                        // for the base ctor call)
                        return(false);
                    }
                    instType = defaultVal.Type;
                    break;

                default:
                    return(false);
                }
                int initializerItemsCount = 0;
                var blockKind             = BlockKind.CollectionInitializer;
                possibleIndexVariables = new Dictionary <ILVariable, (int Index, ILInstruction Value)>();
                currentPath            = new List <AccessPathElement>();
                isCollection           = false;
                pathStack = new Stack <HashSet <AccessPathElement> >();
                pathStack.Push(new HashSet <AccessPathElement>());
                // Detect initializer type by scanning the following statements
                // each must be a callvirt with ldloc v as first argument
                // if the method is a setter we're dealing with an object initializer
                // if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
                while (pos + initializerItemsCount + 1 < body.Instructions.Count &&
                       IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind))
                {
                    initializerItemsCount++;
                }
                // Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable
                // directly after the possible initializer.
                if (IsMethodCallOnVariable(body.Instructions[pos + initializerItemsCount + 1], v))
                {
                    return(false);
                }
                // Calculate the correct number of statements inside the initializer:
                // All index variables that were used in the initializer have Index set to -1.
                // We fetch the first unused variable from the list and remove all instructions after its
                // first usage (i.e. the init store) from the initializer.
                var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index);
                if (index != null)
                {
                    initializerItemsCount = index.Value - pos - 1;
                }
                // The initializer would be empty, there's nothing to do here.
                if (initializerItemsCount <= 0)
                {
                    return(false);
                }
                context.Step("CollectionOrObjectInitializer", inst);
                // Create a new block and final slot (initializer target variable)
                var        initializerBlock = new Block(blockKind);
                ILVariable finalSlot        = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                initializerBlock.FinalInstruction = new LdLoc(finalSlot);
                initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone()));
                // Move all instructions to the initializer block.
                for (int i = 1; i <= initializerItemsCount; i++)
                {
                    switch (body.Instructions[i + pos])
                    {
                    case CallInstruction call:
                        if (!(call is CallVirt || call is Call))
                        {
                            continue;
                        }
                        var newCall   = call;
                        var newTarget = newCall.Arguments[0];
                        foreach (var load in newTarget.Descendants.OfType <IInstructionWithVariableOperand>())
                        {
                            if ((load is LdLoc || load is LdLoca) && load.Variable == v)
                            {
                                load.Variable = finalSlot;
                            }
                        }
                        initializerBlock.Instructions.Add(newCall);
                        break;

                    case StObj stObj:
                        var newStObj = stObj;
                        foreach (var load in newStObj.Target.Descendants.OfType <IInstructionWithVariableOperand>())
                        {
                            if ((load is LdLoc || load is LdLoca) && load.Variable == v)
                            {
                                load.Variable = finalSlot;
                            }
                        }
                        initializerBlock.Instructions.Add(newStObj);
                        break;

                    case StLoc stLoc:
                        var newStLoc = stLoc;
                        initializerBlock.Instructions.Add(newStLoc);
                        break;
                    }
                }
                initInst.ReplaceWith(initializerBlock);
                body.Instructions.RemoveRange(pos + 1, initializerItemsCount);
                ILInlining.InlineIfPossible(body, pos, context);
            }
            return(true);
        }
        /// <summary>
        /// The transform works like this:
        ///
        /// <para>
        /// local functions can either be used in method calls, i.e., call and callvirt instructions,
        /// or can be used as part of the "delegate construction" pattern, i.e.,
        /// <c>newobj Delegate(&lt;target-expression&gt;, ldftn &lt;method&gt;)</c>.
        /// </para>
        /// As local functions can be declared practically anywhere, we have to take a look at
        /// all use-sites and infer the declaration location from that. Use-sites can be call,
        /// callvirt and ldftn instructions.
        /// After all use-sites are collected we construct the ILAst of the local function
        /// and add it to the parent function.
        /// Then all use-sites of the local-function are transformed to a call to the
        /// <c>LocalFunctionMethod</c> or a ldftn of the <c>LocalFunctionMethod</c>.
        /// In a next step we handle all nested local functions.
        /// After all local functions are transformed, we move all local functions that capture
        /// any variables to their respective declaration scope.
        /// </summary>
        public void Run(ILFunction function, ILTransformContext context)
        {
            if (!context.Settings.LocalFunctions)
            {
                return;
            }
            // Disable the transform if we are decompiling a display-class or local function method:
            // This happens if a local function or display class is selected in the ILSpy tree view.
            if (IsLocalFunctionMethod(function.Method, context) || IsLocalFunctionDisplayClass(function.Method.ParentModule.PEFile, (TypeDefinitionHandle)function.Method.DeclaringTypeDefinition.MetadataToken, context))
            {
                return;
            }
            this.context        = context;
            this.resolveContext = new SimpleTypeResolveContext(function.Method);
            var localFunctions    = new Dictionary <MethodDefinitionHandle, LocalFunctionInfo>();
            var cancellationToken = context.CancellationToken;

            // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas.
            FindUseSites(function, context, localFunctions);
            foreach (var(_, info) in localFunctions)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (info.Definition == null)
                {
                    function.Warnings.Add($"Could not decode local function '{info.Method}'");
                    continue;
                }

                context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition);
                try {
                    var localFunction = info.Definition;
                    if (!localFunction.Method.IsStatic)
                    {
                        var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis);
                        var target  = info.UseSites.Where(us => us.Arguments[0].MatchLdLoc(out _)).FirstOrDefault()?.Arguments[0];
                        if (target == null)
                        {
                            target = info.UseSites[0].Arguments[0];
                            if (target.MatchLdFld(out var target1, out var field) && thisVar.Type.Equals(field.Type) && field.Type.Kind == TypeKind.Class && TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition()))
                            {
                                var variable = function.Descendants.OfType <ILFunction>().SelectMany(f => f.Variables).Where(v => !v.IsThis() && TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type)).OnlyOrDefault();
                                if (variable != null)
                                {
                                    target = new LdLoc(variable);
                                    HandleArgument(localFunction, 1, 0, target);
                                }
                            }
                        }
                        context.Step($"Replace 'this' with {target}", localFunction);
                        localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar));
                    }

                    foreach (var useSite in info.UseSites)
                    {
                        DetermineCaptureAndDeclarationScope(localFunction, useSite);

                        if (function.Method.IsConstructor && localFunction.DeclarationScope == null)
                        {
                            localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite);
                        }
                    }

                    if (localFunction.DeclarationScope == null)
                    {
                        localFunction.DeclarationScope = (BlockContainer)function.Body;
                    }

                    ILFunction declaringFunction = GetDeclaringFunction(localFunction);
                    if (declaringFunction != function)
                    {
                        function.LocalFunctions.Remove(localFunction);
                        declaringFunction.LocalFunctions.Add(localFunction);
                    }

                    if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters)
                    {
                        Debug.Assert(false);
                        function.Warnings.Add($"Could not decode local function '{info.Method}'");
                        if (declaringFunction != function)
                        {
                            declaringFunction.LocalFunctions.Remove(localFunction);
                        }
                        continue;
                    }

                    foreach (var useSite in info.UseSites)
                    {
                        context.Step($"Transform use site at IL_{useSite.StartILOffset:x4}", useSite);
                        if (useSite.OpCode == OpCode.NewObj)
                        {
                            TransformToLocalFunctionReference(localFunction, useSite);
                        }
                        else
                        {
                            TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite);
                        }
                    }
                } finally {
                    context.StepEndGroup();
                }
            }
        }