|
// 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.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ILLink.Shared.DataFlow;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis;
using Microsoft.CodeAnalysis.Operations;
using StateValue = ILLink.RoslynAnalyzer.DataFlow.LocalDataFlowState<
ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>,
ILLink.RoslynAnalyzer.DataFlow.FeatureContext,
ILLink.Shared.DataFlow.ValueSetLattice<ILLink.Shared.DataFlow.SingleValue>,
ILLink.RoslynAnalyzer.DataFlow.FeatureContextLattice
>;
namespace ILLink.RoslynAnalyzer.DataFlow
{
// Visits a conditional expression to optionally produce a 'FeatureChecksValue'
// (a set features that are checked to be enabled or disabled).
// The visitor takes a LocalDataFlowState as an argument, allowing for checks that
// depend on the current dataflow state.
internal sealed class FeatureChecksVisitor : OperationVisitor<StateValue, FeatureChecksValue>
{
private DataFlowAnalyzerContext _dataFlowAnalyzerContext;
public FeatureChecksVisitor(DataFlowAnalyzerContext dataFlowAnalyzerContext)
{
_dataFlowAnalyzerContext = dataFlowAnalyzerContext;
}
public override FeatureChecksValue DefaultVisit(IOperation operation, StateValue state)
{
// Visiting a non-understood pattern should return the empty set of features, which will
// prevent this check from acting as a guard for any feature.
return FeatureChecksValue.None;
}
public override FeatureChecksValue VisitArgument(IArgumentOperation operation, StateValue state)
{
return Visit(operation.Value, state);
}
public override FeatureChecksValue VisitPropertyReference(IPropertyReferenceOperation operation, StateValue state)
{
// A single property may serve as a feature check for multiple features.
FeatureChecksValue featureChecks = FeatureChecksValue.None;
foreach (var analyzer in _dataFlowAnalyzerContext.EnabledRequiresAnalyzers)
{
if (analyzer.IsFeatureGuard(operation.Property, _dataFlowAnalyzerContext.Compilation))
{
var featureCheck = new FeatureChecksValue(analyzer.RequiresAttributeFullyQualifiedName);
featureChecks = featureChecks.And(featureCheck);
}
}
return featureChecks;
}
public override FeatureChecksValue VisitUnaryOperator(IUnaryOperation operation, StateValue state)
{
if (operation.OperatorKind is not UnaryOperatorKind.Not)
return FeatureChecksValue.None;
FeatureChecksValue context = Visit(operation.Operand, state);
return context.Negate();
}
public override FeatureChecksValue VisitLiteral(ILiteralOperation operation, StateValue state)
{
// 'false' can guard any feature
if (GetConstantBool(operation.ConstantValue) is false)
return FeatureChecksValue.All;
return FeatureChecksValue.None;
}
public static bool? GetLiteralBool(IOperation operation)
{
if (operation is not ILiteralOperation literal)
return null;
return GetConstantBool(literal.ConstantValue);
}
private static bool? GetConstantBool(Optional<object?> constantValue)
{
if (!constantValue.HasValue || constantValue.Value is not bool value)
return null;
return value;
}
public override FeatureChecksValue VisitBinaryOperator(IBinaryOperation operation, StateValue state)
{
bool expectEqual;
switch (operation.OperatorKind)
{
case BinaryOperatorKind.Equals:
expectEqual = true;
break;
case BinaryOperatorKind.NotEquals:
expectEqual = false;
break;
default:
return FeatureChecksValue.None;
}
if (GetLiteralBool(operation.LeftOperand) is bool leftBool)
{
FeatureChecksValue rightValue = Visit(operation.RightOperand, state);
return leftBool == expectEqual
? rightValue
: rightValue.Negate();
}
if (GetLiteralBool(operation.RightOperand) is bool rightBool)
{
FeatureChecksValue leftValue = Visit(operation.LeftOperand, state);
return rightBool == expectEqual
? leftValue
: leftValue.Negate();
}
return FeatureChecksValue.None;
}
public override FeatureChecksValue VisitIsPattern(IIsPatternOperation operation, StateValue state)
{
if (GetExpectedValueFromPattern(operation.Pattern) is not bool patternValue)
return FeatureChecksValue.None;
FeatureChecksValue value = Visit(operation.Value, state);
return patternValue
? value
: value.Negate();
static bool? GetExpectedValueFromPattern(IPatternOperation pattern)
{
switch (pattern)
{
case IConstantPatternOperation constantPattern:
return GetConstantBool(constantPattern.Value.ConstantValue);
case INegatedPatternOperation negatedPattern:
return !GetExpectedValueFromPattern(negatedPattern.Pattern);
default:
return null;
}
}
}
}
}
|