/// <summary> /// Extracts the specified instruction: /// The instruction is replaced with a load of a new temporary variable; /// and the instruction is moved to a store to said variable at block-level. /// /// May return null if extraction is not possible. /// </summary> public static ILVariable Extract(ILInstruction instToExtract) { var function = instToExtract.Ancestors.OfType <ILFunction>().First(); ExtractionContext ctx = new ExtractionContext(function); ctx.FlagsBeingMoved = instToExtract.Flags; ILInstruction inst = instToExtract; while (inst != null) { if (inst.Parent is Block block && block.Kind == BlockKind.ControlFlow) { // We've reached the target block, and extraction is possible all the way. int insertIndex = inst.ChildIndex; // Move instToExtract itself: var v = function.RegisterVariable(VariableKind.StackSlot, instToExtract.ResultType); instToExtract.ReplaceWith(new LdLoc(v)); block.Instructions.Insert(insertIndex, new StLoc(v, instToExtract)); // Apply the other move actions: foreach (var moveAction in ctx.MoveActions) { block.Instructions.Insert(insertIndex, moveAction()); } return(v); } if (!inst.Parent.PrepareExtract(inst.ChildIndex, ctx)) { return(null); } inst = inst.Parent; } return(null); }
internal void RegisterMove(ILInstruction predecessor) { FlagsBeingMoved |= predecessor.Flags; MoveActions.Add(delegate { var v = Function.RegisterVariable(VariableKind.StackSlot, predecessor.ResultType); predecessor.ReplaceWith(new LdLoc(v)); return(new StLoc(v, predecessor)); }); }
void HandleExit(ILInstruction inst) { if (currentExit == ExitNotYetDetermined && CanIntroduceAsExit(inst)) { currentExit = inst; inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange }); } else if (CompatibleExitInstruction(inst, currentExit)) { inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange }); } }
internal void RegisterMove(ILInstruction predecessor) { FlagsBeingMoved |= predecessor.Flags; MoveActions.Add(delegate { var type = context.TypeSystem.FindType(predecessor.ResultType.ToKnownTypeCode()); var v = Function.RegisterVariable(VariableKind.StackSlot, type); predecessor.ReplaceWith(new LdLoc(v)); return(new StLoc(v, predecessor)); }); }
/// <summary> /// Get index of deconstruction result or tuple element /// Returns -1 on failure. /// </summary> int FindIndex(ILInstruction inst, out Action <DeconstructInstruction> delayedActions) { delayedActions = null; if (inst.MatchLdLoc(out var v)) { if (!deconstructionResultsLookup.TryGetValue(v, out int index)) { return(-1); } return(index); } if (inst.MatchLdFld(out _, out _)) { if (!TupleTransform.MatchTupleFieldAccess((LdFlda)((LdObj)inst).Target, out var tupleType, out var target, out int index)) { return(-1); } // Item fields are one-based, we use zero-based indexing. index--; // normalize tuple type tupleType = TupleType.FromUnderlyingType(context.TypeSystem, tupleType); if (!target.MatchLdLoca(out v)) { return(-1); } if (this.tupleVariable == null) { this.tupleVariable = v; this.tupleType = (TupleType)tupleType; this.deconstructionResults = new ILVariable[this.tupleType.Cardinality]; } if (this.tupleType.Cardinality < 2) { return(-1); } if (v != tupleVariable || !this.tupleType.Equals(tupleType)) { return(-1); } if (this.deconstructionResults[index] == null) { var freshVar = new ILVariable(VariableKind.StackSlot, this.tupleType.ElementTypes[index]) { Name = "E_" + index }; delayedActions += _ => context.Function.Variables.Add(freshVar); this.deconstructionResults[index] = freshVar; } delayedActions += _ => { inst.ReplaceWith(new LdLoc(this.deconstructionResults[index])); }; return(index); } return(-1); }
void HandleExit(ILInstruction inst) { if (currentExit == ExitNotYetDetermined && CanIntroduceAsExit(inst)) { potentialExits.Add(inst); } else if (CompatibleExitInstruction(inst, currentExit)) { inst.ReplaceWith(new Leave(currentContainer).WithILRange(inst)); } }
public void HandleExit(ILInstruction inst) { if (this.CurrentExit == ExitNotYetDetermined && this.Container.LeaveCount == 0) { this.PotentialExits !.Add(inst); } else if (CompatibleExitInstruction(inst, this.CurrentExit)) { inst.ReplaceWith(new Leave(this.Container).WithILRange(inst)); } }
/// <summary> /// Extracts the specified instruction: /// The instruction is replaced with a load of a new temporary variable; /// and the instruction is moved to a store to said variable at block-level. /// /// May return null if extraction is not possible. /// </summary> public static ILVariable Extract(ILInstruction instToExtract, ILTransformContext context) { var function = instToExtract.Ancestors.OfType <ILFunction>().First(); ExtractionContext ctx = new ExtractionContext(function, context); ctx.FlagsBeingMoved = instToExtract.Flags; ILInstruction inst = instToExtract; while (inst != null) { if (inst.Parent is IfInstruction ifInst && inst.SlotInfo != IfInstruction.ConditionSlot) { // this context doesn't support extraction, but maybe we can create a block here? if (ifInst.ResultType == StackType.Void) { Block newBlock = new Block(); inst.ReplaceWith(newBlock); newBlock.Instructions.Add(inst); } } if (inst.Parent is Block block && block.Kind == BlockKind.ControlFlow) { // We've reached the target block, and extraction is possible all the way. int insertIndex = inst.ChildIndex; var type = context.TypeSystem.FindType(instToExtract.ResultType); // Move instToExtract itself: var v = function.RegisterVariable(VariableKind.StackSlot, type); instToExtract.ReplaceWith(new LdLoc(v)); block.Instructions.Insert(insertIndex, new StLoc(v, instToExtract)); // Apply the other move actions: foreach (var moveAction in ctx.MoveActions) { block.Instructions.Insert(insertIndex, moveAction()); } return(v); } if (!inst.Parent.PrepareExtract(inst.ChildIndex, ctx)) { return(null); } inst = inst.Parent; } return(null); }
Statement TransformToForeach(UsingInstruction inst, out Expression resource) { // Check if the using resource matches the GetEnumerator pattern. resource = exprBuilder.Translate(inst.ResourceExpression); var m = getEnumeratorPattern.Match(resource); // The using body must be a BlockContainer. if (!(inst.Body is BlockContainer container) || !m.Success) { return(null); } // The using-variable is the enumerator. var enumeratorVar = inst.Variable; // If there's another BlockContainer nested in this container and it only has one child block, unwrap it. // If there's an extra leave inside the block, extract it into optionalReturnAfterLoop. var loopContainer = UnwrapNestedContainerIfPossible(container, out var optionalReturnAfterLoop); // Detect whether we're dealing with a while loop with multiple embedded statements. var loop = DetectedLoop.DetectLoop(loopContainer); if (loop.Kind != LoopKind.While || !(loop.Body is Block body)) { return(null); } // The loop condition must be a call to enumerator.MoveNext() var condition = exprBuilder.TranslateCondition(loop.Conditions.Single()); var m2 = moveNextConditionPattern.Match(condition.Expression); if (!m2.Success) { return(null); } // Check enumerator variable references. var enumeratorVar2 = m2.Get <IdentifierExpression>("enumerator").Single().GetILVariable(); if (enumeratorVar2 != enumeratorVar) { return(null); } // Detect which foreach-variable transformation is necessary/possible. var transformation = DetectGetCurrentTransformation(container, body, enumeratorVar, condition.ILInstructions.Single(), out var singleGetter, out var foreachVariable); if (transformation == RequiredGetCurrentTransformation.NoForeach) { return(null); } // The existing foreach variable, if found, can only be used in the loop container. if (foreachVariable != null && !(foreachVariable.CaptureScope == null || foreachVariable.CaptureScope == loopContainer)) { return(null); } // Extract in-expression var collectionExpr = m.Get <Expression>("collection").Single(); // Special case: foreach (var item in this) is decompiled as foreach (var item in base) // but a base reference is not valid in this context. if (collectionExpr is BaseReferenceExpression) { collectionExpr = new ThisReferenceExpression().CopyAnnotationsFrom(collectionExpr); } // Handle explicit casts: // This is the case if an explicit type different from the collection-item-type was used. // For example: foreach (ClassA item in nonGenericEnumerable) var type = singleGetter.Method.ReturnType; ILInstruction instToReplace = singleGetter; switch (instToReplace.Parent) { case CastClass cc: type = cc.Type; instToReplace = cc; break; case UnboxAny ua: type = ua.Type; instToReplace = ua; break; } // Handle the required foreach-variable transformation: switch (transformation) { case RequiredGetCurrentTransformation.UseExistingVariable: foreachVariable.Type = type; foreachVariable.Kind = VariableKind.ForeachLocal; foreachVariable.Name = AssignVariableNames.GenerateForeachVariableName(currentFunction, collectionExpr.Annotation <ILInstruction>(), foreachVariable); break; case RequiredGetCurrentTransformation.UninlineAndUseExistingVariable: // Unwrap stloc chain. var nestedStores = new Stack <ILVariable>(); var currentInst = instToReplace; // instToReplace is the innermost value of the stloc chain. while (currentInst.Parent is StLoc stloc) { // Exclude nested stores to foreachVariable // we'll insert one store at the beginning of the block. if (stloc.Variable != foreachVariable && stloc.Parent is StLoc) { nestedStores.Push(stloc.Variable); } currentInst = stloc; } // Rebuild the nested store instructions: ILInstruction reorderedStores = new LdLoc(foreachVariable); while (nestedStores.Count > 0) { reorderedStores = new StLoc(nestedStores.Pop(), reorderedStores); } currentInst.ReplaceWith(reorderedStores); body.Instructions.Insert(0, new StLoc(foreachVariable, instToReplace)); // Adjust variable type, kind and name. goto case RequiredGetCurrentTransformation.UseExistingVariable; case RequiredGetCurrentTransformation.IntroduceNewVariable: foreachVariable = currentFunction.RegisterVariable( VariableKind.ForeachLocal, type, AssignVariableNames.GenerateForeachVariableName(currentFunction, collectionExpr.Annotation <ILInstruction>()) ); instToReplace.ReplaceWith(new LdLoc(foreachVariable)); body.Instructions.Insert(0, new StLoc(foreachVariable, instToReplace)); break; } // Convert the modified body to C# AST: var whileLoop = (WhileStatement)ConvertAsBlock(container).First(); BlockStatement foreachBody = (BlockStatement)whileLoop.EmbeddedStatement.Detach(); // Remove the first statement, as it is the foreachVariable = enumerator.Current; statement. foreachBody.Statements.First().Detach(); // Construct the foreach loop. var foreachStmt = new ForeachStatement { VariableType = settings.AnonymousTypes && foreachVariable.Type.ContainsAnonymousType() ? new SimpleType("var") : exprBuilder.ConvertType(foreachVariable.Type), VariableName = foreachVariable.Name, InExpression = collectionExpr.Detach(), EmbeddedStatement = foreachBody }; // Add the variable annotation for highlighting (TokenTextWriter expects it directly on the ForeachStatement). foreachStmt.AddAnnotation(new ILVariableResolveResult(foreachVariable, foreachVariable.Type)); // If there was an optional return statement, return it as well. if (optionalReturnAfterLoop != null) { return(new BlockStatement { Statements = { foreachStmt, optionalReturnAfterLoop.AcceptVisitor(this) } }); } return(foreachStmt); }
ILFunction TransformDelegateConstruction( ILInstruction value, IMethod targetMethod, ILInstruction target, IType delegateType) { if (!IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod)) { return(null); } if (targetMethod.MetadataToken.IsNil) { return(null); } if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod, context)) { return(null); } if (!ValidateDelegateTarget(target)) { return(null); } var handle = (MethodDefinitionHandle)targetMethod.MetadataToken; if (activeMethods.Contains(handle)) { this.context.Function.Warnings.Add(" Found self-referencing delegate construction. Abort transformation to avoid stack overflow."); return(null); } var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); if (!methodDefinition.HasBody()) { return(null); } var genericContext = 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.Delegate, context.CancellationToken); function.DelegateType = delegateType; // Embed the lambda into the parent function's ILAst, so that "Show steps" can show // how the lambda body is being transformed. value.ReplaceWith(function); function.CheckInvariant(ILPhase.Normal); var contextPrefix = targetMethod.Name; foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) { v.Name = contextPrefix + v.Name; } var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)).Concat(GetTransforms()), nestedContext); nestedContext.Step("DelegateConstruction (ReplaceDelegateTargetVisitor)", function); function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(VariableKindExtensions.IsThis))); // handle nested lambdas nestedContext.StepStartGroup("DelegateConstruction (nested lambdas)", function); ((IILTransform)this).Run(function, nestedContext); nestedContext.StepEndGroup(); function.AddILRange(target); function.AddILRange(value); if (value is Call call) { function.AddILRange(call.Arguments[1]); } return(function); }
Statement TransformToForeach(UsingInstruction inst, out Expression resource) { resource = exprBuilder.Translate(inst.ResourceExpression); var m = getEnumeratorPattern.Match(resource); if (!(inst.Body is BlockContainer container) || !m.Success) { return(null); } var enumeratorVar = inst.Variable; var loopContainer = UnwrapNestedContainerIfPossible(container, out var optionalReturnAfterLoop); var loop = DetectedLoop.DetectLoop(loopContainer); if (loop.Kind != LoopKind.While || !(loop.Body is Block body)) { return(null); } var condition = exprBuilder.TranslateCondition(loop.Conditions.Single()); var m2 = moveNextConditionPattern.Match(condition.Expression); if (!m2.Success) { return(null); } var enumeratorVar2 = m2.Get <IdentifierExpression>("enumerator").Single().GetILVariable(); if (enumeratorVar2 != enumeratorVar || !BodyHasSingleGetCurrent(body, enumeratorVar, condition.ILInstructions.Single(), out var singleGetter, out var needsUninlining, out var itemVariable)) { return(null); } if (itemVariable != null && !(itemVariable.CaptureScope == null || itemVariable.CaptureScope == loopContainer)) { return(null); } var collectionExpr = m.Get <Expression>("collection").Single(); if (collectionExpr is BaseReferenceExpression) { collectionExpr = new ThisReferenceExpression().CopyAnnotationsFrom(collectionExpr); } var type = singleGetter.Method.ReturnType; ILInstruction instToReplace = singleGetter; switch (instToReplace.Parent) { case CastClass cc: type = cc.Type; instToReplace = cc; break; case UnboxAny ua: type = ua.Type; instToReplace = ua; break; } if (needsUninlining) { itemVariable = currentFunction.RegisterVariable( VariableKind.ForeachLocal, type, AssignVariableNames.GenerateForeachVariableName(currentFunction, collectionExpr.Annotation <ILInstruction>()) ); instToReplace.ReplaceWith(new LdLoc(itemVariable)); body.Instructions.Insert(0, new StLoc(itemVariable, instToReplace)); } else { if (itemVariable.StoreCount != 1) { return(null); } itemVariable.Type = type; itemVariable.Kind = VariableKind.ForeachLocal; itemVariable.Name = AssignVariableNames.GenerateForeachVariableName(currentFunction, collectionExpr.Annotation <ILInstruction>(), itemVariable); } var whileLoop = (WhileStatement)ConvertAsBlock(container).First(); BlockStatement foreachBody = (BlockStatement)whileLoop.EmbeddedStatement.Detach(); foreachBody.Statements.First().Detach(); var foreachStmt = new ForeachStatement { VariableType = settings.AnonymousTypes && itemVariable.Type.ContainsAnonymousType() ? new SimpleType("var") : exprBuilder.ConvertType(itemVariable.Type), VariableName = itemVariable.Name, InExpression = collectionExpr.Detach(), EmbeddedStatement = foreachBody }; foreachStmt.AddAnnotation(new ILVariableResolveResult(itemVariable, itemVariable.Type)); if (optionalReturnAfterLoop != null) { return(new BlockStatement { Statements = { foreachStmt, optionalReturnAfterLoop.AcceptVisitor(this) } }); } return(foreachStmt); }
/// <summary> /// Extracts the specified instruction: /// The instruction is replaced with a load of a new temporary variable; /// and the instruction is moved to a store to said variable at block-level. /// /// May return null if extraction is not possible. /// </summary> public static ILVariable Extract(ILInstruction instToExtract, ILTransformContext context) { var function = instToExtract.Ancestors.OfType <ILFunction>().First(); ExtractionContext ctx = new ExtractionContext(function, context); ctx.FlagsBeingMoved = instToExtract.Flags; ILInstruction inst = instToExtract; while (inst != null) { if (inst.Parent is IfInstruction ifInst && inst.SlotInfo != IfInstruction.ConditionSlot) { // this context doesn't support extraction, but maybe we can create a block here? if (ifInst.ResultType == StackType.Void) { Block newBlock = new Block(); inst.ReplaceWith(newBlock); newBlock.Instructions.Add(inst); } } if (inst.Parent is Block { Kind : BlockKind.ControlFlow } block) { // We've reached a target block, and extraction is possible all the way. // Check if the parent BlockContainer allows extraction: if (block.Parent is BlockContainer container) { switch (container.Kind) { case ContainerKind.Normal : case ContainerKind.Loop : // extraction is always possible break; case ContainerKind.Switch: // extraction is possible, unless in the entry-point (i.e., the switch head) if (block == container.EntryPoint && inst.ChildIndex == 0) { // try to extract to the container's parent block, if it's a valid location inst = container; continue; } break; case ContainerKind.While: // extraction is possible, unless in the entry-point (i.e., the condition block) if (block == container.EntryPoint) { return(null); } break; case ContainerKind.DoWhile: // extraction is possible, unless in the last block (i.e., the condition block) if (block == container.Blocks.Last()) { return(null); } break; case ContainerKind.For: // extraction is possible, unless in the first or last block // (i.e., the condition block or increment block) if (block == container.EntryPoint || block == container.Blocks.Last()) { return(null); } break; } } int insertIndex = inst.ChildIndex; var type = context.TypeSystem.FindType(instToExtract.ResultType); // Move instToExtract itself: var v = function.RegisterVariable(VariableKind.StackSlot, type); instToExtract.ReplaceWith(new LdLoc(v)); block.Instructions.Insert(insertIndex, new StLoc(v, instToExtract)); // Apply the other move actions: foreach (var moveAction in ctx.MoveActions) { block.Instructions.Insert(insertIndex, moveAction()); } return(v); } if (!inst.Parent.PrepareExtract(inst.ChildIndex, ctx)) { return(null); } inst = inst.Parent; } return(null); }