File: TrimAnalysis\TrimDataFlowAnalysis.cs
Web Access
Project: src\src\tools\illink\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj (ILLink.RoslynAnalyzer)
// 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; }
 
		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.
		readonly string? traceMethod = null;
 
		bool trace = false;
 
		// Set this to true to print out the dataflow states encountered during the analysis.
		readonly bool showStates = false;
 
		static readonly TracingType tracingMechanism = Debugger.IsAttached ? TracingType.Debug : TracingType.Console;
#pragma warning restore CA1805 // Do not initialize unnecessarily
		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");
			}
		}
 
		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
	}
}