Share entity base classes between CIL and source extraction

This commit is contained in:
Tamas Vajk
2021-02-11 13:52:38 +01:00
parent e7853cc3a0
commit 67289a498f
106 changed files with 705 additions and 710 deletions

View File

@@ -0,0 +1,88 @@
using System.IO;
using Microsoft.CodeAnalysis;
namespace Semmle.Extraction
{
/// <summary>
/// A cached entity.
///
/// The <see cref="Entity.Id"/> property is used as label in caching.
/// </summary>
public abstract class CachedEntity : LabelledEntity
{
protected CachedEntity(Context context) : base(context)
{
}
/// <summary>
/// Populates the <see cref="Label"/> field and generates output in the trap file
/// as required. Is only called when <see cref="NeedsPopulation"/> returns
/// <code>true</code> and the entity has not already been populated.
/// </summary>
public abstract void Populate(TextWriter trapFile);
public abstract bool NeedsPopulation { get; }
}
/// <summary>
/// An abstract symbol, which encapsulates a data type (such as a C# symbol).
/// </summary>
/// <typeparam name="TSymbol">The type of the symbol.</typeparam>
public abstract class CachedEntity<TSymbol> : CachedEntity
{
protected CachedEntity(Context context, TSymbol symbol) : base(context)
{
this.symbol = symbol;
}
/// <summary>
/// For debugging.
/// </summary>
public string DebugContents
{
get
{
using var trap = new StringWriter();
Populate(trap);
return trap.ToString();
}
}
public TSymbol symbol
{
get;
}
//object? ICachedEntity.UnderlyingObject => symbol;
public TSymbol UnderlyingObject => symbol;
public override bool NeedsPopulation { get; }
public override int GetHashCode() => symbol is null ? 0 : symbol.GetHashCode();
public override bool Equals(object? obj)
{
var other = obj as CachedEntity<TSymbol>;
return other?.GetType() == GetType() && Equals(other.symbol, symbol);
}
public override TrapStackBehaviour TrapStackBehaviour { get; }
}
/// <summary>
/// A class used to wrap an `ISymbol` object, which uses `SymbolEqualityComparer.Default`
/// for comparison.
/// </summary>
public struct SymbolEqualityWrapper
{
public ISymbol Symbol { get; }
public SymbolEqualityWrapper(ISymbol symbol) { Symbol = symbol; }
public override bool Equals(object? other) =>
other is SymbolEqualityWrapper sew && SymbolEqualityComparer.Default.Equals(Symbol, sew.Symbol);
public override int GetHashCode() => 11 * SymbolEqualityComparer.Default.GetHashCode(Symbol);
}
}

View File

@@ -0,0 +1,64 @@
using Microsoft.CodeAnalysis;
using System;
using System.IO;
namespace Semmle.Extraction
{
public abstract class Entity : IEntity
{
protected Context Context { get; }
protected Entity(Context context)
{
this.Context = context;
}
public Label Label { get; set; }
public abstract void WriteId(TextWriter trapFile);
public abstract void WriteQuotedId(TextWriter trapFile);
public abstract Location? ReportingLocation { get; }
public abstract TrapStackBehaviour TrapStackBehaviour { get; }
public void DefineLabel(TextWriter trapFile, Extractor extractor)
{
trapFile.WriteLabel(this);
trapFile.Write("=");
try
{
WriteQuotedId(trapFile);
}
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
{
trapFile.WriteLine("\"");
extractor.Message(new Message($"Unhandled exception generating id: {ex.Message}", ToString() ?? "", null, ex.StackTrace));
}
trapFile.WriteLine();
}
public void DefineFreshLabel(TextWriter trapFile)
{
trapFile.WriteLabel(this);
trapFile.WriteLine("=*");
}
#if DEBUG_LABELS
/// <summary>
/// Generates a debug string for this entity.
/// </summary>
public string GetDebugLabel()
{
using var writer = new StringWriter();
writer.WriteLabel(Label.Value);
writer.Write('=');
WriteQuotedId(writer);
return writer.ToString();
}
#endif
public override string ToString() => Label.ToString();
}
}

View File

@@ -0,0 +1,38 @@
using System.IO;
namespace Semmle.Extraction
{
/// <summary>
/// An entity which has a default "*" ID assigned to it.
/// </summary>
public abstract class FreshEntity : UnlabelledEntity
{
protected FreshEntity(Context cx) : base(cx)
{
}
protected abstract void Populate(TextWriter trapFile);
protected void TryPopulate()
{
Context.Try(null, null, () => Populate(Context.TrapWriter.Writer));
}
/// <summary>
/// For debugging.
/// </summary>
public string DebugContents
{
get
{
using var writer = new StringWriter();
Populate(writer);
return writer.ToString();
}
}
public override Microsoft.CodeAnalysis.Location? ReportingLocation => null;
public override TrapStackBehaviour TrapStackBehaviour { get; }
}
}

View File

@@ -0,0 +1,14 @@
namespace Semmle.Extraction
{
/// <summary>
/// A factory for creating cached entities.
/// </summary>
/// <typeparam name="TInit">The type of the initializer.</typeparam>
public interface ICachedEntityFactory<in TInit, out TEntity> where TEntity : CachedEntity
{
/// <summary>
/// Initializes the entity, but does not generate any trap code.
/// </summary>
TEntity Create(Context cx, TInit init);
}
}

