File: Binder\Semantics\AccessCheck.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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// Contains the code for determining C# accessibility rules.
    /// </summary>
    internal static class AccessCheck
    {
        /// <summary>
        /// Checks if 'symbol' is accessible from within assembly 'within'.
        /// </summary>
        public static bool IsSymbolAccessible(
            Symbol symbol,
            AssemblySymbol within,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            bool failedThroughTypeCheck;
            return IsSymbolAccessibleCore(symbol, within, null, out failedThroughTypeCheck, within.DeclaringCompilation, ref useSiteInfo);
        }
 
        /// <summary>
        /// Checks if 'symbol' is accessible from within type 'within', with
        /// an optional qualifier of type "throughTypeOpt".
        /// </summary>
        public static bool IsSymbolAccessible(
            Symbol symbol,
            NamedTypeSymbol within,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
            TypeSymbol throughTypeOpt = null)
        {
            bool failedThroughTypeCheck;
            return IsSymbolAccessibleCore(symbol, within, throughTypeOpt, out failedThroughTypeCheck, within.DeclaringCompilation, ref useSiteInfo);
        }
 
        /// <summary>
        /// Checks if 'symbol' is accessible from within type 'within', with
        /// a qualifier of type "throughTypeOpt". Sets "failedThroughTypeCheck" to true
        /// if it failed the "through type" check.
        /// </summary>
        public static bool IsSymbolAccessible(
            Symbol symbol,
            NamedTypeSymbol within,
            TypeSymbol throughTypeOpt,
            out bool failedThroughTypeCheck,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
            ConsList<TypeSymbol> basesBeingResolved = null)
        {
            return IsSymbolAccessibleCore(symbol, within, throughTypeOpt, out failedThroughTypeCheck, within.DeclaringCompilation, ref useSiteInfo, basesBeingResolved);
        }
 
        /// <summary>
        /// Returns true if the symbol is effectively public or internal based on
        /// the declared accessibility of the symbol and any containing symbols.
        /// </summary>
        internal static bool IsEffectivelyPublicOrInternal(Symbol symbol, out bool isInternal)
        {
            Debug.Assert(symbol is object);
 
            switch (symbol.Kind)
            {
                case SymbolKind.NamedType:
                case SymbolKind.Event:
                case SymbolKind.Field:
                case SymbolKind.Method:
                case SymbolKind.Property:
                    break;
                case SymbolKind.TypeParameter:
                    symbol = symbol.ContainingSymbol;
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
            }
 
            isInternal = false;
 
            do
            {
                switch (symbol.DeclaredAccessibility)
                {
                    case Accessibility.Public:
                    case Accessibility.Protected:
                    case Accessibility.ProtectedOrInternal:
                        break;
                    case Accessibility.Internal:
                    case Accessibility.ProtectedAndInternal:
                        isInternal = true;
                        break;
                    case Accessibility.Private:
                        return false;
                    default:
                        throw ExceptionUtilities.UnexpectedValue(symbol.DeclaredAccessibility);
                }
 
                symbol = symbol.ContainingType;
            }
            while (symbol is object);
 
            return true;
        }
 
        /// <summary>
        /// Checks if 'symbol' is accessible from within 'within', which must be a NamedTypeSymbol
        /// or an AssemblySymbol. 
        /// </summary>
        /// <remarks>
        /// Note that NamedTypeSymbol, if available, is the type that is associated with the binder 
        /// that found the 'symbol', not the inner-most type that contains the access to the
        /// 'symbol'.
        /// <para>
        /// If 'symbol' is accessed off of an expression then 'throughTypeOpt' is the type of that
        /// expression. This is needed to properly do protected access checks. Sets
        /// "failedThroughTypeCheck" to true if this protected check failed.
        /// </para>
        /// <para>
        /// This function is expected to be called a lot.  As such, it avoids memory
        /// allocations in the function itself (including not making any iterators).  This means
        /// that certain helper functions that could otherwise be called are inlined in this method to
        /// prevent the overhead of returning collections or enumerators.
        /// </para>
        /// </remarks>
        private static bool IsSymbolAccessibleCore(
            Symbol symbol,
            Symbol within,  // must be assembly or named type symbol
            TypeSymbol throughTypeOpt,
            out bool failedThroughTypeCheck,
            CSharpCompilation compilation,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
            ConsList<TypeSymbol> basesBeingResolved = null)
        {
            Debug.Assert((object)symbol != null);
            Debug.Assert((object)within != null);
            Debug.Assert(within.IsDefinition);
            Debug.Assert(within is NamedTypeSymbol || within is AssemblySymbol);
 
            failedThroughTypeCheck = false;
 
            switch (symbol.Kind)
            {
                case SymbolKind.ArrayType:
                    return IsSymbolAccessibleCore(((ArrayTypeSymbol)symbol).ElementType, within, null, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved);
 
                case SymbolKind.PointerType:
                    return IsSymbolAccessibleCore(((PointerTypeSymbol)symbol).PointedAtType, within, null, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved);
 
                case SymbolKind.NamedType:
                    return IsNamedTypeAccessible((NamedTypeSymbol)symbol, within, ref useSiteInfo, basesBeingResolved);
 
                case SymbolKind.Alias:
                    return IsSymbolAccessibleCore(((AliasSymbol)symbol).Target, within, null, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved);
 
                case SymbolKind.Discard:
                    return IsSymbolAccessibleCore(((DiscardSymbol)symbol).TypeWithAnnotations.Type, within, null, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved);
 
                case SymbolKind.FunctionPointerType:
                    var funcPtr = (FunctionPointerTypeSymbol)symbol;
                    if (!IsSymbolAccessibleCore(funcPtr.Signature.ReturnType, within, throughTypeOpt: null, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved))
                    {
                        return false;
                    }
 
                    foreach (var param in funcPtr.Signature.Parameters)
                    {
                        if (!IsSymbolAccessibleCore(param.Type, within, throughTypeOpt: null, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved))
                        {
                            return false;
                        }
                    }
 
                    return true;
 
                case SymbolKind.ErrorType:
                    // Always assume that error types are accessible.
                    return true;
 
                case SymbolKind.TypeParameter:
                case SymbolKind.Parameter:
                case SymbolKind.Local:
                case SymbolKind.Label:
                case SymbolKind.Namespace:
                case SymbolKind.DynamicType:
                case SymbolKind.Assembly:
                case SymbolKind.NetModule:
                case SymbolKind.RangeVariable:
                case SymbolKind.Method when ((MethodSymbol)symbol).MethodKind == MethodKind.LocalFunction:
                    // These types of symbols are always accessible (if visible).
                    return true;
 
                case SymbolKind.Method:
                case SymbolKind.Property:
                case SymbolKind.Event:
                case SymbolKind.Field:
                    if (!symbol.RequiresInstanceReceiver())
                    {
                        // static members aren't accessed "through" an "instance" of any type.  So we
                        // null out the "through" instance here.  This ensures that we'll understand
                        // accessing protected statics properly.
                        throughTypeOpt = null;
                    }
 
                    return IsMemberAccessible(symbol.ContainingType, symbol.DeclaredAccessibility, within, throughTypeOpt, out failedThroughTypeCheck, compilation, ref useSiteInfo);
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
            }
        }
 
        /// <summary>
        /// Is the named type <paramref name="type"/> accessible from within <paramref name="within"/>,
        /// which must be a named type or an assembly.
        /// </summary>
        private static bool IsNamedTypeAccessible(NamedTypeSymbol type, Symbol within, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, ConsList<TypeSymbol> basesBeingResolved = null)
        {
            Debug.Assert(within is NamedTypeSymbol || within is AssemblySymbol);
            Debug.Assert((object)type != null);
 
            var compilation = within.DeclaringCompilation;
 
            bool unused;
            if (!type.IsDefinition)
            {
                // All type argument must be accessible.
                var typeArgs = type.TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo);
                foreach (var typeArg in typeArgs)
                {
                    // type parameters are always accessible, so don't check those (so common it's
                    // worth optimizing this).
                    if (typeArg.Type.Kind != SymbolKind.TypeParameter && !IsSymbolAccessibleCore(typeArg.Type, within, null, out unused, compilation, ref useSiteInfo, basesBeingResolved))
                    {
                        return false;
                    }
                }
            }
 
            var containingType = type.ContainingType;
            return (object)containingType == null
                ? IsNonNestedTypeAccessible(type.ContainingAssembly, type.DeclaredAccessibility, within)
                : IsMemberAccessible(containingType, type.DeclaredAccessibility, within, null, out unused, compilation, ref useSiteInfo, basesBeingResolved);
        }
 
        /// <summary>
        /// Is a top-level type with accessibility "declaredAccessibility" inside assembly "assembly"
        /// accessible from "within", which must be a named type of an assembly.
        /// </summary>
        private static bool IsNonNestedTypeAccessible(
            AssemblySymbol assembly,
            Accessibility declaredAccessibility,
            Symbol within)
        {
            Debug.Assert(within is NamedTypeSymbol || within is AssemblySymbol);
            Debug.Assert((object)assembly != null);
 
            switch (declaredAccessibility)
            {
                case Accessibility.NotApplicable:
                case Accessibility.Public:
                    // Public symbols are always accessible from any context
                    return true;
 
                case Accessibility.Private:
                case Accessibility.Protected:
                case Accessibility.ProtectedAndInternal:
                    // Shouldn't happen except in error cases.
                    return false;
 
                case Accessibility.Internal:
                case Accessibility.ProtectedOrInternal:
 
                    // within is typically a type
                    var withinType = within as NamedTypeSymbol;
                    var withinAssembly = (object)withinType != null ? withinType.ContainingAssembly : (AssemblySymbol)within;
 
                    // An internal type is accessible if we're in the same assembly or we have
                    // friend access to the assembly it was defined in.
                    return (object)withinAssembly == (object)assembly || withinAssembly.HasInternalAccessTo(assembly);
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(declaredAccessibility);
            }
        }
 
        /// <summary>
        /// Is a member with declared accessibility "declaredAccessibility" accessible from within
        /// "within", which must be a named type or an assembly.
        /// </summary>
        private static bool IsMemberAccessible(
            NamedTypeSymbol containingType,              // the symbol's containing type
            Accessibility declaredAccessibility,
            Symbol within,
            TypeSymbol throughTypeOpt,
            out bool failedThroughTypeCheck,
            CSharpCompilation compilation,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
            ConsList<TypeSymbol> basesBeingResolved = null)
        {
            Debug.Assert(within is NamedTypeSymbol || within is AssemblySymbol);
            Debug.Assert((object)containingType != null);
 
            failedThroughTypeCheck = false;
 
            // easy case - members of containing type are accessible.
            if ((object)containingType == (object)within)
            {
                return true;
            }
 
            // A nested symbol is only accessible to us if its container is accessible as well.
            if (!IsNamedTypeAccessible(containingType, within, ref useSiteInfo, basesBeingResolved))
            {
                return false;
            }
 
            // public in accessible type is accessible
            if (declaredAccessibility == Accessibility.Public)
            {
                return true;
            }
 
            return IsNonPublicMemberAccessible(
                containingType,
                declaredAccessibility,
                within,
                throughTypeOpt,
                out failedThroughTypeCheck,
                compilation,
                ref useSiteInfo,
                basesBeingResolved);
        }
 
        private static bool IsNonPublicMemberAccessible(
            NamedTypeSymbol containingType,              // the symbol's containing type
            Accessibility declaredAccessibility,
            Symbol within,
            TypeSymbol throughTypeOpt,
            out bool failedThroughTypeCheck,
            CSharpCompilation compilation,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
            ConsList<TypeSymbol> basesBeingResolved = null)
        {
            failedThroughTypeCheck = false;
 
            var originalContainingType = containingType.OriginalDefinition;
            var withinType = within as NamedTypeSymbol;
            var withinAssembly = (object)withinType != null ? withinType.ContainingAssembly : (AssemblySymbol)within;
 
            switch (declaredAccessibility)
            {
                case Accessibility.NotApplicable:
                    return true;
 
                case Accessibility.Private:
                    // All expressions in the current submission (top-level or nested in a method or
                    // type) can access previous submission's private top-level members. Previous
                    // submissions are treated like outer classes for the current submission - the
                    // inner class can access private members of the outer class.
                    if (containingType.TypeKind == TypeKind.Submission)
                    {
                        return true;
                    }
 
                    // private members never accessible from outside a type.
                    return (object)withinType != null && IsPrivateSymbolAccessible(withinType, originalContainingType);
 
                case Accessibility.Internal:
                    // An internal type is accessible if we're in the same assembly or we have
                    // friend access to the assembly it was defined in.
                    return withinAssembly.HasInternalAccessTo(containingType.ContainingAssembly);
 
                case Accessibility.ProtectedAndInternal:
                    if (!withinAssembly.HasInternalAccessTo(containingType.ContainingAssembly))
                    {
                        // We require internal access.  If we don't have it, then this symbol is
                        // definitely not accessible to us.
                        return false;
                    }
 
                    // We had internal access.  Also have to make sure we have protected access.
                    return IsProtectedSymbolAccessible(withinType, throughTypeOpt, originalContainingType, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved);
 
                case Accessibility.ProtectedOrInternal:
                    if (withinAssembly.HasInternalAccessTo(containingType.ContainingAssembly))
                    {
                        // If we have internal access to this symbol, then that's sufficient.  no
                        // need to do the complicated protected case.
                        return true;
                    }
 
                    // We don't have internal access.  But if we have protected access then that's
                    // sufficient.
                    return IsProtectedSymbolAccessible(withinType, throughTypeOpt, originalContainingType, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved);
 
                case Accessibility.Protected:
                    return IsProtectedSymbolAccessible(withinType, throughTypeOpt, originalContainingType, out failedThroughTypeCheck, compilation, ref useSiteInfo, basesBeingResolved);
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(declaredAccessibility);
            }
        }
 
        /// <summary>
        /// Is a protected symbol inside "originalContainingType" accessible from within "within",
        /// which much be a named type or an assembly.
        /// </summary>
        private static bool IsProtectedSymbolAccessible(
            NamedTypeSymbol withinType,
            TypeSymbol throughTypeOpt,
            NamedTypeSymbol originalContainingType,
            out bool failedThroughTypeCheck,
            CSharpCompilation compilation,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
            ConsList<TypeSymbol> basesBeingResolved = null)
        {
            failedThroughTypeCheck = false;
 
            // It is not an error to define protected member in a sealed Script class, it's just a
            // warning. The member behaves like a private one - it is visible in all subsequent
            // submissions.
            if (originalContainingType.TypeKind == TypeKind.Submission)
            {
                return true;
            }
 
            if ((object)withinType == null)
            {
                // If we're not within a type, we can't access a protected symbol
                return false;
            }
 
            // A protected symbol is accessible if we're (optionally nested) inside the type that it
            // was defined in.  
 
            // It is helpful to think about 'protected' as *increasing* the
            // accessibility domain of a private member, rather than *decreasing* that of a public
            // member. Members are naturally private; the protected, internal and public access
            // modifiers all increase the accessibility domain. Since private members are accessible
            // to nested types, so are protected members.
 
            // We do this check up front as it is very fast and easy to do.
            if (IsNestedWithinOriginalContainingType(withinType, originalContainingType))
            {
                return true;
            }
 
            // Protected is really confusing.  Check out 3.5.3 of the language spec "protected access
            // for instance members" to see how it works.  I actually got the code for this from
            // LangCompiler::CheckAccessCore
            {
                var current = withinType.OriginalDefinition;
                var originalThroughTypeOpt = (object)throughTypeOpt == null ? null : throughTypeOpt.OriginalDefinition as TypeSymbol;
                while ((object)current != null)
                {
                    Debug.Assert(current.IsDefinition);
 
                    if (current.InheritsFromOrImplementsIgnoringConstruction(originalContainingType, compilation, ref useSiteInfo, basesBeingResolved))
                    {
                        // NOTE(cyrusn): We're continually walking up the 'throughType's inheritance
                        // chain.  We could compute it up front and cache it in a set.  However, we
                        // don't want to allocate memory in this function.  Also, in practice
                        // inheritance chains should be very short.  As such, it might actually be
                        // slower to create and check inside the set versus just walking the
                        // inheritance chain.
                        if ((object)originalThroughTypeOpt == null ||
                            originalThroughTypeOpt.InheritsFromOrImplementsIgnoringConstruction(current, compilation, ref useSiteInfo))
                        {
                            return true;
                        }
                        else
                        {
                            failedThroughTypeCheck = true;
                        }
                    }
 
                    // NOTE(cyrusn): The container of an original type is always original.
                    current = current.ContainingType;
                }
            }
 
            return false;
        }
 
        private static bool IsPrivateSymbolAccessible(
            Symbol within,
            NamedTypeSymbol originalContainingType)
        {
            Debug.Assert(within is NamedTypeSymbol || within is AssemblySymbol);
 
            var withinType = within as NamedTypeSymbol;
            if ((object)withinType == null)
            {
                // If we're not within a type, we can't access a private symbol
                return false;
            }
 
            // A private symbol is accessible if we're (optionally nested) inside the type that it
            // was defined in.
            return IsNestedWithinOriginalContainingType(withinType, originalContainingType);
        }
 
        /// <summary>
        /// Is the type "withinType" nested within the original type "originalContainingType".
        /// </summary>
        private static bool IsNestedWithinOriginalContainingType(
            NamedTypeSymbol withinType,
            NamedTypeSymbol originalContainingType)
        {
            Debug.Assert((object)withinType != null);
            Debug.Assert((object)originalContainingType != null);
            Debug.Assert(originalContainingType.IsDefinition);
 
            // Walk up my parent chain and see if I eventually hit the owner.  If so then I'm a
            // nested type of that owner and I'm allowed access to everything inside of it.
            var current = withinType.OriginalDefinition;
            while ((object)current != null)
            {
                Debug.Assert(current.IsDefinition);
                if (current == (object)originalContainingType)
                {
                    return true;
                }
 
                // NOTE(cyrusn): The container of an 'original' type is always original. 
                current = current.ContainingType;
            }
 
            return false;
        }
 
        /// <summary>
        /// Determine if "type" inherits from or implements "baseType", ignoring constructed types, and dealing
        /// only with original types.
        /// </summary>
        private static bool InheritsFromOrImplementsIgnoringConstruction(
            this TypeSymbol type,
            NamedTypeSymbol baseType,
            CSharpCompilation compilation,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
            ConsList<TypeSymbol> basesBeingResolved = null)
        {
            Debug.Assert(type.IsDefinition);
            Debug.Assert(baseType.IsDefinition);
 
            PooledHashSet<NamedTypeSymbol> interfacesLookedAt = null;
            ArrayBuilder<NamedTypeSymbol> baseInterfaces = null;
 
            bool baseTypeIsInterface = baseType.IsInterface;
            if (baseTypeIsInterface)
            {
                interfacesLookedAt = PooledHashSet<NamedTypeSymbol>.GetInstance();
                baseInterfaces = ArrayBuilder<NamedTypeSymbol>.GetInstance();
            }
 
            PooledHashSet<NamedTypeSymbol> visited = null;
            var current = type;
            bool result = false;
 
            while ((object)current != null)
            {
                Debug.Assert(current.IsDefinition);
                if (baseTypeIsInterface == current.IsInterfaceType() &&
                    current == (object)baseType)
                {
                    result = true;
                    break;
                }
 
                if (baseTypeIsInterface)
                {
                    getBaseInterfaces(current, baseInterfaces, interfacesLookedAt, basesBeingResolved);
                }
 
                // NOTE(cyrusn): The base type of an 'original' type may not be 'original'. i.e. 
                // "class Goo : IBar<int>".  We must map it back to the 'original' when as we walk up
                // the base type hierarchy.
                var next = current.GetNextBaseTypeNoUseSiteDiagnostics(basesBeingResolved, compilation, ref visited);
                if ((object)next == null)
                {
                    current = null;
                }
                else
                {
                    current = (TypeSymbol)next.OriginalDefinition;
                    current.AddUseSiteInfo(ref useSiteInfo);
                }
            }
 
            visited?.Free();
 
            if (!result && baseTypeIsInterface)
            {
                Debug.Assert(!result);
 
                while (baseInterfaces.Count != 0)
                {
                    NamedTypeSymbol currentBase = baseInterfaces.Pop();
 
                    if (!currentBase.IsInterface)
                    {
                        continue;
                    }
 
                    Debug.Assert(currentBase.IsDefinition);
                    if (currentBase == (object)baseType)
                    {
                        result = true;
                        break;
                    }
 
                    getBaseInterfaces(currentBase, baseInterfaces, interfacesLookedAt, basesBeingResolved);
                }
 
                if (!result)
                {
                    foreach (var candidate in interfacesLookedAt)
                    {
                        candidate.AddUseSiteInfo(ref useSiteInfo);
                    }
                }
            }
 
            interfacesLookedAt?.Free();
            baseInterfaces?.Free();
            return result;
 
            static void getBaseInterfaces(TypeSymbol derived, ArrayBuilder<NamedTypeSymbol> baseInterfaces, PooledHashSet<NamedTypeSymbol> interfacesLookedAt, ConsList<TypeSymbol> basesBeingResolved)
            {
                if (basesBeingResolved != null && basesBeingResolved.ContainsReference(derived))
                {
                    return;
                }
 
                ImmutableArray<NamedTypeSymbol> declaredInterfaces;
 
                switch (derived)
                {
                    case TypeParameterSymbol typeParameter:
                        declaredInterfaces = typeParameter.AllEffectiveInterfacesNoUseSiteDiagnostics;
                        break;
 
                    case NamedTypeSymbol namedType:
                        declaredInterfaces = namedType.GetDeclaredInterfaces(basesBeingResolved);
                        break;
 
                    default:
                        declaredInterfaces = derived.InterfacesNoUseSiteDiagnostics(basesBeingResolved);
                        break;
                }
 
                foreach (var @interface in declaredInterfaces)
                {
                    NamedTypeSymbol definition = @interface.OriginalDefinition;
                    if (interfacesLookedAt.Add(definition))
                    {
                        baseInterfaces.Add(definition);
                    }
                }
            }
        }
 
        /// <summary>
        /// Does the assembly has internal accessibility to "toAssembly"?
        /// </summary>
        /// <param name="fromAssembly">The assembly wanting access.</param>
        /// <param name="toAssembly">The assembly possibly providing symbols to be accessed.</param>
        internal static bool HasInternalAccessTo(this AssemblySymbol fromAssembly, AssemblySymbol toAssembly)
        {
            if (Equals(fromAssembly, toAssembly))
            {
                return true;
            }
 
            if (fromAssembly.AreInternalsVisibleToThisAssembly(toAssembly))
            {
                return true;
            }
 
            // all interactive assemblies are friends of each other:
            if (fromAssembly.IsInteractive && toAssembly.IsInteractive)
            {
                return true;
            }
 
            return false;
        }
 
        internal static ErrorCode GetProtectedMemberInSealedTypeError(NamedTypeSymbol containingType)
        {
            return containingType.TypeKind == TypeKind.Struct ? ErrorCode.ERR_ProtectedInStruct : ErrorCode.WRN_ProtectedInSealed;
        }
    }
}