File: Lowering\LocalRewriter\LocalRewriter_ForStatement.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class LocalRewriter
    {
        public override BoundNode VisitForStatement(BoundForStatement node)
        {
            Debug.Assert(node != null);
 
            var rewrittenInitializer = VisitStatement(node.Initializer);
            var rewrittenCondition = VisitExpression(node.Condition);
            var rewrittenIncrement = VisitStatement(node.Increment);
            var rewrittenBody = VisitStatement(node.Body);
            Debug.Assert(rewrittenBody is { });
 
            // EnC: We need to insert a hidden sequence point to handle function remapping in case 
            // the containing method is edited while methods invoked in the condition are being executed.
            if (rewrittenCondition != null && this.Instrument)
            {
                rewrittenCondition = Instrumenter.InstrumentForStatementCondition(node, rewrittenCondition, _factory);
            }
 
            return RewriteForStatement(
                node,
                rewrittenInitializer,
                rewrittenCondition,
                rewrittenIncrement,
                rewrittenBody);
        }
 
        private BoundStatement RewriteForStatementWithoutInnerLocals(
            BoundNode original,
            ImmutableArray<LocalSymbol> outerLocals,
            BoundStatement? rewrittenInitializer,
            BoundExpression? rewrittenCondition,
            BoundStatement? rewrittenIncrement,
            BoundStatement rewrittenBody,
            GeneratedLabelSymbol breakLabel,
            GeneratedLabelSymbol continueLabel,
            bool hasErrors)
        {
            Debug.Assert(original.Kind is BoundKind.ForStatement or BoundKind.ForEachStatement or BoundKind.CollectionExpressionSpreadElement);
            Debug.Assert(rewrittenBody != null);
 
            // The sequence point behavior exhibited here is different from that of the native compiler.  In the native
            // compiler, if you have something like 
            //
            // for([|int i = 0, j = 0|]; ; [|i++, j++|])
            //
            // then all the initializers are treated as a single sequence point, as are
            // all the loop incrementors.
            //
            // We now make each one individually a sequence point:
            //
            // for([|int i = 0|], [|j = 0|]; ; [|i++|], [|j++|])
            //
            // If we decide that we want to preserve the native compiler stepping behavior
            // then we'll need to be a bit fancy here. The initializer and increment statements
            // can contain lambdas whose bodies need to have sequence points inserted, so we
            // need to make sure we visit the children. But we'll also need to make sure that
            // we do not generate one sequence point for each statement in the initializers
            // and the incrementors.
 
            SyntaxNode syntax = original.Syntax;
            var statementBuilder = ArrayBuilder<BoundStatement>.GetInstance();
            if (rewrittenInitializer != null)
            {
                statementBuilder.Add(rewrittenInitializer);
            }
 
            var startLabel = new GeneratedLabelSymbol("start");
 
            // for (initializer; condition; increment)
            //   body;
            //
            // becomes the following (with block added for locals)
            //
            // {
            //   initializer;
            //   goto end;
            // start:
            //   body;
            // continue:
            //   increment;
            // end:
            //   GotoIfTrue condition start;
            // break:
            // }
 
            var endLabel = new GeneratedLabelSymbol("end");
 
            //  initializer;
            //  goto end;
 
            BoundStatement gotoEnd = new BoundGotoStatement(syntax, endLabel);
 
            if (this.Instrument)
            {
                // Mark the initial jump as hidden.
                // We do it to tell that this is not a part of previous statement.
                // This jump may be a target of another jump (for example if loops are nested) and that will make 
                // impression of the previous statement being re-executed
                gotoEnd = BoundSequencePoint.CreateHidden(gotoEnd);
            }
 
            statementBuilder.Add(gotoEnd);
 
            // start:
            //   body;
            statementBuilder.Add(new BoundLabelStatement(syntax, startLabel));
 
            statementBuilder.Add(rewrittenBody);
 
            // continue:
            //   increment;
            statementBuilder.Add(new BoundLabelStatement(syntax, continueLabel));
            if (rewrittenIncrement != null)
            {
                statementBuilder.Add(rewrittenIncrement);
            }
 
            // end:
            //   GotoIfTrue condition start;
            statementBuilder.Add(new BoundLabelStatement(syntax, endLabel));
            BoundStatement? branchBack = null;
            if (rewrittenCondition != null)
            {
                branchBack = new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, true, startLabel);
            }
            else
            {
                branchBack = new BoundGotoStatement(syntax, startLabel);
            }
 
            if (this.Instrument)
            {
                switch (original.Kind)
                {
                    case BoundKind.ForEachStatement:
                        branchBack = Instrumenter.InstrumentForEachStatementConditionalGotoStart((BoundForEachStatement)original, branchBack);
                        break;
                    case BoundKind.ForStatement:
                        branchBack = Instrumenter.InstrumentForStatementConditionalGotoStartOrBreak((BoundForStatement)original, branchBack);
                        break;
                    case BoundKind.CollectionExpressionSpreadElement:
                        // No instrumentation needed since the loop for the spread expression
                        // was generated in lowering, and not explicit in the source.
                        break;
                    default:
                        throw ExceptionUtilities.UnexpectedValue(original.Kind);
                }
            }
 
            statementBuilder.Add(branchBack);
 
            // break:
            statementBuilder.Add(new BoundLabelStatement(syntax, breakLabel));
 
            var statements = statementBuilder.ToImmutableAndFree();
            return new BoundBlock(syntax, outerLocals, statements, hasErrors);
        }
 
        private BoundStatement RewriteForStatement(
            BoundForStatement node,
            BoundStatement? rewrittenInitializer,
            BoundExpression? rewrittenCondition,
            BoundStatement? rewrittenIncrement,
            BoundStatement rewrittenBody)
        {
            if (node.InnerLocals.IsEmpty)
            {
                return RewriteForStatementWithoutInnerLocals(
                    node,
                    node.OuterLocals,
                    rewrittenInitializer,
                    rewrittenCondition,
                    rewrittenIncrement,
                    rewrittenBody,
                    node.BreakLabel,
                    node.ContinueLabel, node.HasErrors);
            }
 
            // We need to enter inner_scope-block from the top, that is where an instance of a display class will be created
            // if any local is captured within a lambda.
 
            // for (initializer; condition; increment)
            //   body;
            //
            // becomes the following (with block added for locals)
            //
            // {
            //   initializer;
            // start:
            //   {
            //     GotoIfFalse condition break;
            //     body;
            // continue:
            //     increment;
            //     goto start;
            //   }
            // break:
            // }
 
            Debug.Assert(rewrittenBody != null);
 
            SyntaxNode syntax = node.Syntax;
            var statementBuilder = ArrayBuilder<BoundStatement>.GetInstance();
 
            //  initializer;
            if (rewrittenInitializer != null)
            {
                statementBuilder.Add(rewrittenInitializer);
            }
 
            var startLabel = new GeneratedLabelSymbol("start");
 
            // start:
            BoundStatement startLabelStatement = new BoundLabelStatement(syntax, startLabel);
 
            if (Instrument)
            {
                startLabelStatement = BoundSequencePoint.CreateHidden(startLabelStatement);
            }
 
            statementBuilder.Add(startLabelStatement);
 
            var blockBuilder = ArrayBuilder<BoundStatement>.GetInstance();
 
            // GotoIfFalse condition break;
            if (rewrittenCondition != null)
            {
                BoundStatement ifNotConditionGotoBreak = new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, false, node.BreakLabel);
 
                if (this.Instrument)
                {
                    ifNotConditionGotoBreak = Instrumenter.InstrumentForStatementConditionalGotoStartOrBreak(node, ifNotConditionGotoBreak);
                }
 
                blockBuilder.Add(ifNotConditionGotoBreak);
            }
 
            // body;
            blockBuilder.Add(rewrittenBody);
 
            // continue:
            //   increment;
            blockBuilder.Add(new BoundLabelStatement(syntax, node.ContinueLabel));
            if (rewrittenIncrement != null)
            {
                blockBuilder.Add(rewrittenIncrement);
            }
 
            // goto start;
            blockBuilder.Add(new BoundGotoStatement(syntax, startLabel));
 
            statementBuilder.Add(new BoundBlock(syntax, node.InnerLocals, blockBuilder.ToImmutableAndFree()));
 
            // break:
            statementBuilder.Add(new BoundLabelStatement(syntax, node.BreakLabel));
 
            var statements = statementBuilder.ToImmutableAndFree();
            return new BoundBlock(syntax, node.OuterLocals, statements, node.HasErrors);
        }
    }
}