View File

@@ -0,0 +1,35 @@
using Microsoft.CodeAnalysis;
namespace Semmle.Extraction
{
public static class ICachedEntityFactoryExtensions
{
/// <summary>
/// Creates and populates a new entity, or returns the existing one from the cache,
/// based on the supplied cache key.
/// </summary>
/// <typeparam name="TInit">The type used to construct the entity.</typeparam>
/// <typeparam name="TEntity">The type of the entity to create.</typeparam>
/// <param name="factory">The factory used to construct the entity.</param>
/// <param name="cx">The extractor context.</param>
/// <param name="cacheKey">The key used for caching.</param>
/// <param name="init">The initializer for the entity.</param>
/// <returns>The entity.</returns>
public static TEntity CreateEntity<TInit, TEntity>(this ICachedEntityFactory<TInit, TEntity> factory, Context cx, object cacheKey, TInit init)
where TEntity : CachedEntity => cx.CreateEntity(factory, cacheKey, init);
/// <summary>
/// Creates and populates a new entity from an `ISymbol`, or returns the existing one
/// from the cache.
/// </summary>
/// <typeparam name="TSymbol">The type used to construct the entity.</typeparam>
/// <typeparam name="TEntity">The type of the entity to create.</typeparam>
/// <param name="factory">The factory used to construct the entity.</param>
/// <param name="cx">The extractor context.</param>
/// <param name="init">The initializer for the entity.</param>
/// <returns>The entity.</returns>
public static TEntity CreateEntityFromSymbol<TSymbol, TEntity>(this ICachedEntityFactory<TSymbol, TEntity> factory, Context cx, TSymbol init)
where TSymbol : ISymbol
where TEntity : CachedEntity => cx.CreateEntityFromSymbol(factory, init);
}
}

View File

@@ -0,0 +1,53 @@
using Microsoft.CodeAnalysis;
using System.IO;
namespace Semmle.Extraction
{
/// <summary>
/// Any program entity which has a corresponding label in the trap file.
///
/// Entities are divided into two types: normal entities and cached
/// entities.
///
/// Normal entities implement <see cref="FreshEntity"/> directly, and they
/// (may) emit contents to the trap file during object construction.
///
/// Cached entities implement <see cref="CachedEntity"/>, and they
/// emit contents to the trap file when <see cref="CachedEntity.Populate"/>
/// is called. Caching prevents <see cref="CachedEntity.Populate"/>
/// from being called on entities that have already been emitted.
/// </summary>
public interface IEntity
{
/// <summary>
/// The label of the entity, as it is in the trap file.
/// For example, "#123".
/// </summary>
Label Label { get; set; }
/// <summary>
/// Writes the unique identifier of this entitiy to a trap file.
/// </summary>
/// <param name="trapFile">The trapfile to write to.</param>
void WriteId(TextWriter trapFile);
/// <summary>
/// Writes the quoted identifier of this entity,
/// which could be @"..." or *
/// </summary>
/// <param name="trapFile">The trapfile to write to.</param>
void WriteQuotedId(TextWriter trapFile);
/// <summary>
/// The location for reporting purposes.
/// </summary>
Location? ReportingLocation { get; }
/// <summary>
/// How the entity handles .push and .pop.
/// </summary>
TrapStackBehaviour TrapStackBehaviour { get; }
void DefineLabel(TextWriter trapFile, Extractor extractor);
}
}

View File

@@ -0,0 +1,18 @@
using System.IO;
namespace Semmle.Extraction
{
public abstract class LabelledEntity : Entity
{
protected LabelledEntity(Context cx) : base(cx)
{
}
public override void WriteQuotedId(TextWriter trapFile)
{
trapFile.Write("@\"");
WriteId(trapFile);
trapFile.Write('\"');
}
}
}

View File

@@ -0,0 +1,22 @@
using System.IO;
namespace Semmle.Extraction
{
public abstract class UnlabelledEntity : Entity
{
protected UnlabelledEntity(Context cx) : base(cx)
{
cx.AddFreshLabel(this);
}
public sealed override void WriteId(TextWriter writer)
{
writer.Write('*');
}
public sealed override void WriteQuotedId(TextWriter writer)
{
WriteId(writer);
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Semmle.Extraction.Entities
protected override void Populate(TextWriter trapFile)
{
trapFile.extractor_messages(this, msg.Severity, "C# extractor", msg.Text, msg.EntityText ?? string.Empty,
msg.Location ?? cx.CreateLocation(), msg.StackTrace ?? string.Empty);
msg.Location ?? Context.CreateLocation(), msg.StackTrace ?? string.Empty);
}
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;

View File

@@ -19,7 +19,7 @@ namespace Semmle.Extraction.Entities
: base(cx, init)
{
Position = init.GetLineSpan();
FileEntity = File.Create(Context, Position.Path);
FileEntity = File.Create(base.Context, Position.Path);
}
public static Location Create(Context cx, Microsoft.CodeAnalysis.Location loc) => SourceLocationFactory.Instance.CreateEntity(cx, loc, loc);