File: Lowering\LocalRewriter\LocalRewriter_LockStatement.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.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class LocalRewriter
    {
        /// <summary>
        /// Lowers a lock statement to a try-finally block that calls (before and after the body, respectively):
        /// <list type="bullet">
        /// <item>Lock.EnterScope and Lock+Scope.Dispose if the argument is of type Lock, or</item>
        /// <item>Monitor.Enter and Monitor.Exit.</item>
        /// </list>
        /// </summary>
        public override BoundNode VisitLockStatement(BoundLockStatement node)
        {
            LockStatementSyntax lockSyntax = (LockStatementSyntax)node.Syntax;
 
            BoundExpression rewrittenArgument = VisitExpression(node.Argument);
            BoundStatement? rewrittenBody = VisitStatement(node.Body);
            Debug.Assert(rewrittenBody is { });
 
            TypeSymbol? argumentType = rewrittenArgument.Type;
            if (argumentType is null)
            {
                // This isn't particularly elegant, but hopefully locking on null is
                // not very common.
                Debug.Assert(rewrittenArgument.ConstantValueOpt == ConstantValue.Null);
                argumentType = _compilation.GetSpecialType(SpecialType.System_Object);
                rewrittenArgument = MakeLiteral(
                    rewrittenArgument.Syntax,
                    rewrittenArgument.ConstantValueOpt,
                    argumentType); //need to have a non-null type here for TempHelpers.StoreToTemp.
            }
 
            if (argumentType.IsWellKnownTypeLock())
            {
                if (LockBinder.TryFindLockTypeInfo(argumentType, _diagnostics, rewrittenArgument.Syntax) is not { } lockTypeInfo)
                {
                    Debug.Fail("We should have reported an error during binding if lock type info cannot be found.");
                    return node.Update(rewrittenArgument, rewrittenBody).WithHasErrors();
                }
 
                // lock (x) { body } -> using (x.EnterScope()) { body }
 
                var tryBlock = rewrittenBody is BoundBlock block ? block : BoundBlock.SynthesizedNoLocals(lockSyntax, rewrittenBody);
 
                var enterScopeCall = BoundCall.Synthesized(
                    rewrittenArgument.Syntax,
                    rewrittenArgument,
                    initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                    lockTypeInfo.EnterScopeMethod);
 
                // the temp must be associated with the lock statement to support EnC slot mapping:
                BoundLocal boundTemp = _factory.StoreToTemp(enterScopeCall,
                    out BoundAssignmentOperator tempAssignment,
                    syntaxOpt: lockSyntax,
                    kind: SynthesizedLocalKind.Using);
 
                var expressionStatement = new BoundExpressionStatement(rewrittenArgument.Syntax, tempAssignment);
 
                BoundStatement tryFinally = RewriteUsingStatementTryFinally(
                    rewrittenArgument.Syntax,
                    rewrittenArgument.Syntax,
                    tryBlock,
                    boundTemp,
                    awaitKeywordOpt: default,
                    awaitOpt: null,
                    patternDisposeInfo: MethodArgumentInfo.CreateParameterlessMethod(lockTypeInfo.ScopeDisposeMethod));
 
                return new BoundBlock(
                    lockSyntax,
                    locals: ImmutableArray.Create(boundTemp.LocalSymbol),
                    statements: ImmutableArray.Create(expressionStatement, tryFinally));
            }
 
            if (argumentType.Kind == SymbolKind.TypeParameter)
            {
                // If the argument has a type parameter type, then we'll box it right away
                // so that the same object is passed to both Monitor.Enter and Monitor.Exit.
                argumentType = _compilation.GetSpecialType(SpecialType.System_Object);
 
                rewrittenArgument = MakeConversionNode(
                    rewrittenArgument.Syntax,
                    rewrittenArgument,
                    Conversion.Boxing,
                    argumentType,
                    @checked: false,
                    constantValueOpt: rewrittenArgument.ConstantValueOpt);
            }
 
            BoundAssignmentOperator assignmentToLockTemp;
            BoundLocal boundLockTemp = _factory.StoreToTemp(rewrittenArgument, out assignmentToLockTemp, syntaxOpt: lockSyntax, kind: SynthesizedLocalKind.Lock);
 
            BoundStatement boundLockTempInit = new BoundExpressionStatement(lockSyntax, assignmentToLockTemp);
            BoundExpression exitCallExpr;
 
            MethodSymbol? exitMethod;
            if (TryGetWellKnownTypeMember(lockSyntax, WellKnownMember.System_Threading_Monitor__Exit, out exitMethod))
            {
                exitCallExpr = BoundCall.Synthesized(
                    lockSyntax,
                    receiverOpt: null,
                    initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                    exitMethod,
                    boundLockTemp);
            }
            else
            {
                exitCallExpr = new BoundBadExpression(lockSyntax, LookupResultKind.NotInvocable, ImmutableArray<Symbol?>.Empty, ImmutableArray.Create<BoundExpression>(boundLockTemp), ErrorTypeSymbol.UnknownResultType);
            }
 
            BoundStatement exitCall = new BoundExpressionStatement(lockSyntax, exitCallExpr);
 
            MethodSymbol? enterMethod;
 
            if ((TryGetWellKnownTypeMember(lockSyntax, WellKnownMember.System_Threading_Monitor__Enter2, out enterMethod, isOptional: true) ||
                 TryGetWellKnownTypeMember(lockSyntax, WellKnownMember.System_Threading_Monitor__Enter, out enterMethod)) && // If we didn't find the overload introduced in .NET 4.0, then use the older one. 
                enterMethod.ParameterCount == 2)
            {
                // C# 4.0+ version
                // L $lock = `argument`;                      // sequence point
                // bool $lockTaken = false;                   
                // try
                // {
                //     Monitor.Enter($lock, ref $lockTaken);
                //     `body`                                 // sequence point  
                // }
                // finally
                // {                                          // hidden sequence point   
                //     if ($lockTaken) Monitor.Exit($lock);   
                // }
 
                TypeSymbol boolType = _compilation.GetSpecialType(SpecialType.System_Boolean);
                BoundAssignmentOperator assignmentToLockTakenTemp;
 
                BoundLocal boundLockTakenTemp = _factory.StoreToTemp(
                    MakeLiteral(rewrittenArgument.Syntax, ConstantValue.False, boolType),
                    store: out assignmentToLockTakenTemp,
                    syntaxOpt: lockSyntax,
                    kind: SynthesizedLocalKind.LockTaken);
 
                BoundStatement boundLockTakenTempInit = new BoundExpressionStatement(lockSyntax, assignmentToLockTakenTemp);
 
                BoundStatement enterCall = new BoundExpressionStatement(
                    lockSyntax,
                    BoundCall.Synthesized(
                        lockSyntax,
                        receiverOpt: null,
                        initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                        enterMethod,
                        boundLockTemp,
                        boundLockTakenTemp));
 
                exitCall = RewriteIfStatement(
                    lockSyntax,
                    boundLockTakenTemp,
                    exitCall,
                    node.HasErrors);
 
                return new BoundBlock(
                    lockSyntax,
                    ImmutableArray.Create(boundLockTemp.LocalSymbol, boundLockTakenTemp.LocalSymbol),
                    ImmutableArray.Create(
                        InstrumentLockTargetCapture(node, boundLockTempInit),
                        boundLockTakenTempInit,
                        new BoundTryStatement(
                            lockSyntax,
                            BoundBlock.SynthesizedNoLocals(lockSyntax, ImmutableArray.Create<BoundStatement>(
                                enterCall,
                                rewrittenBody)),
                            ImmutableArray<BoundCatchBlock>.Empty,
                            BoundBlock.SynthesizedNoLocals(lockSyntax,
                                exitCall))));
            }
            else
            {
                // Pre-4.0 version
                // L $lock = `argument`;           // sequence point
                // Monitor.Enter($lock);           // NB: before try-finally so we don't Exit if an exception prevents us from acquiring the lock.
                // try 
                // {
                //     `body`                      // sequence point
                // } 
                // finally 
                // {
                //     Monitor.Exit($lock);        // hidden sequence point
                // }
 
                BoundExpression enterCallExpr;
 
                if ((object?)enterMethod != null)
                {
                    Debug.Assert(enterMethod.ParameterCount == 1);
 
                    enterCallExpr = BoundCall.Synthesized(
                        lockSyntax,
                        receiverOpt: null,
                        initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                        enterMethod,
                        boundLockTemp);
                }
                else
                {
                    enterCallExpr = new BoundBadExpression(lockSyntax, LookupResultKind.NotInvocable, ImmutableArray<Symbol?>.Empty, ImmutableArray.Create<BoundExpression>(boundLockTemp), ErrorTypeSymbol.UnknownResultType);
                }
 
                BoundStatement enterCall = new BoundExpressionStatement(
                    lockSyntax,
                    enterCallExpr);
 
                return new BoundBlock(
                    lockSyntax,
                    ImmutableArray.Create(boundLockTemp.LocalSymbol),
                    ImmutableArray.Create(
                        InstrumentLockTargetCapture(node, boundLockTempInit),
                        enterCall,
                        new BoundTryStatement(
                            lockSyntax,
                            BoundBlock.SynthesizedNoLocals(lockSyntax, rewrittenBody),
                            ImmutableArray<BoundCatchBlock>.Empty,
                            BoundBlock.SynthesizedNoLocals(lockSyntax, exitCall))));
            }
        }
 
        private BoundStatement InstrumentLockTargetCapture(BoundLockStatement original, BoundStatement lockTargetCapture)
        {
            return this.Instrument ?
                Instrumenter.InstrumentLockTargetCapture(original, lockTargetCapture) :
                lockTargetCapture;
        }
    }
}