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;
int GetNewId() => TrapWriter.IdCounter++;
///
/// Creates a new entity using the factory.
///
/// The entity factory.
/// The initializer for the entity.
/// The new/existing entity.
public Entity CreateEntity(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity
{
return init == null ? CreateEntity2(factory, init) : CreateNonNullEntity(factory, init);
}
// 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)
{
if (WritingLabel)
{
// Don't define a label whilst writing a label.
PopulateLater(() => DefineLabel(entity, trapFile));
}
else
{
try
{
WritingLabel = true;
entity.DefineLabel(trapFile);
}
finally
{
WritingLabel = false;
}
}
}
///
/// Creates a new entity using the factory.
/// Uses a different cache to ,
/// and can store null values.
///
/// The entity factory.
/// The initializer for the entity.
/// The new/existing entity.
public Entity CreateEntity2(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity
{
using (StackGuard)
{
var entity = factory.Create(this, init);
if (entityLabelCache.TryGetValue(entity, out var label))
{
entity.Label = label;
}
else
{
label = GetNewLabel();
entity.Label = label;
entityLabelCache[entity] = label;
DefineLabel(entity, TrapWriter.Writer);
if (entity.NeedsPopulation)
Populate(init as ISymbol, entity);
#if DEBUG_LABELS
using (var id = new StringWriter())
{
entity.WriteId(id);
CheckEntityHasUniqueLabel(id.ToString(), entity);
}
#endif
}
return entity;
}
}
#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());
private Entity CreateNonNullEntity(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity
{
if (objectEntityCache.TryGetValue(init, out var cached))
return (Entity)cached;
using (StackGuard)
{
var label = GetNewLabel();
var entity = factory.Create(this, init);
entity.Label = label;
objectEntityCache[init] = entity;
DefineLabel(entity, TrapWriter.Writer);
if (entity.NeedsPopulation)
Populate(init as ISymbol, entity);
#if DEBUG_LABELS
using (var id = new StringWriter())
{
entity.WriteId(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 Dictionary