// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license 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
{
///
/// Lowers a lock statement to a try-finally block that calls Monitor.Enter and Monitor.Exit
/// before and after the body, respectively.
///
public override BoundNode VisitLockStatement(BoundLockStatement node)
{
LockStatementSyntax lockSyntax = (LockStatementSyntax)node.Syntax;
BoundExpression rewrittenArgument = VisitExpression(node.Argument);
BoundStatement rewrittenBody = (BoundStatement)Visit(node.Body);
TypeSymbol argumentType = rewrittenArgument.Type;
if ((object)argumentType == null)
{
// This isn't particularly elegant, but hopefully locking on null is
// not very common.
Debug.Assert(rewrittenArgument.ConstantValue == ConstantValue.Null);
argumentType = _compilation.GetSpecialType(SpecialType.System_Object);
rewrittenArgument = MakeLiteral(
rewrittenArgument.Syntax,
rewrittenArgument.ConstantValue,
argumentType); //need to have a non-null type here for TempHelpers.StoreToTemp.
}
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 = MakeConversion(
rewrittenArgument.Syntax,
rewrittenArgument,
ConversionKind.Boxing,
argumentType,
@checked: false,
constantValueOpt: rewrittenArgument.ConstantValue);
}
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,
null,
exitMethod,
boundLockTemp);
}
else
{
exitCallExpr = new BoundBadExpression(lockSyntax, LookupResultKind.NotInvocable, ImmutableArray.Empty, ImmutableArray.Create(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,
null,
enterMethod,
boundLockTemp,
boundLockTakenTemp));
exitCall = RewriteIfStatement(
lockSyntax,
boundLockTakenTemp,
exitCall,
null,
node.HasErrors);
return new BoundBlock(
lockSyntax,
ImmutableArray.Create(boundLockTemp.LocalSymbol, boundLockTakenTemp.LocalSymbol),
ImmutableArray.Create(
MakeInitialLockSequencePoint(boundLockTempInit, lockSyntax),
boundLockTakenTempInit,
new BoundTryStatement(
lockSyntax,
BoundBlock.SynthesizedNoLocals(lockSyntax, ImmutableArray.Create(
enterCall,
rewrittenBody)),
ImmutableArray.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,
null,
enterMethod,
boundLockTemp);
}
else
{
enterCallExpr = new BoundBadExpression(lockSyntax, LookupResultKind.NotInvocable, ImmutableArray.Empty, ImmutableArray.Create(boundLockTemp), ErrorTypeSymbol.UnknownResultType);
}
BoundStatement enterCall = new BoundExpressionStatement(
lockSyntax,
enterCallExpr);
return new BoundBlock(
lockSyntax,
ImmutableArray.Create(boundLockTemp.LocalSymbol),
ImmutableArray.Create(
MakeInitialLockSequencePoint(boundLockTempInit, lockSyntax),
enterCall,
new BoundTryStatement(
lockSyntax,
BoundBlock.SynthesizedNoLocals(lockSyntax, rewrittenBody),
ImmutableArray.Empty,
BoundBlock.SynthesizedNoLocals(lockSyntax, exitCall))));
}
}
private BoundStatement MakeInitialLockSequencePoint(BoundStatement statement, LockStatementSyntax lockSyntax)
{
return this.GenerateDebugInfo ?
new BoundSequencePointWithSpan(lockSyntax, statement, TextSpan.FromBounds(lockSyntax.LockKeyword.SpanStart, lockSyntax.CloseParenToken.Span.End)) :
statement;
}
}
}