C#: Implement NullabilityEntity to model structured nullability on the side

This commit is contained in:
Calum Grant
2019-09-27 14:52:55 +01:00
parent 61ab9431ab
commit 9fd4a9ceb6
19 changed files with 353 additions and 260 deletions

View File

@@ -1208,7 +1208,7 @@ namespace Semmle.Extraction.CIL.Entities
{
elementType.WriteId(trapFile, gc);
trapFile.Write('[');
for(int i=1; i<shape.Rank; ++i)
for (int i=1; i<shape.Rank; ++i)
trapFile.Write(',');
trapFile.Write(']');
}

View File

@@ -258,7 +258,6 @@ namespace Semmle.Extraction.CSharp
var assemblyPath = r.FilePath;
var projectLayout = layout.LookupProjectOrDefault(assemblyPath);
using (var trapWriter = projectLayout.CreateTrapWriter(Logger, assemblyPath, true, options.TrapCompression))
{
var skipExtraction = options.Cache && File.Exists(trapWriter.TrapFile);
@@ -278,14 +277,15 @@ namespace Semmle.Extraction.CSharp
* then there is a small amount of duplicated work but the output should
* still be correct.
*/
// compilation.Clone() reduces memory footprint by allowing the symbols
// in c to be garbage collected.
Compilation c = compilation.Clone();
var assembly = c.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol;
if (assembly != null)
{
var cx = extractor.CreateContext(c, trapWriter, new AssemblyScope(assembly, assemblyPath, false));
foreach (var module in assembly.Modules)

View File

@@ -19,7 +19,7 @@ namespace Semmle.Extraction.CSharp.Entities
if (symbol.IsGenericMethod && !IsSourceDeclaration)
{
trapFile.Write('<');
trapFile.BuildList(",", symbol.TypeArguments, (ta, tb0) => AddSignatureTypeToId(Context, tb0, symbol, ta, TypeNameContext.MethodName));
trapFile.BuildList(",", symbol.TypeArguments, (ta, tb0) => AddSignatureTypeToId(Context, tb0, symbol, ta, TypeIdentifierContext.MethodName));
trapFile.Write('>');
}
trapFile.Write(";localfunction");

View File

@@ -108,7 +108,6 @@ namespace Semmle.Extraction.CSharp.Entities
/// </summary>
protected static void BuildMethodId(Method m, TextWriter trapFile)
{
// AddSignatureTypeToId(m.Context, trapFile, m.symbol, m.ContainingType.symbol, false);
trapFile.WriteSubId(m.ContainingType);
AddExplicitInterfaceQualifierToId(m.Context, trapFile, m.symbol.ExplicitInterfaceImplementations);
@@ -130,7 +129,7 @@ namespace Semmle.Extraction.CSharp.Entities
// 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, TypeNameContext.MethodName); trapFile.Write((int)ta.Nullability); });
trapFile.BuildList(",", m.symbol.GetAnnotatedTypeArguments(), (ta, tb0) => { AddSignatureTypeToId(m.Context, tb0, m.symbol, ta.Symbol, TypeIdentifierContext.MethodName); trapFile.Write((int)ta.Nullability); });
trapFile.Write('>');
}
}
@@ -200,10 +199,10 @@ namespace Semmle.Extraction.CSharp.Entities
/// to make the reference to <code>#3</code> in the label definition <code>#4</code> for
/// <code>T</code> valid.
/// </summary>
protected static void AddSignatureTypeToId(Context cx, TextWriter trapFile, IMethodSymbol method, ITypeSymbol type, TypeNameContext assemblyPrefix)
protected static void AddSignatureTypeToId(Context cx, TextWriter trapFile, IMethodSymbol method, ITypeSymbol type, TypeIdentifierContext tic)
{
if (type.ContainsTypeParameters(cx, method))
type.BuildTypeId(cx, trapFile, (cx0, tb0, type0, _) => AddSignatureTypeToId(cx, tb0, method, type0, assemblyPrefix), assemblyPrefix, method);
type.BuildTypeId(cx, trapFile, (cx0, tb0, type0, _) => AddSignatureTypeToId(cx, tb0, method, type0, tic), tic, method);
else
trapFile.WriteSubId(Type.Create(cx, type).TypeRef);
}
@@ -216,13 +215,13 @@ namespace Semmle.Extraction.CSharp.Entities
if (method.MethodKind == MethodKind.ReducedExtension)
{
trapFile.WriteSeparator(",", ref index);
AddSignatureTypeToId(cx, trapFile, method, method.ReceiverType, TypeNameContext.MethodParam);
AddSignatureTypeToId(cx, trapFile, method, method.ReceiverType, TypeIdentifierContext.MethodParam);
}
foreach (var param in method.Parameters)
{
trapFile.WriteSeparator(",", ref index);
AddSignatureTypeToId(cx, trapFile, method, param.Type, TypeNameContext.MethodParam);
AddSignatureTypeToId(cx, trapFile, method, param.Type, TypeIdentifierContext.MethodParam);
switch (param.RefKind)
{
case RefKind.Out:

View File

@@ -15,7 +15,6 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(TextWriter trapFile)
{
// trapFile.WriteSubId(ContainingType.TypeRef);
trapFile.WriteSubId(ContainingType);
trapFile.Write('.');
Method.AddExplicitInterfaceQualifierToId(Context, trapFile, symbol.ExplicitInterfaceImplementations);

View File

@@ -32,7 +32,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
var ta = type.Nullability.GetTypeAnnotation();
var n = NullabilityEntity.Create(Context, Nullability.Create(type));
if (ta != Kinds.TypeAnnotation.None || !type.HasConsistentNullability())
if (!type.HasObliviousNullability())
{
trapFile.type_nullability(this, n);
}

View File

@@ -29,7 +29,7 @@ namespace Semmle.Extraction.CSharp.Entities
return;
}
trapFile.typeref_type((NamedTypeRef)TypeRef, this);
trapFile.typeref_type((TypeRef)TypeRef, this);
if (symbol.IsGenericType)
{
@@ -111,7 +111,7 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(TextWriter trapFile)
{
symbol.BuildTypeId(Context, trapFile, (cx0, tb0, sub, _) => tb0.WriteSubId(Create(cx0, sub)), TypeNameContext.TypeName, symbol);
symbol.BuildTypeId(Context, trapFile, (cx0, tb0, sub, _) => tb0.WriteSubId(Create(cx0, sub)), TypeIdentifierContext.TypeName, symbol);
trapFile.Write(";type");
}
@@ -151,26 +151,26 @@ namespace Semmle.Extraction.CSharp.Entities
public NamedType Create(Context cx, INamedTypeSymbol init) => new NamedType(cx, init);
}
public override Type TypeRef => NamedTypeRef.Create(Context, this, symbol);
public override Type TypeRef => Entities.TypeRef.Create(Context, this, symbol);
}
class NamedTypeRef : Type<ITypeSymbol>
class TypeRef : Type<ITypeSymbol>
{
readonly Type referencedType;
public NamedTypeRef(Context cx, Type type, ITypeSymbol symbol) : base(cx, symbol)
public TypeRef(Context cx, Type type, ITypeSymbol symbol) : base(cx, symbol)
{
referencedType = type;
}
public static NamedTypeRef Create(Context cx, Type referencedType, ITypeSymbol type) =>
public static TypeRef Create(Context cx, Type referencedType, ITypeSymbol type) =>
NamedTypeRefFactory.Instance.CreateEntity2(cx, (referencedType, type));
class NamedTypeRefFactory : ICachedEntityFactory<(Type, ITypeSymbol), NamedTypeRef>
class NamedTypeRefFactory : ICachedEntityFactory<(Type, ITypeSymbol), TypeRef>
{
public static readonly NamedTypeRefFactory Instance = new NamedTypeRefFactory();
public NamedTypeRef Create(Context cx, (Type, ITypeSymbol) init) => new NamedTypeRef(cx, init.Item1, init.Item2);
public TypeRef Create(Context cx, (Type, ITypeSymbol) init) => new TypeRef(cx, init.Item1, init.Item2);
}
public override bool NeedsPopulation => true;
@@ -179,11 +179,10 @@ namespace Semmle.Extraction.CSharp.Entities
{
void ExpandType(Context cx0, TextWriter tb0, ITypeSymbol sub, ISymbol gc)
{
sub.BuildTypeId(cx0, tb0, ExpandType, TypeNameContext.TypeRef, gc);
sub.BuildTypeId(cx0, tb0, ExpandType, TypeIdentifierContext.TypeRef, gc);
}
// Prefix anonymous types because they shouldn't be visible outside of the current assembly.
symbol.BuildTypeId(Context, trapFile, ExpandType, TypeNameContext.TypeRef, symbol);
symbol.BuildTypeId(Context, trapFile, ExpandType, TypeIdentifierContext.TypeRef, symbol);
trapFile.Write(";typeref");
}

View File

@@ -32,7 +32,7 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(TextWriter trapFile)
{
symbol.BuildTypeId(Context, trapFile, (cx0, tb0, sub, _) => tb0.WriteSubId(Create(cx0, sub)), TypeNameContext.TypeName, symbol);
symbol.BuildTypeId(Context, trapFile, (cx0, tb0, sub, _) => tb0.WriteSubId(Create(cx0, sub)), TypeIdentifierContext.TypeName, symbol);
trapFile.Write(";tuple");
}

View File

@@ -51,11 +51,11 @@ namespace Semmle.Extraction.CSharp.Entities
baseType = abase.Symbol;
var t = Create(Context, abase.Symbol);
trapFile.specific_type_parameter_constraints(constraints, t.TypeRef);
if (abase.Nullability.GetTypeAnnotation() != Kinds.TypeAnnotation.None)
trapFile.specific_type_parameter_annotation(constraints, t.TypeRef, abase.Nullability.GetTypeAnnotation());
if (!abase.HasObliviousNullability())
trapFile.specific_type_parameter_nullability(constraints, t.TypeRef, NullabilityEntity.Create(Context, Nullability.Create(abase)));
}
trapFile.types(this, Semmle.Extraction.Kinds.TypeKind.TYPE_PARAMETER, symbol.Name);
trapFile.types(this, Kinds.TypeKind.TYPE_PARAMETER, symbol.Name);
trapFile.extend(this, Create(Context, baseType).TypeRef);
Namespace parentNs = Namespace.Create(Context, symbol.TypeParameterKind == TypeParameterKind.Method ? Context.Compilation.GlobalNamespace : symbol.ContainingNamespace);
@@ -66,7 +66,7 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.type_location(this, Context.Create(l));
}
if (this.IsSourceDeclaration)
if (IsSourceDeclaration)
{
var declSyntaxReferences = symbol.DeclaringSyntaxReferences.Select(d => d.GetSyntax()).
Select(s => s.Parent).Where(p => p != null).Select(p => p.Parent).ToArray();

View File

@@ -51,7 +51,7 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(TextWriter trapFile)
{
AddSignatureTypeToId(Context, trapFile, symbol, symbol.ReturnType, TypeNameContext.MethodParam); // Needed for op_explicit(), which differs only by return type.
AddSignatureTypeToId(Context, trapFile, symbol, symbol.ReturnType, TypeIdentifierContext.MethodParam); // Needed for op_explicit(), which differs only by return type.
trapFile.Write(' ');
BuildMethodId(this, trapFile);
}

View File

@@ -27,7 +27,11 @@ namespace Semmle.Extraction.CSharp
}
}
public enum TypeNameContext
/// <summary>
/// Marks where a type identifier is being generated, which could change
/// the way that the identifier is generated.
/// </summary>
public enum TypeIdentifierContext
{
TypeName,
TypeRef,
@@ -36,7 +40,6 @@ namespace Semmle.Extraction.CSharp
MethodParam
}
static class SymbolExtensions
{
/// <summary>
@@ -143,7 +146,7 @@ namespace Semmle.Extraction.CSharp
/// <param name="cx">The extraction context.</param>
/// <param name="trapFile">The trap builder used to store the result.</param>
/// <param name="subTermAction">The action to apply to syntactic sub terms of this type.</param>
public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, Action<Context, TextWriter, ITypeSymbol, ISymbol> subTermAction, TypeNameContext assemblyPrefix, ISymbol genericContext)
public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, Action<Context, TextWriter, ITypeSymbol, ISymbol> subTermAction, TypeIdentifierContext tic, ISymbol genericContext)
{
switch(type.SpecialType)
{
@@ -182,8 +185,6 @@ namespace Semmle.Extraction.CSharp
case TypeKind.Array:
var array = (IArrayTypeSymbol)type;
subTermAction(cx, trapFile, array.ElementType, genericContext);
if(assemblyPrefix == TypeNameContext.TypeName)
trapFile.Write((int)array.ElementNullableAnnotation);
array.BuildArraySuffix(trapFile);
return;
case TypeKind.Class:
@@ -193,7 +194,7 @@ namespace Semmle.Extraction.CSharp
case TypeKind.Delegate:
case TypeKind.Error:
var named = (INamedTypeSymbol)type;
named.BuildNamedTypeId(cx, trapFile, subTermAction, assemblyPrefix, genericContext);
named.BuildNamedTypeId(cx, trapFile, subTermAction, tic, genericContext);
return;
case TypeKind.Pointer:
var ptr = (IPointerTypeSymbol)type;
@@ -205,12 +206,12 @@ namespace Semmle.Extraction.CSharp
switch(tp.TypeParameterKind)
{
case TypeParameterKind.Method:
if(!Equals(genericContext, tp.DeclaringMethod))
if (!Equals(genericContext, tp.DeclaringMethod))
trapFile.WriteSubId(Method.Create(cx, tp.DeclaringMethod));
trapFile.Write("!!");
break;
case TypeParameterKind.Type:
if(!Equals(genericContext,tp.DeclaringType))
if (!Equals(genericContext,tp.DeclaringType))
subTermAction(cx, trapFile, tp.DeclaringType, genericContext);
trapFile.Write("!");
break;
@@ -257,11 +258,11 @@ namespace Semmle.Extraction.CSharp
trapFile.Write("::");
}
public static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, Action<Context, TextWriter, ITypeSymbol, ISymbol> subTermAction, TypeNameContext assemblyPrefix, ISymbol genericContext)
public static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, Action<Context, TextWriter, ITypeSymbol, ISymbol> subTermAction, TypeIdentifierContext tic, ISymbol genericContext)
{
bool prefixAssembly = false;
if (named.IsAnonymous()) prefixAssembly = true;
else if(assemblyPrefix == TypeNameContext.TypeName && cx.Extractor.Identifiers != IdentifierMode.Imprecise) prefixAssembly = true;
else if(tic == TypeIdentifierContext.TypeName && cx.Extractor.TrapIdentifiers != TrapIdenfierMode.Imprecise) prefixAssembly = true;
if (named.ContainingAssembly is null) prefixAssembly = false;
if (prefixAssembly)
@@ -310,7 +311,9 @@ namespace Semmle.Extraction.CSharp
// 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); trapFile.Write((int)ta.Nullability); });
trapFile.BuildList(",", named.GetAnnotatedTypeArguments(),
(ta, tb0) => subTermAction(cx, tb0, ta.Symbol, genericContext)
);
trapFile.Write('>');
}
}
@@ -406,12 +409,11 @@ namespace Semmle.Extraction.CSharp
}
trapFile.Write(namedType.Name);
if (namedType.IsGenericType && /* namedType.TypeKind != TypeKind.Error && */ namedType.TypeArguments.Any())
if (namedType.IsGenericType && namedType.TypeArguments.Any())
{
trapFile.Write('<');
trapFile.BuildList(",", namedType.TypeArguments, (p, tb0) =>
{
// tb0.Write(p.Name);
if (IsReallyBound(namedType))
p.BuildDisplayName(cx, tb0);
});
@@ -554,6 +556,10 @@ namespace Semmle.Extraction.CSharp
/// This has not yet been exposed on the public API.
/// </summary>
public static AnnotatedTypeSymbol GetAnnotatedType(this IFieldSymbol symbol) => new AnnotatedTypeSymbol(symbol.Type, symbol.NullableAnnotation);
private static bool IsSpecialized(this IMethodSymbol method) =>
method.IsGenericMethod &&
!ReferenceEquals(method, method.OriginalDefinition) &&
method.TypeParameters.Zip(method.TypeArguments, (a, b) => !ReferenceEquals(a, b)).Any(b => b);
/// <summary>
/// Gets the annotated return type of an IMethodSymbol.
@@ -607,6 +613,7 @@ namespace Semmle.Extraction.CSharp
/// <summary>
/// Creates an AnnotatedTypeSymbol from an ITypeSymbol.
/// Note: not currently used but might be useful.
/// </summary>
public static AnnotatedTypeSymbol WithAnnotation(this ITypeSymbol symbol, NullableAnnotation annotation) =>
new AnnotatedTypeSymbol(symbol, annotation);

View File

@@ -456,9 +456,9 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("specific_type_parameter_constraints", constraints, baseType);
}
internal static void specific_type_parameter_annotation(this TextWriter trapFile, TypeParameterConstraints constraints, Type baseType, TypeAnnotation annotation)
internal static void specific_type_parameter_nullability(this TextWriter trapFile, TypeParameterConstraints constraints, Type baseType, NullabilityEntity nullability)
{
trapFile.WriteTuple("specific_type_parameter_annotation", constraints, baseType, (int)annotation);
trapFile.WriteTuple("specific_type_parameter_nullability", constraints, baseType, nullability);
}
internal static void stmt_location(this TextWriter trapFile, Statement stmt, Location location)
@@ -526,12 +526,12 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("type_parameters", param, child, typeOrMethod, (int)param.Variance);
}
internal static void typeref_type(this TextWriter trapFile, NamedTypeRef typeref, Type type)
internal static void typeref_type(this TextWriter trapFile, TypeRef typeref, Type type)
{
trapFile.WriteTuple("typeref_type", typeref, type);
}
internal static void typerefs(this TextWriter trapFile, NamedTypeRef type, string name)
internal static void typerefs(this TextWriter trapFile, TypeRef type, string name)
{
trapFile.WriteTuple("typerefs", type, name);
}

