public void InspectType_SealedClass_False()
        {
            var type = Type("sealed class foo {}");

            var inspector = new MutabilityInspector(type.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(type.Symbol);

            AssertResultsAreEqual(expected, actual);
        }
        private void AssertUnauditedReasonsResult(TestSymbol <ITypeSymbol> ty, params string[] expectedUnauditedReasons)
        {
            var inspector = new MutabilityInspector(ty.Compilation);

            var expected = MutabilityInspectionResult.NotMutable(
                ImmutableHashSet.Create(expectedUnauditedReasons)
                );

            var actual = inspector.InspectType(ty.Symbol, MutabilityInspectionFlags.IgnoreImmutabilityAttribute);

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_NullablePrimitiveType_NotMutable()
        {
            var field = Field("uint? foo");

            var inspector = new MutabilityInspector(field.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(field.Symbol.Type);

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_IEnumerableGenericCollectionWithImmutableElement_ReturnsFalse()
        {
            var field = Field("private readonly System.Collections.Generic.IEnumerable<int> random");

            var inspector = new MutabilityInspector(field.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(field.Symbol.Type);

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_ParenthesizedLambda_NotMutable()
        {
            var field = Field("private readonly Func<int,int> m_addTwo = ( a ) => a + 2;");

            var inspector = new MutabilityInspector(field.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(field.Symbol.ContainingType);

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_LambdaInitializedFromStaticMethod_NotMutable()
        {
            var field = Field("private readonly Func<int> m_func = () => int.Parse( \"1\" );");

            var inspector = new MutabilityInspector(field.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(field.Symbol.ContainingType);

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_SimpleLambdaMember_NotMutable()
        {
            var field = Field("private readonly Func<int> m_func = () => 1;");

            var inspector = new MutabilityInspector(field.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(field.Symbol.ContainingType);

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_Enum_False()
        {
            var type = Type("enum blah {}");

            var inspector = new MutabilityInspector(type.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(type.Symbol);

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_KnownImmutableType_False()
        {
            var field = Field("string random");

            var inspector = new MutabilityInspector(field.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(field.Symbol.Type);

            AssertResultsAreEqual(expected, actual);
        }
        private MutabilityInspectionResult InspectClassOrStruct(
            ITypeSymbol type,
            HashSet <ITypeSymbol> typeStack
            )
        {
            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(MutabilityInspectionResult.NotMutable());
            }

            // We have a type that is not marked immutable, is not an interface, is not an immutable container, etc..
            // If it is defined in a different assembly, we might not have the metadata to correctly analyze it; so we fail.
            if (TypeIsFromOtherAssembly(type))
            {
                return(MutabilityInspectionResult.MutableType(type, MutabilityCause.IsAnExternalUnmarkedType));
            }

            ImmutableHashSet <string> .Builder seenUnauditedReasonsBuilder = ImmutableHashSet.CreateBuilder <string>();

            typeStack.Add(type);
            try {
                foreach (ISymbol member in type.GetExplicitNonStaticMembers())
                {
                    var result = InspectMemberRecursive(member, typeStack);
                    if (result.IsMutable)
                    {
                        return(result);
                    }
                    seenUnauditedReasonsBuilder.UnionWith(result.SeenUnauditedReasons);
                }

                // We descend into the base class last
                if (type.BaseType != null)
                {
                    var baseResult = InspectConcreteType(type.BaseType, typeStack);

                    if (baseResult.IsMutable)
                    {
                        return(baseResult);
                    }
                    seenUnauditedReasonsBuilder.UnionWith(baseResult.SeenUnauditedReasons);
                }
            } finally {
                typeStack.Remove(type);
            }

            return(MutabilityInspectionResult.NotMutable(seenUnauditedReasonsBuilder.ToImmutable()));
        }
        public void InspectType_ImmutableGenericCollectionWithValueTypeElement_ReturnsFalse()
        {
            var field = Field("private readonly System.Collections.Immutable.ImmutableArray<int> random");

            var inspector = new MutabilityInspector(
                field.Compilation,
                KnownImmutableTypes.Default
                );

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(field.Symbol.Type);

            AssertResultsAreEqual(expected, actual);
        }
        private MutabilityInspectionResult InspectProperty(
            IPropertySymbol property,
            HashSet <ITypeSymbol> typeStack
            )
        {
            if (property.IsIndexer)
            {
                // Indexer properties are just glorified method syntax and dont' hold state
                return(MutabilityInspectionResult.NotMutable());
            }

            var propertySyntax =
                GetDeclarationSyntax <PropertyDeclarationSyntax>(property);

            // TODO: can we do this without the syntax; with only the symbol?
            if (!propertySyntax.IsAutoImplemented())
            {
                // Properties that are auto-implemented have an implicit
                // backing field that may be mutable. Otherwise, properties are
                // just sugar for getter/setter methods and don't themselves
                // contribute to mutability.
                return(MutabilityInspectionResult.NotMutable());
            }

            if (!property.IsReadOnly)
            {
                return(MutabilityInspectionResult.MutableProperty(
                           property,
                           MutabilityCause.IsNotReadonly
                           ));
            }

            if (propertySyntax.Initializer != null)
            {
                return(InspectInitializer(
                           propertySyntax.Initializer.Value,
                           typeStack
                           ).WithPrefixedMember(property.Name));
            }

            return(InspectType(
                       property.Type,
                       MutabilityInspectionFlags.Default,
                       typeStack
                       ).WithPrefixedMember(property.Name));
        }
Example #13
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 InspectImmutableContainerType(
            ITypeSymbol type,
            HashSet <ITypeSymbol> typeStack
            )
        {
            var namedType = type as INamedTypeSymbol;

            ImmutableHashSet <string> .Builder unauditedReasonsBuilder = ImmutableHashSet.CreateBuilder <string>();

            for (int i = 0; i < namedType.TypeArguments.Length; i++)
            {
                var arg = namedType.TypeArguments[i];

                var result = InspectType(
                    arg,
                    MutabilityInspectionFlags.Default,
                    typeStack
                    );

                if (result.IsMutable)
                {
                    if (result.Target == MutabilityTarget.Member)
                    {
                        // modify the result to prefix with container member.
                        var prefix = ImmutableContainerTypes[type.GetFullTypeName()];
                        result = result.WithPrefixedMember(prefix[i]);
                    }
                    else
                    {
                        // modify the result to target the type argument if the
                        // target is not a member
                        result = result.WithTarget(
                            MutabilityTarget.TypeArgument
                            );
                    }
                    return(result);
                }

                unauditedReasonsBuilder.UnionWith(result.SeenUnauditedReasons);
            }

            return(MutabilityInspectionResult.NotMutable(unauditedReasonsBuilder.ToImmutable()));
        }
        public void InspectType_NullableNonPrimitiveType_NotMutable()
        {
            var type = Type(@"
				class Test {
					struct Hello { }
					Hello? nullable;
				}"
                            );
            var field = type.Symbol.GetMembers().FirstOrDefault(m => m is IFieldSymbol);

            Assert.IsNotNull(field);
            var realType = (field as IFieldSymbol).Type;

            var inspector = new MutabilityInspector(type.Compilation);

            var expected = MutabilityInspectionResult.NotMutable();

            var actual = inspector.InspectType(realType);

            AssertResultsAreEqual(expected, actual);
        }
        private MutabilityInspectionResult InspectMemberRecursive(
            ISymbol symbol,
            HashSet <ITypeSymbol> typeStack
            )
        {
            // if the member is audited or unaudited, ignore it
            if (Attributes.Mutability.Audited.IsDefined(symbol))
            {
                return(MutabilityInspectionResult.NotMutable());
            }
            if (Attributes.Mutability.Unaudited.IsDefined(symbol))
            {
                string unauditedReason = BecauseHelpers.GetUnauditedReason(symbol);
                return(MutabilityInspectionResult.NotMutable(ImmutableHashSet.Create(unauditedReason)));
            }

            switch (symbol.Kind)
            {
            case SymbolKind.Property:
                return(InspectProperty(
                           symbol as IPropertySymbol,
                           typeStack
                           ));

            case SymbolKind.Field:
                return(InspectField(
                           symbol as IFieldSymbol,
                           typeStack
                           ));

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

            default:
                // we've got a member (event, etc.) that we can't currently be smart about, so fail
                return(MutabilityInspectionResult.PotentiallyMutableMember(symbol));
            }
        }
        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 InspectInitializer(
            ExpressionSyntax expr,
            HashSet <ITypeSymbol> typeStack
            )
        {
            if (expr.Kind() == SyntaxKind.NullLiteralExpression)
            {
                // This is perhaps a bit suspicious, because fields and
                // properties have to be readonly, but it is safe...
                return(MutabilityInspectionResult.NotMutable());
            }

            var model = m_compilation.GetSemanticModel(expr.SyntaxTree);

            var typeInfo = model.GetTypeInfo(expr);

            // Type can be null in the case of an implicit conversion where
            // the expression alone doesn't have a type. For example:
            //   int[] foo = { 1, 2, 3 };
            var exprType = typeInfo.Type ?? typeInfo.ConvertedType;

            if (expr is ObjectCreationExpressionSyntax)
            {
                // If our initializer is "new Foo( ... )" we only need to
                // consider Foo concretely; not subtypes of Foo.
                return(InspectConcreteType(exprType, typeStack));
            }

            // In general we can use the initializers type in place of the
            // field/properties type because it may be narrower.
            return(InspectType(
                       exprType,
                       MutabilityInspectionFlags.Default,
                       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"
                          );
            }
        }