File: Lowering\Instrumentation\Instrumenter.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;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.Shared.Collections;
using System.Collections.Immutable;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// A base class for components that instrument various portions of executable code.
    /// It provides a set of APIs that are called by <see cref="LocalRewriter"/> to instrument
    /// specific portions of the code. These APIs have at least two parameters:
    ///     - original bound node produced by the <see cref="Binder"/> for the relevant portion of the code;
    ///     - rewritten bound node created by the <see cref="LocalRewriter"/> for the original node.
    /// The APIs are expected to return new state of the rewritten node, after they apply appropriate
    /// modifications, if any.
    /// 
    /// The base class provides default implementation for all APIs, which simply returns the rewritten node. 
    /// </summary>
    internal class Instrumenter
    {
        /// <summary>
        /// The singleton NoOp instrumenter, can be used to terminate the chain of <see cref="CompoundInstrumenter"/>s.
        /// </summary>
        public static readonly Instrumenter NoOp = new Instrumenter();
 
        public Instrumenter()
        {
        }
 
        private static BoundStatement InstrumentStatement(BoundStatement original, BoundStatement rewritten)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            return rewritten;
        }
 
        public virtual BoundStatement InstrumentNoOpStatement(BoundNoOpStatement original, BoundStatement rewritten)
        {
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundStatement InstrumentYieldBreakStatement(BoundYieldBreakStatement original, BoundStatement rewritten)
        {
            Debug.Assert(!original.WasCompilerGenerated || original.Syntax.Kind() == SyntaxKind.Block);
            return rewritten;
        }
 
        public virtual BoundStatement InstrumentYieldReturnStatement(BoundYieldReturnStatement original, BoundStatement rewritten)
        {
            return InstrumentStatement(original, rewritten);
        }
 
        /// <summary>
        /// Called before the statements of the <paramref name="original"/> block are lowered.
        /// </summary>
        public virtual void PreInstrumentBlock(BoundBlock original, LocalRewriter rewriter)
        {
        }
 
        /// <summary>
        /// Instruments <paramref name="original"/> block.
        /// </summary>
        /// <param name="original">Original block.</param>
        /// <param name="rewriter">Local rewriter.</param>
        /// <param name="additionalLocals">Local symbols to be added to <see cref="BoundBlock.Locals"/> of the resulting block.</param>
        /// <param name="prologue">Node to be added to the beginning of the statement list of the instrumented block.</param>
        /// <param name="epilogue">Node to be added at the end of the statement list of the instrumented block.</param>
        public virtual void InstrumentBlock(BoundBlock original, LocalRewriter rewriter, ref TemporaryArray<LocalSymbol> additionalLocals, out BoundStatement? prologue, out BoundStatement? epilogue, out BoundBlockInstrumentation? instrumentation)
        {
            prologue = null;
            epilogue = null;
            instrumentation = null;
        }
 
        public virtual BoundStatement InstrumentThrowStatement(BoundThrowStatement original, BoundStatement rewritten)
        {
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundStatement InstrumentContinueStatement(BoundContinueStatement original, BoundStatement rewritten)
        {
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundStatement InstrumentGotoStatement(BoundGotoStatement original, BoundStatement rewritten)
        {
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundStatement InstrumentExpressionStatement(BoundExpressionStatement original, BoundStatement rewritten)
        {
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundStatement InstrumentFieldOrPropertyInitializer(BoundStatement original, BoundStatement rewritten)
        {
            Debug.Assert(LocalRewriter.IsFieldOrPropertyInitializer(original));
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundStatement InstrumentBreakStatement(BoundBreakStatement original, BoundStatement rewritten)
        {
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundExpression InstrumentDoStatementCondition(BoundDoStatement original, BoundExpression rewrittenCondition, SyntheticBoundNodeFactory factory)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.DoStatement);
            Debug.Assert(factory != null);
            return rewrittenCondition;
        }
 
        public virtual BoundExpression InstrumentWhileStatementCondition(BoundWhileStatement original, BoundExpression rewrittenCondition, SyntheticBoundNodeFactory factory)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.WhileStatement);
            Debug.Assert(factory != null);
            return rewrittenCondition;
        }
 
        public virtual BoundStatement InstrumentDoStatementConditionalGotoStart(BoundDoStatement original, BoundStatement ifConditionGotoStart)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.DoStatement);
            return ifConditionGotoStart;
        }
 
        public virtual BoundStatement InstrumentWhileStatementConditionalGotoStartOrBreak(BoundWhileStatement original, BoundStatement ifConditionGotoStart)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.WhileStatement);
            return ifConditionGotoStart;
        }
 
        [return: NotNullIfNotNull(nameof(collectionVarDecl))]
        public virtual BoundStatement? InstrumentForEachStatementCollectionVarDeclaration(BoundForEachStatement original, BoundStatement? collectionVarDecl)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax is CommonForEachStatementSyntax);
            return collectionVarDecl;
        }
 
        public virtual BoundStatement InstrumentForEachStatement(BoundForEachStatement original, BoundStatement rewritten)
        {
            Debug.Assert(original.Syntax is CommonForEachStatementSyntax);
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundStatement InstrumentForEachStatementIterationVarDeclaration(BoundForEachStatement original, BoundStatement iterationVarDecl)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.ForEachStatement);
            return iterationVarDecl;
        }
 
        public virtual BoundStatement InstrumentForEachStatementDeconstructionVariablesDeclaration(BoundForEachStatement original, BoundStatement iterationVarDecl)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.ForEachVariableStatement);
            return iterationVarDecl;
        }
 
        public virtual BoundStatement InstrumentForEachStatementConditionalGotoStart(BoundForEachStatement original, BoundStatement branchBack)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax is CommonForEachStatementSyntax);
            return branchBack;
        }
 
        public virtual BoundStatement InstrumentForStatementConditionalGotoStartOrBreak(BoundForStatement original, BoundStatement branchBack)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.ForStatement);
            return branchBack;
        }
 
        public virtual BoundExpression InstrumentForStatementCondition(BoundForStatement original, BoundExpression rewrittenCondition, SyntheticBoundNodeFactory factory)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.ForStatement);
            Debug.Assert(factory != null);
            return rewrittenCondition;
        }
 
        public virtual BoundStatement InstrumentIfStatementConditionalGoto(BoundIfStatement original, BoundStatement rewritten)
        {
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.IfStatement);
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundExpression InstrumentIfStatementCondition(BoundIfStatement original, BoundExpression rewrittenCondition, SyntheticBoundNodeFactory factory)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.IfStatement);
            Debug.Assert(factory != null);
            return rewrittenCondition;
        }
 
        public virtual BoundStatement InstrumentLabelStatement(BoundLabeledStatement original, BoundStatement rewritten)
        {
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.LabeledStatement);
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundStatement InstrumentUserDefinedLocalInitialization(BoundLocalDeclaration original, BoundStatement rewritten)
        {
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.VariableDeclarator ||
                         (original.Syntax.Kind() == SyntaxKind.LocalDeclarationStatement &&
                                ((LocalDeclarationStatementSyntax)original.Syntax).Declaration.Variables.Count == 1));
            return InstrumentStatement(original, rewritten);
        }
 
        public virtual BoundExpression InstrumentUserDefinedLocalAssignment(BoundAssignmentOperator original)
        {
            Debug.Assert(original.Left is BoundLocal { LocalSymbol.SynthesizedKind: SynthesizedLocalKind.UserDefined } or BoundParameter);
 
            return original;
        }
 
        public virtual BoundExpression InstrumentCall(BoundCall original, BoundExpression rewritten)
        {
            return rewritten;
        }
 
        /// <summary>
        /// Similarly to an interceptor, gives the instrumenter an opportunity to adjust call target, receiver and arguments.
        /// </summary>
        /// <remarks>
        /// Unlike interceptors, called also for constructor calls (with <paramref name="receiver"/> being null).
        /// </remarks>
        public virtual void InterceptCallAndAdjustArguments(
            ref MethodSymbol method,
            ref BoundExpression? receiver,
            ref ImmutableArray<BoundExpression> arguments,
            ref ImmutableArray<RefKind> argumentRefKindsOpt)
        {
        }
 
        public virtual BoundExpression InstrumentObjectCreationExpression(BoundObjectCreationExpression original, BoundExpression rewritten)
        {
            return rewritten;
        }
 
        public virtual BoundExpression InstrumentFunctionPointerInvocation(BoundFunctionPointerInvocation original, BoundExpression rewritten)
        {
            return rewritten;
        }
 
        public virtual BoundStatement InstrumentLockTargetCapture(BoundLockStatement original, BoundStatement lockTargetCapture)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.LockStatement);
            return lockTargetCapture;
        }
 
        public virtual BoundStatement InstrumentReturnStatement(BoundReturnStatement original, BoundStatement rewritten)
        {
            return rewritten;
        }
 
        public virtual BoundStatement InstrumentSwitchStatement(BoundSwitchStatement original, BoundStatement rewritten)
        {
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.SwitchStatement);
            return InstrumentStatement(original, rewritten);
        }
 
        /// <summary>
        /// Instrument a switch case when clause, which is translated to a conditional branch to the body of the case block.
        /// </summary>
        /// <param name="original">the bound expression of the when clause</param>
        /// <param name="ifConditionGotoBody">the lowered conditional branch into the case block</param>
        public virtual BoundStatement InstrumentSwitchWhenClauseConditionalGotoBody(BoundExpression original, BoundStatement ifConditionGotoBody)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.FirstAncestorOrSelf<WhenClauseSyntax>() != null);
            return ifConditionGotoBody;
        }
 
        public virtual BoundStatement InstrumentUsingTargetCapture(BoundUsingStatement original, BoundStatement usingTargetCapture)
        {
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.UsingStatement);
            return usingTargetCapture;
        }
 
        public virtual void InstrumentCatchBlock(
            BoundCatchBlock original,
            ref BoundExpression? rewrittenSource,
            ref BoundStatementList? rewrittenFilterPrologue,
            ref BoundExpression? rewrittenFilter,
            ref BoundBlock rewrittenBody,
            ref TypeSymbol? rewrittenType,
            SyntheticBoundNodeFactory factory)
        {
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.CatchClause);
        }
 
        public virtual BoundExpression InstrumentSwitchStatementExpression(BoundStatement original, BoundExpression rewrittenExpression, SyntheticBoundNodeFactory factory)
        {
            Debug.Assert(original.Kind == BoundKind.SwitchStatement);
            Debug.Assert(!original.WasCompilerGenerated);
            Debug.Assert(original.Syntax.Kind() == SyntaxKind.SwitchStatement);
            Debug.Assert(factory != null);
            return rewrittenExpression;
        }
 
        /// <summary>
        /// Instrument the expression of a switch arm of a switch expression.
        /// </summary>
        public virtual BoundExpression InstrumentSwitchExpressionArmExpression(BoundExpression original, BoundExpression rewrittenExpression, SyntheticBoundNodeFactory factory)
        {
            Debug.Assert(factory != null);
            return rewrittenExpression;
        }
 
        public virtual BoundStatement InstrumentSwitchBindCasePatternVariables(BoundStatement bindings)
        {
            return bindings;
        }
    }
}