HashSet <ILVariable> CollectLoopCounters(ILFunction function) { var loopCounters = new HashSet <ILVariable>(); foreach (BlockContainer possibleLoop in function.Descendants.OfType <BlockContainer>()) { if (possibleLoop.EntryPoint.IncomingEdgeCount == 1) { continue; } var loop = DetectedLoop.DetectLoop(possibleLoop); if (loop.Kind != LoopKind.For || loop.IncrementTarget == null) { continue; } loopCounters.Add(loop.IncrementTarget); } return(loopCounters); }
Statement ConvertLoop(BlockContainer container) { DetectedLoop loop = DetectedLoop.DetectLoop(container); continueCount = 0; breakTarget = container; continueTarget = loop.ContinueJumpTarget; Expression conditionExpr; BlockStatement blockStatement; switch (loop.Kind) { case LoopKind.DoWhile: blockStatement = ConvertBlockContainer(new BlockStatement(), loop.Container, loop.AdditionalBlocks, true); if (container.EntryPoint.IncomingEdgeCount == continueCount) { // Remove the entrypoint label if all jumps to the label were replaced with 'continue;' statements blockStatement.Statements.First().Remove(); } if (blockStatement.LastOrDefault() is ContinueStatement continueStmt) { continueStmt.Remove(); } return(new DoWhileStatement { EmbeddedStatement = blockStatement, Condition = exprBuilder.TranslateCondition(CombineConditions(loop.Conditions)) }); case LoopKind.For: conditionExpr = exprBuilder.TranslateCondition(loop.Conditions[0]); blockStatement = ConvertAsBlock(loop.Body); if (!loop.Body.HasFlag(InstructionFlags.EndPointUnreachable)) { blockStatement.Add(new BreakStatement()); } Statement iterator = null; if (loop.IncrementBlock == null) { // increment check is done by DetectLoop var statement = blockStatement.Last(); while (statement is ContinueStatement) { statement = (Statement)statement.PrevSibling; } iterator = statement.Detach(); } var forBody = ConvertBlockContainer(blockStatement, container, loop.AdditionalBlocks, true); var forStmt = new ForStatement() { Condition = conditionExpr, EmbeddedStatement = forBody }; if (forBody.LastOrDefault() is ContinueStatement continueStmt2) { continueStmt2.Remove(); } if (loop.IncrementBlock != null) { for (int i = 0; i < loop.IncrementBlock.Instructions.Count - 1; i++) { forStmt.Iterators.Add(Convert(loop.IncrementBlock.Instructions[i])); } if (loop.IncrementBlock.IncomingEdgeCount > continueCount) { forBody.Add(new LabelStatement { Label = loop.IncrementBlock.Label }); } } else if (iterator != null) { forStmt.Iterators.Add(iterator.Detach()); } return(forStmt); case LoopKind.While: if (loop.Body == null) { blockStatement = ConvertBlockContainer(container, true); } else { blockStatement = ConvertAsBlock(loop.Body); if (!loop.Body.HasFlag(InstructionFlags.EndPointUnreachable)) { blockStatement.Add(new BreakStatement()); } } if (loop.Conditions == null) { conditionExpr = new PrimitiveExpression(true); Debug.Assert(continueCount < container.EntryPoint.IncomingEdgeCount); Debug.Assert(blockStatement.Statements.First() is LabelStatement); if (container.EntryPoint.IncomingEdgeCount == continueCount + 1) { // Remove the entrypoint label if all jumps to the label were replaced with 'continue;' statements blockStatement.Statements.First().Remove(); } } else { conditionExpr = exprBuilder.TranslateCondition(loop.Conditions[0]); blockStatement = ConvertBlockContainer(blockStatement, container, loop.AdditionalBlocks, true); } if (blockStatement.LastOrDefault() is ContinueStatement stmt) { stmt.Remove(); } return(new WhileStatement(conditionExpr, blockStatement)); default: throw new ArgumentOutOfRangeException(); } }
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); }
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); }