示例#1
0
        /// <summary>
        /// For a field/property assignment, figure out what needs to be checked for it.
        /// </summary>
        /// <param name="assignment">The assignment syntax for the field/property (possibly null)</param>
        /// <param name="query">The query to do (if the return value is AssignmentQueryKind.ImmutabilityQuery.)</param>
        /// <param name="diagnostic">A diagnostic to report (if the return value is AssignmentQueryKind.Hopeless.)</param>
        /// <returns>The details about what to check for this assignment</returns>
        private AssignmentQueryKind GetQueryForAssignment(
            AssignmentInfo assignment,
            out ImmutabilityQuery query,
            out Diagnostic diagnostic
            )
        {
            // When we have an assignment we use it to narrow our check, e.g.
            //
            //   private readonly object m_lock = new object();
            //
            // is safe even though object (the type of the field) in general is not.

            query      = default;
            diagnostic = null;

            // Easy cases
            switch (assignment.Expression.Kind())
            {
            case SyntaxKind.NullLiteralExpression:
                // Sometimes people explicitly (but needlessly) assign null in
                // an initializer... this is safe.
                return(AssignmentQueryKind.NothingToCheck);

            case SyntaxKind.SimpleLambdaExpression:
            case SyntaxKind.ParenthesizedLambdaExpression:
                if (assignment.IsInitializer)
                {
                    // Lambda initializers for readonly members are safe
                    // because they can only close over other members,
                    // which will be checked independently, or static
                    // members of another class, which are also analyzed.
                    return(AssignmentQueryKind.NothingToCheck);
                }

                // But for assignments inside a constructor we're in more
                // trouble. They could capture arbitrary state from their
                // lexical scope.

                // static functions can't capture state.
                if (assignment.Expression.IsStaticFunction())
                {
                    return(AssignmentQueryKind.NothingToCheck);
                }

                // In general we must panic.
                diagnostic = Diagnostic.Create(
                    Diagnostics.AnonymousFunctionsMayCaptureMutability,
                    assignment.Expression.GetLocation()
                    );

                return(AssignmentQueryKind.Hopeless);

            case SyntaxKind.InvocationExpression:

                // Some methods are known to have return values that are
                // immutable (such as Enumerable.Empty()).
                // These should be considered immutable by the Analyzer.
                SemanticModel semanticModel = m_compilation.GetSemanticModel(assignment.Expression.SyntaxTree);
                if (semanticModel
                    .GetSymbolInfo(assignment.Expression)
                    .Symbol is not IMethodSymbol methodSymbol
                    )
                {
                    break;
                }

                if (m_context.IsReturnValueKnownToBeImmutable(methodSymbol))
                {
                    return(AssignmentQueryKind.NothingToCheck);
                }

                break;
            }

            // If nothing above was caught, then fallback to querying.

            if (assignment.Expression is BaseObjectCreationExpressionSyntax _)
            {
                // When we have a new T() we don't need to worry about the value
                // being anything other than an instance of T.
                query = new ImmutabilityQuery(
                    ImmutableTypeKind.Instance,
                    type: assignment.AssignedType
                    );
            }
            else
            {
                // In general we need to handle subtypes.
                query = new ImmutabilityQuery(
                    ImmutableTypeKind.Total,
                    type: assignment.AssignedType
                    );
            }

            return(AssignmentQueryKind.ImmutabilityQuery);
        }
示例#2
0
        /// <summary>
        /// Determines if a type is known to be immutable.
        /// </summary>
        /// <param name="query">The type to check (and what kind of check to do.)</param>
        /// <param name="diag">If this method returns false, an explaination for why its not known to be immutable.</param>
        /// <returns>Is the type immutable?</returns>
        public bool IsImmutable(
            ImmutabilityQuery query,
            Func <Location> getLocation,
            out Diagnostic diagnostic
            )
        {
            if (query.Kind == ImmutableTypeKind.None)
            {
                throw new ArgumentException(
                          "ImmutabilityKind.None is not a valid question to ask this function",
                          nameof(query.Kind)
                          );
            }

            diagnostic = null;

            // Things like int are totally OK
            if (m_totallyImmutableSpecialTypes.Contains(query.Type.SpecialType))
            {
                return(true);
            }

            // "new object()" are always immutable (and that's the only
            // constructor for System.Object) but in general things of type
            // System.Object (any reference type) may be mutable.
            //
            // This is hard-coded (rather than in m_extraImmutableTypes) to
            // avoid the ITypeSymbol lookup.
            if (query.Kind == ImmutableTypeKind.Instance && query.Type.SpecialType == SpecialType.System_Object)
            {
                return(true);
            }

            if (query.Type is INamedTypeSymbol namedType)
            {
                ImmutableTypeInfo info = GetImmutableTypeInfo(namedType);
                if (info.Kind.HasFlag(query.Kind))
                {
                    return(info.IsImmutableDefinition(
                               context: this,
                               definition: namedType,
                               getLocation: getLocation,
                               out diagnostic
                               ));
                }
            }

            switch (query.Type.TypeKind)
            {
            case TypeKind.Error:
                // Just say this is fine -- there is some other compiler
                // error in this case and we don't need to pile on.
                return(true);

            case TypeKind.Enum:
                // Enums are like ints -- always immutable
                return(true);

            case TypeKind.Array:
                diagnostic = Diagnostic.Create(
                    Diagnostics.ArraysAreMutable,
                    getLocation(),
                    (query.Type as IArrayTypeSymbol).ElementType.Name
                    );

                return(false);

            case TypeKind.Delegate:
                diagnostic = Diagnostic.Create(
                    Diagnostics.DelegateTypesPossiblyMutable,
                    getLocation()
                    );

                return(false);

            case TypeKind.Dynamic:
                diagnostic = Diagnostic.Create(
                    Diagnostics.DynamicObjectsAreMutable,
                    getLocation()
                    );

                return(false);

            case TypeKind.TypeParameter:
                if (GetImmutabilityFromAttributes(query.Type).HasFlag(ImmutableTypeKind.Total))
                {
                    return(true);
                }

                if (query.Type is ITypeParameterSymbol tp && m_conditionalTypeParameters.Contains(tp))
                {
                    return(true);
                }

                diagnostic = Diagnostic.Create(
                    Diagnostics.TypeParameterIsNotKnownToBeImmutable,
                    getLocation(),
                    query.Type.ToDisplayString()
                    );

                return(false);

            case TypeKind.Class:
                diagnostic = Diagnostic.Create(
                    Diagnostics.NonImmutableTypeHeldByImmutable,
                    getLocation(),
                    query.Type.TypeKind.ToString().ToLower(),
                    query.Type.ToDisplayString(),
                    query.Kind == ImmutableTypeKind.Instance && !query.Type.IsSealed ? " (or [ImmutableBaseClass])" : ""
                    );

                return(false);

            case TypeKind.Interface:
            case TypeKind.Struct:
                diagnostic = Diagnostic.Create(
                    Diagnostics.NonImmutableTypeHeldByImmutable,
                    getLocation(),
                    query.Type.TypeKind.ToString().ToLower(),
                    query.Type.ToDisplayString(),
                    ""
                    );

                return(false);

            case TypeKind.Unknown:
            default:
                diagnostic = Diagnostic.Create(
                    Diagnostics.UnexpectedTypeKind,
                    location: getLocation(),
                    query.Type.Kind
                    );

                return(false);
            }
        }