using Microsoft.CodeAnalysis;
using Semmle.Extraction.CommentProcessing;
using Semmle.Extraction.Entities;
using Semmle.Util.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Semmle.Extraction
{
///
/// State that needs to be available throughout the extraction process.
/// There is one Context object per trap output file.
///
public class Context
{
///
/// Access various extraction functions, e.g. logger, trap writer.
///
public readonly IExtractor Extractor;
///
/// The program database provided by Roslyn.
/// There's one per syntax tree, which makes things awkward.
///
public SemanticModel GetModel(SyntaxNode node)
{
if (cachedModel == null || node.SyntaxTree != cachedModel.SyntaxTree)
{
cachedModel = Compilation.GetSemanticModel(node.SyntaxTree);
}
return cachedModel;
}
private SemanticModel? cachedModel;
///
/// Access to the trap file.
///
public readonly TrapWriter TrapWriter;
///
/// Holds if assembly information should be prefixed to TRAP labels.
///
public readonly bool ShouldAddAssemblyTrapPrefix;
int GetNewId() => TrapWriter.IdCounter++;
// A recursion guard against writing to the trap file whilst writing an id to the trap file.
bool WritingLabel = false;
public void DefineLabel(IEntity entity, TextWriter trapFile, IExtractor extractor)
{
if (WritingLabel)
{
// Don't define a label whilst writing a label.
PopulateLater(() => DefineLabel(entity, trapFile, extractor));
}
else
{
try
{
WritingLabel = true;
entity.DefineLabel(trapFile, extractor);
}
finally
{
WritingLabel = false;
}
}
}
#if DEBUG_LABELS
private void CheckEntityHasUniqueLabel(string id, ICachedEntity entity)
{
if (idLabelCache.TryGetValue(id, out var originalEntity))
{
ExtractionError("Label collision for " + id, entity.Label.ToString(), Entities.Location.Create(this, entity.ReportingLocation), "", Severity.Warning);
}
else
{
idLabelCache[id] = entity;
}
}
#endif
public Label GetNewLabel() => new Label(GetNewId());
public Entity CreateEntity(ICachedEntityFactory factory, object cacheKey, Type init)
where Entity : ICachedEntity =>
cacheKey is ISymbol s ? CreateEntity(factory, s, init, symbolEntityCache) : CreateEntity(factory, cacheKey, init, objectEntityCache);
public Entity CreateEntityFromSymbol(ICachedEntityFactory factory, Type init)
where Type : ISymbol
where Entity : ICachedEntity => CreateEntity(factory, init, init, symbolEntityCache);
///
/// Creates and populates a new entity, or returns the existing one from the cache.
///
/// The entity factory.
/// The key used for caching.
/// The initializer for the entity.
/// The dictionary to use for caching.
/// The new/existing entity.
Entity CreateEntity(ICachedEntityFactory factory, CacheKeyType cacheKey, Type init, IDictionary dictionary)
where CacheKeyType : notnull
where Entity : ICachedEntity
{
if (dictionary.TryGetValue(cacheKey, out var cached))
return (Entity)cached;
using (StackGuard)
{
var label = GetNewLabel();
var entity = factory.Create(this, init);
entity.Label = label;
dictionary[cacheKey] = entity;
DefineLabel(entity, TrapWriter.Writer, Extractor);
if (entity.NeedsPopulation)
Populate(init as ISymbol, entity);
#if DEBUG_LABELS
using (var id = new StringWriter())
{
entity.WriteQuotedId(id);
CheckEntityHasUniqueLabel(id.ToString(), entity);
}
#endif
return entity;
}
}
///
/// Should the given entity be extracted?
/// A second call to this method will always return false,
/// on the assumption that it would have been extracted on the first call.
///
/// This is used to track the extraction of generics, which cannot be extracted
/// in a top-down manner.
///
/// The entity to extract.
/// True only on the first call for a particular entity.
public bool ExtractGenerics(ICachedEntity entity)
{
if (extractedGenerics.Contains(entity.Label))
{
return false;
}
else
{
extractedGenerics.Add(entity.Label);
return true;
}
}
///
/// Creates a fresh label with ID "*", and set it on the
/// supplied object.
///
public void AddFreshLabel(IEntity entity)
{
entity.Label = GetNewLabel();
entity.DefineFreshLabel(TrapWriter.Writer);
}
#if DEBUG_LABELS
readonly Dictionary idLabelCache = new Dictionary();
#endif
readonly IDictionary