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); }
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(<target-expression>, ldftn <method>)</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(); } } }