private bool IsMemberMutableRecursive(
            ISymbol symbol,
            MutabilityInspectionFlags flags,
            HashSet <ITypeSymbol> typeStack
            )
        {
            switch (symbol.Kind)
            {
            case SymbolKind.Property:

                var prop = (IPropertySymbol)symbol;

                var sourceTree        = prop.DeclaringSyntaxReferences.FirstOrDefault();
                var declarationSyntax = sourceTree?.GetSyntax() as PropertyDeclarationSyntax;
                if (declarationSyntax != null && declarationSyntax.IsPropertyGetterImplemented())
                {
                    // property has getter with body; it is either backed by a field, or is a static function; ignore
                    return(false);
                }

                if (IsPropertyMutable(prop))
                {
                    return(true);
                }

                if (IsTypeMutableRecursive(prop.Type, flags, typeStack))
                {
                    return(true);
                }

                return(false);

            case SymbolKind.Field:

                var field = (IFieldSymbol)symbol;

                if (IsFieldMutable(field))
                {
                    return(true);
                }

                if (IsTypeMutableRecursive(field.Type, flags, typeStack))
                {
                    return(true);
                }

                return(false);

            case SymbolKind.Method:
            case SymbolKind.NamedType:
                // ignore these symbols, because they do not contribute to immutability
                return(false);

            default:
                // we've got a member (event, etc.) that we can't currently be smart about, so fail
                return(true);
            }
        }
        public MutabilityInspectionResult InspectConcreteType(
            ITypeSymbol type,
            MutabilityInspectionFlags flags = MutabilityInspectionFlags.Default
            )
        {
            var typesInCurrentCycle = new HashSet <ITypeSymbol>();

            return(InspectConcreteType(type, typesInCurrentCycle, flags));
        }
        /// <summary>
        /// Determine if a given type is mutable.
        /// </summary>
        /// <param name="type">The type to determine mutability for.</param>
        /// <returns>Whether the type is mutable.</returns>
        public bool IsTypeMutable(
            ITypeSymbol type,
            MutabilityInspectionFlags flags = MutabilityInspectionFlags.Default
            )
        {
            var typesInCurrentCycle = new HashSet <ITypeSymbol>();
            var result = IsTypeMutableRecursive(type, flags, typesInCurrentCycle);

            return(result);
        }
        private MutabilityInspectionResult InspectType(
            ITypeSymbol type,
            MutabilityInspectionFlags flags,
            HashSet <ITypeSymbol> typeStack
            )
        {
            var cacheKey = new Tuple <ITypeSymbol, MutabilityInspectionFlags>(
                type,
                flags
                );

            return(m_cache.GetOrAdd(
                       cacheKey,
                       query => InspectTypeImpl(query.Item1, query.Item2, typeStack)
                       ));
        }
