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_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);
        }
        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_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);
        }
        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_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);
        }
        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);
        }
        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 string Format(MutabilityInspectionResult result)
        {
            if (!result.IsMutable)
            {
                return(string.Empty);
            }

            var targetString = FormatTarget(result);
            var causeString  = FormatCause(result);

            var formattedResult = $"{targetString} is {causeString}";

            return(formattedResult);
        }
 public void NonReadOnlyProperty_Diagnostic()
 {
     const string test = @"
      namespace test {
     class tests {
     public static int PropertyWithSetter { get; set; }
     }
     }";
     AssertSingleDiagnostic( s_preamble + test, 15, 9, "PropertyWithSetter", MutabilityInspectionResult.Mutable(
         mutableMemberPath: "PropertyWithSetter",
         membersTypeName: "Widget",
         kind: MutabilityTarget.Member,
         cause: MutabilityCause.IsNotReadonly
     ) );
 }
        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 void AssertSingleDiagnostic(string file, int line, int column, MutabilityInspectionResult result)
        {
            var reason  = m_formatter.Format(result);
            var message = string.Format(Diagnostics.ImmutableClassIsnt.MessageFormat.ToString(), reason);

            var expected = new DiagnosticResult {
                Id        = Diagnostics.ImmutableClassIsnt.Id,
                Message   = message,
                Severity  = DiagnosticSeverity.Error,
                Locations = new[] {
                    new DiagnosticResultLocation("Test0.cs", line, column)
                }
            };

            VerifyCSharpDiagnostic(file, expected);
        }
        public void InspectType_ArrayType_True()
        {
            var field    = Field("int[] random");
            var expected = MutabilityInspectionResult.Mutable(
                null,
                "System.Int32[]",
                MutabilityTarget.Type,
                MutabilityCause.IsAnArray
                );

            var inspector = new MutabilityInspector(field.Compilation);

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

            AssertResultsAreEqual(expected, actual);
        }
예제 #16
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 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));
        }
        public void InspectType_Interface_True()
        {
            var type = Type("interface foo {}");

            var inspector = new MutabilityInspector(type.Compilation);

            var expected = MutabilityInspectionResult.Mutable(
                null,
                $"{RootNamespace}.foo",
                MutabilityTarget.Type,
                MutabilityCause.IsAnInterface
                );

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

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_LooksAtPropertiesInNonExternalType()
        {
            var prop = Property("public string random { get; set; }");

            var inspector = new MutabilityInspector(prop.Compilation);

            var expected = MutabilityInspectionResult.Mutable(
                "random",
                "System.String",
                MutabilityTarget.Member,
                MutabilityCause.IsNotReadonly
                );

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

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_TypeWithFuncProperty_ReturnsMutable()
        {
            var prop = Property("public Func<string> StringGetter { get; }");

            var inspector = new MutabilityInspector(prop.Compilation);

            var expected = MutabilityInspectionResult.Mutable(
                "StringGetter",
                "System.Func",
                MutabilityTarget.Type,
                MutabilityCause.IsADelegate
                );

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

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

            var inspector = new MutabilityInspector(field.Compilation);

            var expected = MutabilityInspectionResult.Mutable(
                "random",
                "System.String",
                MutabilityTarget.Member,
                MutabilityCause.IsNotReadonly
                );

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

            AssertResultsAreEqual(expected, actual);
        }
        public void InspectType_DoesNotLookAtMembersInExternalType()
        {
            var field = Field("public readonly System.Text.StringBuilder random");

            var inspector = new MutabilityInspector(field.Compilation);

            var expected = MutabilityInspectionResult.Mutable(
                null,
                "System.Text.StringBuilder",
                MutabilityTarget.Type,
                MutabilityCause.IsAnExternalUnmarkedType
                );

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

            AssertResultsAreEqual(expected, actual);
        }
        public void DocumentWithStaticImmutableCollectionField_GenericObject_Diag()
        {
            const string test = @"
            using System;

            namespace test {
            class Tests {
            public static readonly System.Collections.Immutable.ImmutableList<object> bad;

            }
            }";
            AssertSingleDiagnostic( s_preamble + test, 17, 87, "bad", MutabilityInspectionResult.Mutable(
                mutableMemberPath: "bad",
                membersTypeName: "System.Object",
                kind: MutabilityTarget.TypeArgument,
                cause: MutabilityCause.IsNotSealed
            ) );
        }
        public void DocumentWithStaticCollectionField_NonGeneric_Diag()
        {
            const string test = @"
            using System;

            namespace test {
            class Tests {
            public static readonly System.Collections.IList bad;

            }
            }";
            AssertSingleDiagnostic( s_preamble + test, 17, 61, "bad", MutabilityInspectionResult.Mutable(
                mutableMemberPath: "bad",
                membersTypeName: "System.Collections.IList",
                kind: MutabilityTarget.Type,
                cause: MutabilityCause.IsAnInterface
            ) );
        }
        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 DocumentWithStatic_ClassIsNotImmutableButImplementsImmutableInterface_Diag()
        {
            const string test = @"
	namespace test {
		[Immutable] interface IFoo {} 
		class Test : IFoo {

			public DateTime bad = DateTime.Now;
			public DateTime badToo { get; set; }

		}
	}"    ;

            AssertSingleDiagnostic(s_preamble + test, 13, 9, MutabilityInspectionResult.Mutable(
                                       "bad",
                                       "System.DateTime",
                                       MutabilityTarget.Member,
                                       MutabilityCause.IsNotReadonly
                                       ));
        }
        public void InspectType_NonSealedClass_True()
        {
            var type = Type("class foo {}");

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

            var expected = MutabilityInspectionResult.Mutable(
                null,
                $"{RootNamespace}.foo",
                MutabilityTarget.Type,
                MutabilityCause.IsNotSealed
                );

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

            AssertResultsAreEqual(expected, actual);
        }
        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
                       ));
        }