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)); }
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" ); } }