Exemplo n.º 5
0
        private MutabilityInspectionResult InspectConcreteType(
            ITypeSymbol type,
            HashSet <ITypeSymbol> typeStack,
            MutabilityInspectionFlags flags = MutabilityInspectionFlags.Default
            )
        {
            if (type is IErrorTypeSymbol)
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            return(InspectType(
                       type,
                       flags | MutabilityInspectionFlags.AllowUnsealed,
                       typeStack
                       ));
        }
        private MutabilityInspectionResult InspectConcreteType(
            ITypeSymbol type,
            HashSet <ITypeSymbol> typeStack,
            MutabilityInspectionFlags flags = MutabilityInspectionFlags.Default
            )
        {
            if (type is IErrorTypeSymbol)
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            // The concrete type System.Object is empty (and therefore safe.)
            // For example, `private readonly object m_lock = new object();` is fine.
            if (type.SpecialType == SpecialType.System_Object)
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            // System.ValueType is the base class of all value types (obscure detail)
            if (type.SpecialType == SpecialType.System_ValueType)
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            var scope = type.GetImmutabilityScope();

            if (!flags.HasFlag(MutabilityInspectionFlags.IgnoreImmutabilityAttribute) && scope != ImmutabilityScope.None)
            {
                ImmutableHashSet <string> immutableExceptions = type.GetAllImmutableExceptions();
                return(MutabilityInspectionResult.NotMutable(immutableExceptions));
            }

            return(InspectType(
                       type,
                       flags | MutabilityInspectionFlags.AllowUnsealed,
                       typeStack
                       ));
        }
        private MutabilityInspectionResult InspectTypeImpl(
            ITypeSymbol type,
            MutabilityInspectionFlags flags,
            HashSet <ITypeSymbol> typeStack
            )
        {
            if (type is IErrorTypeSymbol)
            {
                // This only happens for code that otherwise won't compile. Our
                // analyzer doesn't need to validate these types. It only needs
                // to be strict for valid code.
                return(MutabilityInspectionResult.NotMutable());
            }

            if (type == null)
            {
                throw new Exception("Type cannot be resolved. Please ensure all dependencies "
                                    + "are referenced, including transitive dependencies.");
            }

            // If we're not verifying immutability, we might be able to bail out early
            var scope = type.GetImmutabilityScope();

            if (!flags.HasFlag(MutabilityInspectionFlags.IgnoreImmutabilityAttribute) && scope == ImmutabilityScope.SelfAndChildren)
            {
                ImmutableHashSet <string> immutableExceptions = type.GetAllImmutableExceptions();
                return(MutabilityInspectionResult.NotMutable(immutableExceptions));
            }

            if (m_knownImmutableTypes.IsTypeKnownImmutable(type))
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            if (IsAnImmutableContainerType(type))
            {
                return(InspectImmutableContainerType(type, typeStack));
            }

            if (!flags.HasFlag(MutabilityInspectionFlags.AllowUnsealed) &&
                type.TypeKind == TypeKind.Class &&
                !type.IsSealed
                )
            {
                return(MutabilityInspectionResult.MutableType(type, MutabilityCause.IsNotSealed));
            }

            switch (type.TypeKind)
            {
            case TypeKind.Array:
                // Arrays are always mutable because you can rebind the
                // individual elements.
                return(MutabilityInspectionResult.MutableType(
                           type,
                           MutabilityCause.IsAnArray
                           ));

            case TypeKind.Delegate:
                // Delegates can hold state so are mutable in general.
                return(MutabilityInspectionResult.MutableType(
                           type,
                           MutabilityCause.IsADelegate
                           ));

            case TypeKind.Dynamic:
                // Dynamic types are always mutable
                return(MutabilityInspectionResult.MutableType(
                           type,
                           MutabilityCause.IsDynamic
                           ));

            case TypeKind.Enum:
                // Enums are just fancy ints.
                return(MutabilityInspectionResult.NotMutable());

            case TypeKind.TypeParameter:
                return(InspectTypeParameter(
                           type,
                           typeStack
                           ));

            case TypeKind.Interface:
                return(MutabilityInspectionResult.MutableType(type, MutabilityCause.IsAnInterface));

            case TypeKind.Class:
            case TypeKind.Struct:                     // equivalent to TypeKind.Structure
                return(InspectClassOrStruct(
                           type,
                           typeStack
                           ));

            case TypeKind.Error:
                // This only happens when the build is failing for other
                // (more fundamental) reasons. We only need to be strict
                // for otherwise-successful builds, so we bail analysis in
                // this case.
                return(MutabilityInspectionResult.NotMutable());

            case TypeKind.Unknown:
                // Looking at the Roslyn source this doesn't appear to
                // happen outside their tests. It is value 0 in the enum so
                // it may just be a safety guard.
                throw new NotImplementedException();

            default:
                // not handled: Module, Pointer, Submission.
                throw new NotImplementedException(
                          $"TypeKind.{type.Kind} not handled by analysis"
                          );
            }
        }
        private bool IsTypeMutableRecursive(
            ITypeSymbol type,
            MutabilityInspectionFlags flags,
            HashSet <ITypeSymbol> typeStack
            )
        {
            if (type is IErrorTypeSymbol || type == null)
            {
                throw new Exception($"Type '{type}' cannot be resolved. Please ensure all dependencies "
                                    + "are referenced, including transitive dependencies.");
            }

            if (IsTypeKnownImmutable(type))
            {
                return(false);
            }

            if (type.TypeKind == TypeKind.Array)
            {
                return(true);
            }

            if (!flags.HasFlag(MutabilityInspectionFlags.IgnoreImmutabilityAttribute) && IsTypeMarkedImmutable(type))
            {
                return(false);
            }

            if (typeStack.Contains(type))
            {
                // We have a cycle. If we're here, it means that either some read-only member causes the cycle (via IsMemberMutableRecursive),
                // or a generic parameter to a type causes the cycle (via IsTypeMutableRecursive). This is safe if the checks above have
                // passed and the remaining members are read-only immutable. So we can skip the current check, and allow the type to continue
                // to be evaluated.
                return(false);
            }

            typeStack.Add(type);
            try {
                if (ImmutableContainerTypes.Contains(type.GetFullTypeName()))
                {
                    var  namedType = type as INamedTypeSymbol;
                    bool isMutable = namedType.TypeArguments.Any(t => IsTypeMutableRecursive(t, MutabilityInspectionFlags.Default, typeStack));
                    return(isMutable);
                }

                if (type.TypeKind == TypeKind.Interface)
                {
                    return(true);
                }

                if (!flags.HasFlag(MutabilityInspectionFlags.AllowUnsealed) &&
                    type.TypeKind == TypeKind.Class &&
                    !type.IsSealed
                    )
                {
                    return(true);
                }

                foreach (ISymbol member in type.GetNonStaticMembers())
                {
                    if (IsMemberMutableRecursive(member, MutabilityInspectionFlags.Default, typeStack))
                    {
                        return(true);
                    }
                }

                return(false);
            } finally {
                typeStack.Remove(type);
            }
        }