|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using ILLink.RoslynAnalyzer.DataFlow;
using ILLink.Shared.DataFlow;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.FlowAnalysis;
using LocalStateValue = ILLink.RoslynAnalyzer.DataFlow.LocalStateAndContext<
ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>,
ILLink.RoslynAnalyzer.DataFlow.FeatureContext
>;
using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>;
namespace ILLink.RoslynAnalyzer.TrimAnalysis
{
internal sealed class TrimDataFlowAnalysis : LocalDataFlowAnalysis<
MultiValue,
FeatureContext,
ValueSetLattice<SingleValue>,
FeatureContextLattice,
TrimAnalysisVisitor,
FeatureChecksValue>
{
public TrimAnalysisPatternStore TrimAnalysisPatterns { get; }
private DataFlowAnalyzerContext _dataFlowAnalyzerContext;
// The initial state of the feature context is None, meaning that
// no features are enabled at the beginning of the entry block.
// This way, calls to all Requires-annotated APIs will warn unless
// guarded by a feature check.
public TrimDataFlowAnalysis(
OperationBlockAnalysisContext context,
DataFlowAnalyzerContext dataFlowAnalyzerContext,
IOperation operationBlock)
: base(
context,
operationBlock,
default(ValueSetLattice<SingleValue>),
new FeatureContextLattice(),
initialContext: FeatureContext.None)
{
TrimAnalysisPatterns = new TrimAnalysisPatternStore(lattice.LocalStateLattice.Lattice.ValueLattice, lattice.ContextLattice);
_dataFlowAnalyzerContext = dataFlowAnalyzerContext;
}
public void ReportDiagnostics(Action<Diagnostic> reportDiagnostic)
{
TrimAnalysisPatterns.ReportDiagnostics(_dataFlowAnalyzerContext, reportDiagnostic);
}
protected override TrimAnalysisVisitor GetVisitor(
ISymbol owningSymbol,
ControlFlowGraph methodCFG,
ImmutableDictionary<CaptureId, FlowCaptureKind> lValueFlowCaptures,
InterproceduralState<MultiValue, ValueSetLattice<SingleValue>> interproceduralState)
=> new(Context.Compilation, lattice, owningSymbol, methodCFG, lValueFlowCaptures, TrimAnalysisPatterns, interproceduralState, _dataFlowAnalyzerContext);
#if DEBUG
#pragma warning disable CA1805 // Do not initialize unnecessarily
// Set this to a method name to trace the analysis of the method.
private readonly string? traceMethod = null;
private bool trace = false;
// Set this to true to print out the dataflow states encountered during the analysis.
private readonly bool showStates = false;
private static readonly TracingType tracingMechanism = Debugger.IsAttached ? TracingType.Debug : TracingType.Console;
#pragma warning restore CA1805 // Do not initialize unnecessarily
private ControlFlowGraphProxy cfg;
private enum TracingType
{
Console,
Debug
}
public override void TraceStart(ControlFlowGraphProxy cfg)
{
this.cfg = cfg;
var blocks = cfg.Blocks.ToList();
string? methodName = null;
foreach (var block in blocks)
{
if (block.Block.Operations.FirstOrDefault() is not IOperation op)
continue;
var method = op.Syntax.FirstAncestorOrSelf<MethodDeclarationSyntax>();
if (method is MethodDeclarationSyntax)
methodName = method.Identifier.ValueText;
break;
}
if (methodName?.Equals(traceMethod) == true)
trace = true;
if (trace)
TraceWriteLine("Tracing method " + methodName);
}
public override void TraceVisitBlock(BlockProxy block)
{
if (!trace)
return;
TraceWrite("block " + block.Block.Ordinal + ": ");
if (block.Block.Operations.FirstOrDefault() is IOperation firstBlockOp)
{
TraceWriteLine(firstBlockOp.Syntax.ToString());
}
else if (block.Block.BranchValue is IOperation branchOp)
{
TraceWriteLine(branchOp.Syntax.ToString());
}
else
{
TraceWriteLine("");
}
TraceWrite("predecessors: ");
foreach (var predecessor in cfg.GetPredecessors(block))
{
var predProxy = predecessor.Source;
TraceWrite(predProxy.Block.Ordinal + " ");
}
TraceWriteLine("");
}
private static void TraceWriteLine(string tracingInfo)
{
switch (tracingMechanism)
{
case TracingType.Console:
// Analyzers should not be writing to the console,
// but this is only used for debugging purposes and is off by default.
#pragma warning disable RS1035
Console.WriteLine(tracingInfo);
#pragma warning restore RS1035
break;
case TracingType.Debug:
Debug.WriteLine(tracingInfo);
break;
default:
throw new NotImplementedException(message: "invalid TracingType is being used");
}
}
private static void TraceWrite(string tracingInfo)
{
switch (tracingMechanism)
{
case TracingType.Console:
// Analyzers should not be writing to the console,
// but this is only used for debugging purposes and is off by default.
#pragma warning disable RS1035
Console.Write(tracingInfo);
#pragma warning restore RS1035
break;
case TracingType.Debug:
Debug.Write(tracingInfo);
break;
default:
throw new NotImplementedException(message: "invalid TracingType is being used");
}
}
private static void WriteIndented(string? s, int level)
{
if (s is not null)
{
var reader = new StringReader(s);
string? line;
while ((line = reader.ReadLine()) != null)
{
if (line.Length != 0)
{
TraceWrite(new string('\t', level));
TraceWriteLine(line);
}
}
}
}
public override void TraceEdgeInput(
IControlFlowGraph<BlockProxy, RegionProxy>.ControlFlowBranch branch,
LocalStateValue state
)
{
if (trace && showStates)
{
var source = branch.Source.Block.Ordinal;
var target = branch.Destination?.Block.Ordinal;
WriteIndented($"--- Edge from [{source}] to [{target}] ---", 1);
WriteIndented(state.ToString(), 2);
}
}
public override void TraceBlockInput(
LocalStateValue normalState,
LocalStateValue? exceptionState,
LocalStateValue? exceptionFinallyState
)
{
if (trace && showStates)
{
WriteIndented("--- before transfer ---", 1);
WriteIndented("normal state:", 1);
WriteIndented(normalState.ToString(), 2);
WriteIndented("exception state:", 1);
WriteIndented(exceptionState?.ToString(), 2);
WriteIndented("finally exception state:", 1);
WriteIndented(exceptionFinallyState?.ToString(), 2);
}
}
public override void TraceBlockOutput(
LocalStateValue normalState,
LocalStateValue? exceptionState,
LocalStateValue? exceptionFinallyState
)
{
if (trace && showStates)
{
WriteIndented("--- after transfer ---", 1);
WriteIndented("normal state:", 1);
WriteIndented(normalState.ToString(), 2);
WriteIndented("exception state:", 1);
WriteIndented(exceptionState?.ToString(), 2);
WriteIndented("finally state:", 1);
WriteIndented(exceptionFinallyState?.ToString(), 2);
}
}
#endif
}
}
|