View File

@@ -88,15 +88,21 @@ namespace Semmle.Extraction
/// <returns></returns>
Context CreateContext(Compilation c, TrapWriter trapWriter, IExtractionScope scope);
IdentifierMode Identifiers { get; }
/// <summary>
/// Adjusts the algorithm used to generate identifiers in trap files.
/// </summary>
TrapIdenfierMode TrapIdentifiers { get; }
}
public enum IdentifierMode
/// <summary>
/// Configures how to generate identifiers in trap files.
/// </summary>
public enum TrapIdenfierMode
{
Imprecise,
Flexible,
Precise,
ExtraPrecise
Imprecise, // Names are not qualified by assembly version
Flexible, // Some names are qualified by assembly versions - typerefs are not however.
Precise, // All names are qualified by their partial assembly version (excludes build number)
ExtraPrecise // All names are qualified by their full assembly version.
}
/// <summary>
@@ -122,7 +128,7 @@ namespace Semmle.Extraction
public Extractor(bool standalone, string outputPath, ILogger logger)
{
Standalone = standalone;
if (Standalone) Identifiers = IdentifierMode.Imprecise;
if (Standalone) TrapIdentifiers = TrapIdenfierMode.Imprecise;
OutputPath = outputPath;
Logger = logger;
}
@@ -209,6 +215,6 @@ namespace Semmle.Extraction
public static string Version => $"{ThisAssembly.Git.BaseTag} ({ThisAssembly.Git.Sha})";
public IdentifierMode Identifiers { get; } = IdentifierMode.Imprecise;
public TrapIdenfierMode TrapIdentifiers { get; } = TrapIdenfierMode.Imprecise;
}
}