using Microsoft.CodeAnalysis; using System; using System.Reflection; namespace Semmle.Extraction.CSharp.Entities { /// /// Provide a "Key" object to allow modifiers to exist as entities in the extractor /// hash map. (Raw strings would work as keys but might clash with other types). /// class ModifierKey : Object { public readonly string name; public ModifierKey(string m) { name = m; } public override bool Equals(Object obj) { return obj.GetType() == GetType() && name == ((ModifierKey)obj).name; } public override int GetHashCode() => 13 * name.GetHashCode(); } class Modifier : Extraction.CachedEntity { Modifier(Context cx, ModifierKey init) : base(cx, init) { } public override Microsoft.CodeAnalysis.Location ReportingLocation => null; public override IId Id => new Key(symbol.name, ";modifier"); public override bool NeedsPopulation => true; public override void Populate() { Context.Emit(new Tuple("modifiers", Label, symbol.name)); } public static string AccessbilityModifier(Accessibility access) { switch (access) { case Accessibility.Private: return "private"; case Accessibility.Protected: return "protected"; case Accessibility.Public: return "public"; case Accessibility.Internal: return "internal"; default: throw new InternalError("Unavailable modifier combination"); } } public static void HasAccessibility(Context cx, IEntity type, Accessibility access) { switch (access) { case Accessibility.Private: case Accessibility.Public: case Accessibility.Protected: case Accessibility.Internal: HasModifier(cx, type, Modifier.AccessbilityModifier(access)); break; case Accessibility.NotApplicable: break; case Accessibility.ProtectedOrInternal: HasModifier(cx, type, "protected"); HasModifier(cx, type, "internal"); break; case Accessibility.ProtectedAndInternal: HasModifier(cx, type, "protected"); HasModifier(cx, type, "private"); break; default: throw new InternalError("Unhandled Microsoft.CodeAnalysis.Accessibility value: {0}", access); } } public static void HasModifier(Context cx, IEntity target, string modifier) { cx.Emit(Tuples.has_modifiers(target, Modifier.Create(cx, modifier))); } public static void ExtractModifiers(Context cx, IEntity key, ISymbol symbol) { bool interfaceDefinition = symbol.ContainingType != null && symbol.ContainingType.Kind == SymbolKind.NamedType && symbol.ContainingType.TypeKind == TypeKind.Interface; Modifier.HasAccessibility(cx, key, symbol.DeclaredAccessibility); if (symbol.Kind == SymbolKind.ErrorType) cx.Emit(Tuples.has_modifiers(key, Modifier.Create(cx, Accessibility.Public))); if (symbol.IsAbstract && (symbol.Kind != SymbolKind.NamedType || ((INamedTypeSymbol)symbol).TypeKind != TypeKind.Interface) && !interfaceDefinition) Modifier.HasModifier(cx, key, "abstract"); if (symbol.IsSealed) HasModifier(cx, key, "sealed"); bool fromSource = symbol.DeclaringSyntaxReferences.Length > 0; if (symbol.IsStatic && !(symbol.Kind == SymbolKind.Field && ((IFieldSymbol)symbol).IsConst && !fromSource)) HasModifier(cx, key, "static"); if (symbol.IsVirtual) HasModifier(cx, key, "virtual"); // For some reason, method in interfaces are "virtual", not "abstract" if (symbol.IsAbstract && interfaceDefinition) HasModifier(cx, key, "virtual"); if (symbol.Kind == SymbolKind.Field && ((IFieldSymbol)symbol).IsReadOnly) HasModifier(cx, key, "readonly"); if (symbol.IsOverride) HasModifier(cx, key, "override"); if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).IsAsync) HasModifier(cx, key, "async"); if (symbol.IsExtern) HasModifier(cx, key, "extern"); foreach (var modifier in symbol.GetSourceLevelModifiers()) HasModifier(cx, key, modifier); if (symbol.Kind == SymbolKind.NamedType) { INamedTypeSymbol nt = symbol as INamedTypeSymbol; if (nt.TypeKind == TypeKind.Struct) { // Sadly, these properties are internal so cannot be accessed directly. // This seems to be a deficiency in the model. var readonlyProperty = nt.GetType().GetProperty("IsReadOnly", BindingFlags.NonPublic | BindingFlags.Instance); var isByRefProperty = nt.GetType().GetProperty("IsByRefLikeType", BindingFlags.NonPublic | BindingFlags.Instance); bool isReadOnly = (bool)readonlyProperty.GetValue(nt); bool isByRefLikeType = (bool)isByRefProperty.GetValue(nt); if (isReadOnly) HasModifier(cx, key, "readonly"); if (isByRefLikeType) HasModifier(cx, key, "ref"); } } } public static Modifier Create(Context cx, string modifier) => ModifierFactory.Instance.CreateEntity(cx, new ModifierKey(modifier)); public static Modifier Create(Context cx, Accessibility access) => ModifierFactory.Instance.CreateEntity(cx, new ModifierKey(AccessbilityModifier(access))); class ModifierFactory : ICachedEntityFactory { public static readonly ModifierFactory Instance = new ModifierFactory(); public Modifier Create(Context cx, ModifierKey init) => new Modifier(cx, init); } public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel; } }