File: Binder\LockOrUsingBinder.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.
 
#nullable disable
 
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <remarks>
    /// This type exists to share code between UsingStatementBinder and LockBinder.
    /// </remarks>
    internal abstract class LockOrUsingBinder : LocalScopeBinder
    {
        private ImmutableHashSet<Symbol> _lazyLockedOrDisposedVariables;
        private ExpressionAndDiagnostics _lazyExpressionAndDiagnostics;
 
        internal LockOrUsingBinder(Binder enclosing)
            : base(enclosing)
        {
        }
 
        protected abstract ExpressionSyntax TargetExpressionSyntax { get; }
 
        internal sealed override ImmutableHashSet<Symbol> LockedOrDisposedVariables
        {
            get
            {
                if (_lazyLockedOrDisposedVariables == null)
                {
                    ImmutableHashSet<Symbol> lockedOrDisposedVariables = this.Next.LockedOrDisposedVariables;
 
                    ExpressionSyntax targetExpressionSyntax = TargetExpressionSyntax;
 
                    if (targetExpressionSyntax != null)
                    {
                        // We rely on this in the GetBinder call below.
                        Debug.Assert(targetExpressionSyntax.Parent.Kind() == SyntaxKind.LockStatement ||
                                     targetExpressionSyntax.Parent.Kind() == SyntaxKind.UsingStatement);
 
                        // For some reason, dev11 only warnings about locals and parameters.  If you do the same thing
                        // with a field of a local or parameter (e.g. lock(p.x)), there's no warning when you modify
                        // the local/parameter or its field.  We're going to take advantage of this restriction to break
                        // a cycle: if the expression contains any lvalues, the binder is going to check LockedOrDisposedVariables,
                        // which is going to bind the expression, which is going to check LockedOrDisposedVariables, etc.
                        // Fortunately, SyntaxKind.IdentifierName includes local and parameter accesses, but no expressions
                        // that require lvalue checks.
                        if (targetExpressionSyntax.Kind() == SyntaxKind.IdentifierName)
                        {
                            BoundExpression expression = BindTargetExpression(diagnostics: null, // Diagnostics reported by BindUsingStatementParts.
                                                                              originalBinder: GetBinder(targetExpressionSyntax.Parent));
 
                            switch (expression.Kind)
                            {
                                case BoundKind.Local:
                                    lockedOrDisposedVariables = lockedOrDisposedVariables.Add(((BoundLocal)expression).LocalSymbol);
                                    break;
                                case BoundKind.Parameter:
                                    lockedOrDisposedVariables = lockedOrDisposedVariables.Add(((BoundParameter)expression).ParameterSymbol);
                                    break;
                            }
                        }
                    }
 
                    Interlocked.CompareExchange(ref _lazyLockedOrDisposedVariables, lockedOrDisposedVariables, null);
                }
                Debug.Assert(_lazyLockedOrDisposedVariables != null);
                return _lazyLockedOrDisposedVariables;
            }
        }
 
        protected BoundExpression BindTargetExpression(BindingDiagnosticBag diagnostics, Binder originalBinder, TypeSymbol targetTypeOpt = null)
        {
            if (_lazyExpressionAndDiagnostics == null)
            {
                // Filter out method group in conversion.
                var expressionDiagnostics = BindingDiagnosticBag.GetInstance();
                BoundExpression boundExpression = originalBinder.BindValue(TargetExpressionSyntax, expressionDiagnostics, Binder.BindValueKind.RValueOrMethodGroup);
                if (targetTypeOpt is object)
                {
                    boundExpression = originalBinder.GenerateConversionForAssignment(targetTypeOpt, boundExpression, expressionDiagnostics);
                }
                else
                {
                    boundExpression = originalBinder.BindToNaturalType(boundExpression, expressionDiagnostics);
                }
                Interlocked.CompareExchange(ref _lazyExpressionAndDiagnostics, new ExpressionAndDiagnostics(boundExpression, expressionDiagnostics.ToReadOnlyAndFree()), null);
            }
            Debug.Assert(_lazyExpressionAndDiagnostics != null);
 
            if (diagnostics != null)
            {
                diagnostics.AddRange(_lazyExpressionAndDiagnostics.Diagnostics, allowMismatchInDependencyAccumulation: true);
            }
 
            return _lazyExpressionAndDiagnostics.Expression;
        }
 
        /// <remarks>
        /// This class exists so these two fields can be set atomically.
        /// CONSIDER: If this causes too many allocations, we could use start and end flags plus spinlocking
        /// as for completion parts.
        /// </remarks>
        private class ExpressionAndDiagnostics
        {
            public readonly BoundExpression Expression;
            public readonly ReadOnlyBindingDiagnostic<AssemblySymbol> Diagnostics;
 
            public ExpressionAndDiagnostics(BoundExpression expression, ReadOnlyBindingDiagnostic<AssemblySymbol> diagnostics)
            {
                this.Expression = expression;
                this.Diagnostics = diagnostics;
            }
        }
    }
}