C#: Take nullability into account when creating symbol entities. Otherwise, an entity with the wrong (cached) nullability could be created.

This commit is contained in:
Calum Grant
2020-04-20 09:32:35 +01:00
committed by Tom Hvitved
parent d1cde2a815
commit cd51a67c0d
23 changed files with 59 additions and 24 deletions

View File

@@ -77,7 +77,7 @@ namespace Semmle.Extraction.CSharp.Entities
}
public new static Accessor Create(Context cx, IMethodSymbol symbol) =>
AccessorFactory.Instance.CreateEntity(cx, symbol);
AccessorFactory.Instance.CreateEntityFromSymbol(cx, symbol);
class AccessorFactory : ICachedEntityFactory<IMethodSymbol, Accessor>
{

View File

@@ -104,7 +104,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
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");
}

View File

@@ -11,7 +11,7 @@ namespace Semmle.Extraction.CSharp.Entities
: 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
{

View File

@@ -24,7 +24,7 @@ namespace Semmle.Extraction.CSharp.Entities
}
public new static Destructor Create(Context cx, IMethodSymbol symbol) =>
DestructorFactory.Instance.CreateEntity(cx, symbol);
DestructorFactory.Instance.CreateEntityFromSymbol(cx, symbol);
class DestructorFactory : ICachedEntityFactory<IMethodSymbol, Destructor>
{

View File

@@ -60,7 +60,7 @@ namespace Semmle.Extraction.CSharp.Entities
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<IEventSymbol, Event>
{

View File

@@ -53,7 +53,7 @@ namespace Semmle.Extraction.CSharp.Entities
}
public new static EventAccessor Create(Context cx, IMethodSymbol symbol) =>
EventAccessorFactory.Instance.CreateEntity(cx, symbol);
EventAccessorFactory.Instance.CreateEntityFromSymbol(cx, symbol);
class EventAccessorFactory : ICachedEntityFactory<IMethodSymbol, EventAccessor>
{

View File

@@ -18,7 +18,7 @@ namespace Semmle.Extraction.CSharp.Entities
type = new Lazy<AnnotatedType>(() => 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.

View File

@@ -71,7 +71,7 @@ namespace Semmle.Extraction.CSharp.Entities
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)
{

View File

@@ -22,7 +22,7 @@ namespace Semmle.Extraction.CSharp.Entities
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<IMethodSymbol, LocalFunction>
{

View File

@@ -43,7 +43,7 @@ namespace Semmle.Extraction.CSharp.Entities
public static LocalVariable Create(Context cx, ISymbol local)
{
return LocalVariableFactory.Instance.CreateEntity(cx, local);
return LocalVariableFactory.Instance.CreateEntityFromSymbol(cx, local);
}
void DefineConstantValue(TextWriter trapFile)

View File

@@ -51,7 +51,7 @@ namespace Semmle.Extraction.CSharp.Entities
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<IMethodSymbol, OrdinaryMethod>
{

View File

@@ -113,7 +113,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
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)

View File

@@ -36,7 +36,7 @@ namespace Semmle.Extraction.CSharp.Entities
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<IArrayTypeSymbol, ArrayType>
{

View File

@@ -9,7 +9,7 @@ namespace Semmle.Extraction.CSharp.Entities
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();

View File

@@ -17,7 +17,7 @@ namespace Semmle.Extraction.CSharp.Entities
typeArgumentsLazy = new Lazy<Type[]>(() => 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;

View File

@@ -29,7 +29,7 @@ namespace Semmle.Extraction.CSharp.Entities
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<IPointerTypeSymbol, PointerType>
{

View File

@@ -13,7 +13,7 @@ namespace Semmle.Extraction.CSharp.Entities
/// </summary>
class TupleType : Type<INamedTypeSymbol>
{
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<INamedTypeSymbol, TupleType>
{

View File

@@ -324,6 +324,14 @@ namespace Semmle.Extraction.CSharp.Entities
}
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<T> : Type where T : ITypeSymbol

View File

@@ -88,7 +88,7 @@ namespace Semmle.Extraction.CSharp.Entities
}
static public TypeParameter Create(Context cx, ITypeParameterSymbol p) =>
TypeParameterFactory.Instance.CreateEntity(cx, p);
TypeParameterFactory.Instance.CreateEntityFromSymbol(cx, p);
/// <summary>
/// The variance of this type parameter.

View File

@@ -190,7 +190,7 @@ namespace Semmle.Extraction.CSharp.Entities
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<IMethodSymbol, UserOperator>
{

View File

@@ -62,7 +62,7 @@ namespace Semmle.Extraction
/// <returns>The new/existing entity.</returns>
public Entity CreateNullableEntity<Type, Entity>(ICachedEntityFactory<Type, Entity> 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);
}
/// <summary>
@@ -161,10 +161,14 @@ namespace Semmle.Extraction
public Entity CreateNonNullEntity<Type, Entity>(ICachedEntityFactory<Type, Entity> factory, Type init)
where Entity : ICachedEntity
=> CreateNonNullEntity(factory, init, objectEntityCache);
private Entity CreateNonNullEntity<Type, Entity>(ICachedEntityFactory<Type, Entity> factory, Type init, IDictionary<object, ICachedEntity> 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 +177,7 @@ namespace Semmle.Extraction
var entity = factory.Create(this, init);
entity.Label = label;
objectEntityCache[init] = entity;
dictionary[init] = entity;
DefineLabel(entity, TrapWriter.Writer, Extractor);
if (entity.NeedsPopulation)
@@ -227,7 +231,17 @@ namespace Semmle.Extraction
#if DEBUG_LABELS
readonly Dictionary<string, ICachedEntity> idLabelCache = new Dictionary<string, ICachedEntity>();
#endif
readonly Dictionary<object, ICachedEntity> objectEntityCache = new Dictionary<object, ICachedEntity>();
class SymbolComparer : IEqualityComparer<object>
{
IEqualityComparer<ISymbol> comparer = SymbolEqualityComparer.IncludeNullability;
bool IEqualityComparer<object>.Equals(object x, object y) => comparer.Equals((ISymbol)x, (ISymbol)y);
int IEqualityComparer<object>.GetHashCode(object obj) => comparer.GetHashCode((ISymbol)obj);
}
readonly IDictionary<object, ICachedEntity> objectEntityCache = new Dictionary<object, ICachedEntity>();
readonly IDictionary<object, ICachedEntity> symbolEntityCache = new Dictionary<object, ICachedEntity>(10000, new SymbolComparer());
readonly Dictionary<ICachedEntity, Label> entityLabelCache = new Dictionary<ICachedEntity, Label>();
readonly HashSet<Label> extractedGenerics = new HashSet<Label>();

View File

@@ -133,6 +133,19 @@ namespace Semmle.Extraction
public static Entity CreateNullableEntity<Type, Entity>(this ICachedEntityFactory<Type, Entity> factory, Context cx, Type init)
where Entity : ICachedEntity => cx.CreateNullableEntity(factory, init);
/// <summary>
/// Creates and populates a new entity, or returns the existing one from the cache.
/// </summary>
/// <typeparam name="Type">The symbol type used to construct the entity.</typeparam>
/// <typeparam name="Entity">The type of the entity to create.</typeparam>
/// <param name="cx">The extractor context.</param>
/// <param name="factory">The factory used to construct the entity.</param>
/// <param name="init">The initializer for the entity, which may not be null.</param>
/// <returns>The entity.</returns>
public static Entity CreateEntityFromSymbol<Type, Entity>(this ICachedEntityFactory<Type, Entity> factory, Context cx, Type init)
where Entity : ICachedEntity
where Type : ISymbol => cx.CreateEntityFromSymbol(factory, init);
/// <summary>
/// Creates and populates a new entity, but uses a different cache.
/// </summary>

View File

@@ -241,7 +241,7 @@ expressionTypes
| NullableRefTypes.cs:17:29:17:32 | null | null |
| NullableRefTypes.cs:18:31:18:34 | this access | MyClass! |
| NullableRefTypes.cs:19:33:19:36 | this access | MyClass! |
| NullableRefTypes.cs:26:44:26:53 | throw ... | MyClass?[]! |
| NullableRefTypes.cs:26:44:26:53 | throw ... | MyClass![]! |
| NullableRefTypes.cs:26:50:26:53 | null | null |
| NullableRefTypes.cs:27:44:27:53 | throw ... | MyClass?[]! |
| NullableRefTypes.cs:27:50:27:53 | null | null |