diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs index 98c021c8a6d6..cc713b5ad2f3 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs @@ -77,7 +77,7 @@ public override void Populate(TextWriter trapFile) } public new static Accessor Create(Context cx, IMethodSymbol symbol) => - AccessorFactory.Instance.CreateEntity(cx, symbol); + AccessorFactory.Instance.CreateEntityFromSymbol(cx, symbol); class AccessorFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs index 298f838cfb93..a9baacbf7287 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs @@ -104,7 +104,7 @@ ConstructorDeclarationSyntax Syntax { case MethodKind.StaticConstructor: case MethodKind.Constructor: - return ConstructorFactory.Instance.CreateEntity(cx, constructor); + return ConstructorFactory.Instance.CreateEntityFromSymbol(cx, constructor); default: throw new InternalError(constructor, "Attempt to create a Constructor from a symbol that isn't a constructor"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Conversion.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Conversion.cs index e365ca53ae0c..98758fb773f6 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Conversion.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Conversion.cs @@ -11,7 +11,7 @@ class Conversion : UserOperator : base(cx, init) { } public new static Conversion Create(Context cx, IMethodSymbol symbol) => - ConversionFactory.Instance.CreateEntity(cx, symbol); + ConversionFactory.Instance.CreateEntityFromSymbol(cx, symbol); public override Microsoft.CodeAnalysis.Location ReportingLocation { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Destructor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Destructor.cs index 33f3a330f944..65557b16b67f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Destructor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Destructor.cs @@ -24,7 +24,7 @@ public override void Populate(TextWriter trapFile) } public new static Destructor Create(Context cx, IMethodSymbol symbol) => - DestructorFactory.Instance.CreateEntity(cx, symbol); + DestructorFactory.Instance.CreateEntityFromSymbol(cx, symbol); class DestructorFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs index 224c81b9524b..59f937a6331e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs @@ -60,7 +60,7 @@ public override void Populate(TextWriter trapFile) TypeMention.Create(Context, syntaxType, this, type); } - public static Event Create(Context cx, IEventSymbol symbol) => EventFactory.Instance.CreateEntity(cx, symbol); + public static Event Create(Context cx, IEventSymbol symbol) => EventFactory.Instance.CreateEntityFromSymbol(cx, symbol); class EventFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs index 7dc0a4dfcffa..da858b689dc3 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs @@ -53,7 +53,7 @@ public override void Populate(TextWriter trapFile) } public new static EventAccessor Create(Context cx, IMethodSymbol symbol) => - EventAccessorFactory.Instance.CreateEntity(cx, symbol); + EventAccessorFactory.Instance.CreateEntityFromSymbol(cx, symbol); class EventAccessorFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs index 7ae4572cd7d0..f126b7ff52f3 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs @@ -18,7 +18,7 @@ class Field : CachedSymbol, IExpressionParentEntity type = new Lazy(() => Entities.Type.Create(cx, symbol.GetAnnotatedType())); } - public static Field Create(Context cx, IFieldSymbol field) => FieldFactory.Instance.CreateEntity(cx, field); + public static Field Create(Context cx, IFieldSymbol field) => FieldFactory.Instance.CreateEntityFromSymbol(cx, field); // Do not populate backing fields. // Populate Tuple fields. diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs index b9ece3d8c8e1..af07c41dd6b7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs @@ -71,7 +71,7 @@ public override void Populate(TextWriter trapFile) TypeMention.Create(Context, syntax.Type, this, type); } - public static new Indexer Create(Context cx, IPropertySymbol prop) => IndexerFactory.Instance.CreateEntity(cx, prop); + public static new Indexer Create(Context cx, IPropertySymbol prop) => IndexerFactory.Instance.CreateEntityFromSymbol(cx, prop); public override void WriteId(TextWriter trapFile) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs index e570802ab3d8..8b90f6905dd3 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs @@ -22,7 +22,7 @@ public override void WriteQuotedId(TextWriter trapFile) trapFile.Write('*'); } - public static new LocalFunction Create(Context cx, IMethodSymbol field) => LocalFunctionFactory.Instance.CreateEntity(cx, field); + public static new LocalFunction Create(Context cx, IMethodSymbol field) => LocalFunctionFactory.Instance.CreateEntityFromSymbol(cx, field); class LocalFunctionFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs index 6d0f14a014eb..02d89d08a2f7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs @@ -43,7 +43,7 @@ symbol is ILocalSymbol l ? public static LocalVariable Create(Context cx, ISymbol local) { - return LocalVariableFactory.Instance.CreateEntity(cx, local); + return LocalVariableFactory.Instance.CreateEntityFromSymbol(cx, local); } void DefineConstantValue(TextWriter trapFile) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs index 982d21b25698..dbcae8f1bfe9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs @@ -129,7 +129,7 @@ protected static void BuildMethodId(Method m, TextWriter trapFile) // Type arguments with different nullability can result in // a constructed method with different nullability of its parameters and return type, // so we need to create a distinct database entity for it. - trapFile.BuildList(",", m.symbol.GetAnnotatedTypeArguments(), (ta, tb0) => { AddSignatureTypeToId(m.Context, tb0, m.symbol, ta.Symbol); trapFile.Write((int)ta.Nullability); }); + trapFile.BuildList(",", m.symbol.GetAnnotatedTypeArguments(), (ta, tb0) => { ta.Symbol.BuildNestedTypeId(m.Context, tb0, m.symbol); trapFile.Write((int)ta.Nullability); }); trapFile.Write('>'); } } @@ -163,50 +163,6 @@ public override void WriteId(TextWriter trapFile) BuildMethodId(this, trapFile); } - /// - /// Adds an appropriate label ID to the trap builder - /// for the type belonging to the signature of method - /// . - /// - /// For methods without type parameters this will always add the key of the - /// corresponding type. - /// - /// For methods with type parameters, this will add the key of the - /// corresponding type if the type does *not* contain one of the method - /// type parameters, otherwise it will add a textual representation of - /// the type. This distinction is required because type parameter IDs - /// refer to their declaring methods. - /// - /// Example: - /// - /// - /// int Count<T>(IEnumerable items) - /// - /// - /// The label definitions for Count (#4) and T - /// (#5) will look like: - /// - /// - /// #1=<label for System.Int32> - /// #2=<label for type containing Count> - /// #3=<label for IEnumerable`1> - /// #4=@"{#1} {#2}.Count`2(#3);method" - /// #5=@"{#4}T;typeparameter" - /// - /// - /// Note how int is referenced in the label definition #3 for - /// Count, while T[] is represented textually in order - /// to make the reference to #3 in the label definition #4 for - /// T valid. - /// - protected static void AddSignatureTypeToId(Context cx, TextWriter trapFile, IMethodSymbol method, ITypeSymbol type) - { - if (type.ContainsTypeParameters(cx, method)) - type.BuildTypeId(cx, trapFile, (cx0, tb0, type0) => AddSignatureTypeToId(cx, tb0, method, type0)); - else - trapFile.WriteSubId(Type.Create(cx, type)); - } - protected static void AddParametersToId(Context cx, TextWriter trapFile, IMethodSymbol method) { trapFile.Write('('); @@ -215,13 +171,13 @@ protected static void AddParametersToId(Context cx, TextWriter trapFile, IMethod if (method.MethodKind == MethodKind.ReducedExtension) { trapFile.WriteSeparator(",", ref index); - AddSignatureTypeToId(cx, trapFile, method, method.ReceiverType); + method.ReceiverType.BuildNestedTypeId(cx, trapFile, method); } foreach (var param in method.Parameters) { trapFile.WriteSeparator(",", ref index); - AddSignatureTypeToId(cx, trapFile, method, param.Type); + param.Type.BuildNestedTypeId(cx, trapFile, method); switch (param.RefKind) { case RefKind.Out: @@ -244,9 +200,10 @@ protected static void AddParametersToId(Context cx, TextWriter trapFile, IMethod public static void AddExplicitInterfaceQualifierToId(Context cx, System.IO.TextWriter trapFile, IEnumerable explicitInterfaceImplementations) { - if (explicitInterfaceImplementations.Any()) + foreach (var i in explicitInterfaceImplementations) { - trapFile.AppendList(",", explicitInterfaceImplementations.Select(impl => cx.CreateEntity(impl.ContainingType))); + trapFile.Write(';'); + i.ContainingType.BuildNestedTypeId(cx, trapFile, null); } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs index cb12918e72e7..74e7de35a162 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs @@ -49,7 +49,7 @@ public override void Populate(TextWriter trapFile) ExtractCompilerGenerated(trapFile); } - public new static OrdinaryMethod Create(Context cx, IMethodSymbol method) => OrdinaryMethodFactory.Instance.CreateEntity(cx, method); + public new static OrdinaryMethod Create(Context cx, IMethodSymbol method) => OrdinaryMethodFactory.Instance.CreateEntityFromSymbol(cx, method); class OrdinaryMethodFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs index e5e2cc8ab261..05ff97db8050 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs @@ -113,7 +113,7 @@ public static Property Create(Context cx, IPropertySymbol prop) { bool isIndexer = prop.IsIndexer || prop.Parameters.Any(); - return isIndexer ? Indexer.Create(cx, prop) : PropertyFactory.Instance.CreateEntity(cx, prop); + return isIndexer ? Indexer.Create(cx, prop) : PropertyFactory.Instance.CreateEntityFromSymbol(cx, prop); } public void VisitDeclaration(Context cx, PropertyDeclarationSyntax p) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs index c4a79a7764b0..9e2116603441 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs @@ -36,7 +36,7 @@ public override void WriteId(TextWriter trapFile) trapFile.Write(";type"); } - public static ArrayType Create(Context cx, IArrayTypeSymbol symbol) => ArrayTypeFactory.Instance.CreateEntity(cx, symbol); + public static ArrayType Create(Context cx, IArrayTypeSymbol symbol) => ArrayTypeFactory.Instance.CreateEntityFromSymbol(cx, symbol); class ArrayTypeFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs index f5b6bf36c440..bfaba1cff85c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs @@ -9,7 +9,7 @@ class DynamicType : Type DynamicType(Context cx, IDynamicTypeSymbol init) : base(cx, init) { } - public static DynamicType Create(Context cx, IDynamicTypeSymbol type) => DynamicTypeFactory.Instance.CreateEntity(cx, type); + public static DynamicType Create(Context cx, IDynamicTypeSymbol type) => DynamicTypeFactory.Instance.CreateEntityFromSymbol(cx, type); public override Microsoft.CodeAnalysis.Location ReportingLocation => Context.Compilation.ObjectType.Locations.FirstOrDefault(); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs index 93c13726750e..cc43687b3101 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs @@ -17,7 +17,7 @@ class NamedType : Type typeArgumentsLazy = new Lazy(() => symbol.TypeArguments.Select(t => Create(cx, t)).ToArray()); } - public static NamedType Create(Context cx, INamedTypeSymbol type) => NamedTypeFactory.Instance.CreateEntity(cx, type); + public static NamedType Create(Context cx, INamedTypeSymbol type) => NamedTypeFactory.Instance.CreateEntityFromSymbol(cx, type); public override bool NeedsPopulation => base.NeedsPopulation || symbol.TypeKind == TypeKind.Error; @@ -29,7 +29,8 @@ public override void Populate(TextWriter trapFile) return; } - trapFile.typeref_type((NamedTypeRef)TypeRef, this); + if (UsesTypeRef) + trapFile.typeref_type((NamedTypeRef)TypeRef, this); if (symbol.IsGenericType) { @@ -108,7 +109,7 @@ public override IEnumerable Locations public override void WriteId(TextWriter trapFile) { - symbol.BuildTypeId(Context, trapFile, (cx0, tb0, sub) => tb0.WriteSubId(Create(cx0, sub))); + symbol.BuildTypeId(Context, trapFile, true, symbol, (cx0, tb0, sub, _) => tb0.WriteSubId(Create(cx0, sub))); trapFile.Write(";type"); } @@ -148,7 +149,14 @@ class NamedTypeFactory : ICachedEntityFactory public NamedType Create(Context cx, INamedTypeSymbol init) => new NamedType(cx, init); } - public override Type TypeRef => NamedTypeRef.Create(Context, symbol); + // Do not create typerefs of constructed generics as they are always in the current trap file, and there is a possibility + // that a generic could make the typedef ambiguous which leads to performance problems in QL. + // Create typerefs for constructed error types in case they are fully defined elsewhere. + // We cannot use `!this.NeedsPopulation` because this would not be stable as it would depend on + // the assembly that was being extracted at the time. + bool UsesTypeRef => symbol.TypeKind == TypeKind.Error || SymbolEqualityComparer.Default.Equals(symbol.OriginalDefinition, symbol); + + public override Type TypeRef => UsesTypeRef ? (Type)NamedTypeRef.Create(Context, symbol) : this; } class NamedTypeRef : Type @@ -174,7 +182,7 @@ class NamedTypeRefFactory : ICachedEntityFactory public override void WriteId(TextWriter trapFile) { - trapFile.WriteSubId(referencedType); + referencedType.symbol.BuildNestedTypeId(Context, trapFile, referencedType.symbol); trapFile.Write(";typeRef"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/PointerType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/PointerType.cs index db8b5ff8c23b..229666e6260e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/PointerType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/PointerType.cs @@ -29,7 +29,7 @@ public override void Populate(TextWriter trapFile) public Type PointedAtType { get; private set; } - public static PointerType Create(Context cx, IPointerTypeSymbol symbol) => PointerTypeFactory.Instance.CreateEntity(cx, symbol); + public static PointerType Create(Context cx, IPointerTypeSymbol symbol) => PointerTypeFactory.Instance.CreateEntityFromSymbol(cx, symbol); class PointerTypeFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs index de6ea2170849..ae9d3139a5c2 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs @@ -13,7 +13,7 @@ namespace Semmle.Extraction.CSharp.Entities /// class TupleType : Type { - public static TupleType Create(Context cx, INamedTypeSymbol type) => TupleTypeFactory.Instance.CreateEntity(cx, type); + public static TupleType Create(Context cx, INamedTypeSymbol type) => TupleTypeFactory.Instance.CreateEntityFromSymbol(cx, type); class TupleTypeFactory : ICachedEntityFactory { @@ -32,7 +32,7 @@ class TupleTypeFactory : ICachedEntityFactory public override void WriteId(TextWriter trapFile) { - symbol.BuildTypeId(Context, trapFile, (cx0, tb0, sub) => tb0.WriteSubId(Create(cx0, sub))); + symbol.BuildTypeId(Context, trapFile, false, symbol, (cx0, tb0, sub, _) => tb0.WriteSubId(Create(cx0, sub))); trapFile.Write(";tuple"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs index 7d9f5e3651e8..857d3def9456 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs @@ -324,6 +324,14 @@ public virtual IEnumerable TypeMentions } public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel; + + public override bool Equals(object obj) + { + var other = obj as Type; + return other?.GetType() == GetType() && SymbolEqualityComparer.IncludeNullability.Equals(other.symbol, symbol); + } + + public override int GetHashCode() => SymbolEqualityComparer.IncludeNullability.GetHashCode(symbol); } abstract class Type : Type where T : ITypeSymbol diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs index 2fdb8cd3a834..2c7c63731434 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs @@ -88,7 +88,7 @@ public override void Populate(TextWriter trapFile) } static public TypeParameter Create(Context cx, ITypeParameterSymbol p) => - TypeParameterFactory.Instance.CreateEntity(cx, p); + TypeParameterFactory.Instance.CreateEntityFromSymbol(cx, p); /// /// The variance of this type parameter. diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs index 2a64a29fa0d2..bebcffd4dd7a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs @@ -51,7 +51,7 @@ public override Type ContainingType public override void WriteId(TextWriter trapFile) { - AddSignatureTypeToId(Context, trapFile, symbol, symbol.ReturnType); // Needed for op_explicit(), which differs only by return type. + symbol.ReturnType.BuildNestedTypeId(Context, trapFile, symbol); // Needed for op_explicit(), which differs only by return type. trapFile.Write(' '); BuildMethodId(this, trapFile); } @@ -190,7 +190,7 @@ public static string OperatorSymbol(Context cx, string methodName) return result; } - public new static UserOperator Create(Context cx, IMethodSymbol symbol) => UserOperatorFactory.Instance.CreateEntity(cx, symbol); + public new static UserOperator Create(Context cx, IMethodSymbol symbol) => UserOperatorFactory.Instance.CreateEntityFromSymbol(cx, symbol); class UserOperatorFactory : ICachedEntityFactory { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs b/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs index 6050ad910a52..17a493771c63 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs @@ -122,6 +122,24 @@ public static bool ContainsTypeParameters(this ITypeSymbol type, Context cx, ISy } } + /// + /// Write the identifier for the symbol to the trapfile . + /// If any nested types are found in the identifier, then they are written out explicitly, without + /// prefixing the assembly ID. + /// + /// The type to write. + /// The extraction context. + /// The trap file to write to. + /// The outer symbol being defined (to avoid recursive ids). + public static void BuildNestedTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined) + { + void WriteType(Context cx, TextWriter trapFile, ITypeSymbol symbol, ISymbol symbolBeingDefined) + { + symbol.BuildTypeId(cx, trapFile, false, symbolBeingDefined, WriteType); + } + WriteType(cx, trapFile, type, symbolBeingDefined); + } + /// /// Constructs a unique string for this type symbol. /// @@ -130,10 +148,12 @@ public static bool ContainsTypeParameters(this ITypeSymbol type, Context cx, ISy /// /// The extraction context. /// The trap builder used to store the result. + /// Whether to prefix the type ID with the assembly ID. + /// The outer symbol being defined (to avoid recursive ids). /// The action to apply to syntactic sub terms of this type. - public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, Action subTermAction) + public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, bool prefix, ISymbol symbolBeingDefined, Action subTermAction) { - if (type.SpecialType != SpecialType.None) + if (type.SpecialType != SpecialType.None && !(type is INamedTypeSymbol n && n.IsGenericType)) { /* * Use the keyword ("int" etc) for the built-in types. @@ -150,7 +170,7 @@ public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter tra { case TypeKind.Array: var array = (IArrayTypeSymbol)type; - subTermAction(cx, trapFile, array.ElementType); + subTermAction(cx, trapFile, array.ElementType, symbolBeingDefined); array.BuildArraySuffix(trapFile); return; case TypeKind.Class: @@ -160,15 +180,30 @@ public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter tra case TypeKind.Delegate: case TypeKind.Error: var named = (INamedTypeSymbol)type; - named.BuildNamedTypeId(cx, trapFile, subTermAction); + named.BuildNamedTypeId(cx, trapFile, prefix, symbolBeingDefined, subTermAction); return; case TypeKind.Pointer: var ptr = (IPointerTypeSymbol)type; - subTermAction(cx, trapFile, ptr.PointedAtType); + subTermAction(cx, trapFile, ptr.PointedAtType, symbolBeingDefined); trapFile.Write('*'); return; case TypeKind.TypeParameter: var tp = (ITypeParameterSymbol)type; + if (!SymbolEqualityComparer.Default.Equals(tp.ContainingSymbol, symbolBeingDefined)) + { + switch (tp.TypeParameterKind) + { + case TypeParameterKind.Method: + var method = Method.Create(cx, (IMethodSymbol)tp.ContainingSymbol); + trapFile.WriteSubId(method); + trapFile.Write('_'); + break; + case TypeParameterKind.Type: + subTermAction(cx, trapFile, tp.ContainingType, symbolBeingDefined); + trapFile.Write('_'); + break; + } + } trapFile.Write(tp.Name); return; case TypeKind.Dynamic: @@ -211,9 +246,8 @@ private static void BuildAssembly(IAssemblySymbol asm, TextWriter trapFile, bool trapFile.Write("::"); } - static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, Action subTermAction) + static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, bool prefixAssembly, ISymbol symbolBeingDefined, Action subTermAction) { - bool prefixAssembly = true; if (named.ContainingAssembly is null) prefixAssembly = false; if (named.IsTupleType) @@ -224,7 +258,7 @@ static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter { trapFile.Write(f.Name); trapFile.Write(":"); - subTermAction(cx, tb0, f.Type); + subTermAction(cx, tb0, f.Type, symbolBeingDefined); } ); trapFile.Write(")"); @@ -233,7 +267,7 @@ static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter if (named.ContainingType != null) { - subTermAction(cx, trapFile, named.ContainingType); + subTermAction(cx, trapFile, named.ContainingType, symbolBeingDefined); trapFile.Write('.'); } else if (named.ContainingNamespace != null) @@ -255,14 +289,14 @@ static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter } else { - subTermAction(cx, trapFile, named.ConstructedFrom); + subTermAction(cx, trapFile, named.ConstructedFrom, symbolBeingDefined); trapFile.Write('<'); // Encode the nullability of the type arguments in the label. // Type arguments with different nullability can result in // a constructed type with different nullability of its members and methods, // so we need to create a distinct database entity for it. trapFile.BuildList(",", named.GetAnnotatedTypeArguments(), - (ta, tb0) => subTermAction(cx, tb0, ta.Symbol) + (ta, tb0) => subTermAction(cx, tb0, ta.Symbol, symbolBeingDefined) ); trapFile.Write('>'); } @@ -274,16 +308,16 @@ static void BuildNamespace(this INamespaceSymbol ns, Context cx, TextWriter trap trapFile.Write('.'); } - static void BuildAnonymousName(this ITypeSymbol type, Context cx, TextWriter trapFile, Action subTermAction, bool includeParamName) + static void BuildAnonymousName(this ITypeSymbol type, Context cx, TextWriter trapFile, Action subTermAction, bool includeParamName) { var buildParam = includeParamName ? (prop, tb0) => { tb0.Write(prop.Name); tb0.Write(' '); - subTermAction(cx, tb0, prop.Type); + subTermAction(cx, tb0, prop.Type, null); } - : (Action)((prop, tb0) => subTermAction(cx, tb0, prop.Type)); + : (Action)((prop, tb0) => subTermAction(cx, tb0, prop.Type, null)); int memberCount = type.GetMembers().OfType().Count(); int hackTypeNumber = memberCount == 1 ? 1 : 0; trapFile.Write("<>__AnonType"); @@ -355,7 +389,7 @@ public static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, Co if (namedType.IsAnonymousType) { - namedType.BuildAnonymousName(cx, trapFile, (cx0, tb0, sub) => sub.BuildDisplayName(cx0, tb0), false); + namedType.BuildAnonymousName(cx, trapFile, (cx0, tb0, sub, _) => sub.BuildDisplayName(cx0, tb0), false); } trapFile.Write(namedType.Name); diff --git a/csharp/extractor/Semmle.Extraction/CommentProcessing.cs b/csharp/extractor/Semmle.Extraction/CommentProcessing.cs index 2067f8694717..6cc122e92c11 100644 --- a/csharp/extractor/Semmle.Extraction/CommentProcessing.cs +++ b/csharp/extractor/Semmle.Extraction/CommentProcessing.cs @@ -35,7 +35,7 @@ public void AddComment(ICommentLine comment) class LocationComparer : IComparer { - public int Compare(Location l1, Location l2) => CommentProcessor.Compare(l1, l2); + int IComparer.Compare(Location? l1, Location? l2) => l1 is null ? (l2 is null ? 0 : -1) : l2 is null ? 1 : CommentProcessor.Compare(l1, l2); } /// diff --git a/csharp/extractor/Semmle.Extraction/Context.cs b/csharp/extractor/Semmle.Extraction/Context.cs index 3e55c1068e2c..1ec2f0db14aa 100644 --- a/csharp/extractor/Semmle.Extraction/Context.cs +++ b/csharp/extractor/Semmle.Extraction/Context.cs @@ -62,7 +62,7 @@ public Entity CreateEntity(ICachedEntityFactory fact /// The new/existing entity. public Entity CreateNullableEntity(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity { - return init == null ? CreateEntity2(factory, init) : CreateNonNullEntity(factory, init); + return init == null ? CreateEntity2(factory, init) : CreateNonNullEntity(factory, init, objectEntityCache); } /// @@ -75,7 +75,7 @@ public Entity CreateEntityFromSymbol(ICachedEntityFactory(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity + where Type : notnull + => CreateNonNullEntity(factory, init, objectEntityCache); + + private Entity CreateNonNullEntity(ICachedEntityFactory factory, Type init, IDictionary dictionary) + where Entity : ICachedEntity { if (init is null) throw new ArgumentException("Unexpected null value", nameof(init)); - if (objectEntityCache.TryGetValue(init, out var cached)) + if (dictionary.TryGetValue(init, out var cached)) return (Entity)cached; using (StackGuard) @@ -173,7 +178,7 @@ public Entity CreateNonNullEntity(ICachedEntityFactory idLabelCache = new Dictionary(); #endif - readonly Dictionary objectEntityCache = new Dictionary(); + + // Adaptor to convert IEqualityComparer to IEqualityComparer + class SymbolComparer : IEqualityComparer + { + IEqualityComparer comparer = SymbolEqualityComparer.IncludeNullability; + + bool IEqualityComparer.Equals(object? x, object? y) => comparer.Equals(x as ISymbol, y as ISymbol); + + int IEqualityComparer.GetHashCode(object obj) => comparer.GetHashCode((ISymbol)obj); + } + + readonly IDictionary objectEntityCache = new Dictionary(); + readonly IDictionary symbolEntityCache = new Dictionary(10000, new SymbolComparer()); readonly Dictionary entityLabelCache = new Dictionary(); readonly HashSet