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