File: Lowering\LocalRewriter\LocalRewriter_WhileStatement.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.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class LocalRewriter
    {
        public override BoundNode VisitWhileStatement(BoundWhileStatement node)
        {
            Debug.Assert(node != null);
 
            var rewrittenCondition = VisitExpression(node.Condition);
            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 (!node.WasCompilerGenerated && this.Instrument)
            {
                rewrittenCondition = Instrumenter.InstrumentWhileStatementCondition(node, rewrittenCondition, _factory);
            }
 
            return RewriteWhileStatement(
                node,
                node.Locals,
                rewrittenCondition,
                rewrittenBody,
                node.BreakLabel,
                node.ContinueLabel,
                node.HasErrors);
        }
 
        private BoundStatement RewriteWhileStatement(
            BoundNode loop,
            BoundExpression rewrittenCondition,
            BoundStatement rewrittenBody,
            GeneratedLabelSymbol breakLabel,
            GeneratedLabelSymbol continueLabel,
            bool hasErrors)
        {
            Debug.Assert(loop.Kind is BoundKind.WhileStatement or BoundKind.ForEachStatement or BoundKind.CollectionExpressionSpreadElement);
 
            // while (condition) 
            //   body;
            //
            // becomes
            //
            // goto continue;
            // start: 
            // {
            //     body
            //     continue:
            //     GotoIfTrue condition start;
            // }
            // break:
 
            SyntaxNode syntax = loop.Syntax;
            var startLabel = new GeneratedLabelSymbol("start");
            BoundStatement ifConditionGotoStart = new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, true, startLabel);
            BoundStatement gotoContinue = new BoundGotoStatement(syntax, continueLabel);
 
            if (this.Instrument && !loop.WasCompilerGenerated)
            {
                switch (loop.Kind)
                {
                    case BoundKind.WhileStatement:
                        ifConditionGotoStart = Instrumenter.InstrumentWhileStatementConditionalGotoStartOrBreak((BoundWhileStatement)loop, ifConditionGotoStart);
                        break;
 
                    case BoundKind.ForEachStatement:
                        ifConditionGotoStart = Instrumenter.InstrumentForEachStatementConditionalGotoStart((BoundForEachStatement)loop, ifConditionGotoStart);
                        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(loop.Kind);
                }
 
                // 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 would give the
                // impression that the previous statement is being re-executed.
                gotoContinue = BoundSequencePoint.CreateHidden(gotoContinue);
            }
 
            return BoundStatementList.Synthesized(syntax, hasErrors,
                gotoContinue,
                new BoundLabelStatement(syntax, startLabel),
                rewrittenBody,
                new BoundLabelStatement(syntax, continueLabel),
                ifConditionGotoStart,
                new BoundLabelStatement(syntax, breakLabel));
        }
 
        private BoundStatement RewriteWhileStatement(
            BoundWhileStatement loop,
            ImmutableArray<LocalSymbol> locals,
            BoundExpression rewrittenCondition,
            BoundStatement rewrittenBody,
            GeneratedLabelSymbol breakLabel,
            GeneratedLabelSymbol continueLabel,
            bool hasErrors)
        {
            if (locals.IsEmpty)
            {
                return RewriteWhileStatement(loop, rewrittenCondition, rewrittenBody, breakLabel, continueLabel, hasErrors);
            }
 
            // We need to enter 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.
 
            // while (condition) 
            //   body;
            //
            // becomes
            //
            // continue:
            // {
            //     GotoIfFalse condition break;
            //     body
            //     goto continue;
            // }
            // break:
 
            SyntaxNode syntax = loop.Syntax;
            BoundStatement continueLabelStatement = new BoundLabelStatement(syntax, continueLabel);
            BoundStatement ifNotConditionGotoBreak = new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, false, breakLabel);
 
            if (this.Instrument && !loop.WasCompilerGenerated)
            {
                ifNotConditionGotoBreak = Instrumenter.InstrumentWhileStatementConditionalGotoStartOrBreak(loop, ifNotConditionGotoBreak);
                continueLabelStatement = BoundSequencePoint.CreateHidden(continueLabelStatement);
            }
 
            return BoundStatementList.Synthesized(syntax, hasErrors,
                continueLabelStatement,
                new BoundBlock(syntax,
                               locals,
                               ImmutableArray.Create(
                                    ifNotConditionGotoBreak,
                                    rewrittenBody,
                                    new BoundGotoStatement(syntax, continueLabel))),
                new BoundLabelStatement(syntax, breakLabel));
        }
    }
}