// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;
namespace Microsoft.CodeAnalysis.UnitTests.Diagnostics
{
// These analyzers are not intended for any actual use. They exist solely to test IOperation support.
/// Analyzer used to test for bad statements and expressions.
public class BadStuffTestAnalyzer : DiagnosticAnalyzer
{
public static readonly DiagnosticDescriptor InvalidExpressionDescriptor = new DiagnosticDescriptor(
"InvalidExpression",
"Invalid Expression",
"Invalid expression found.",
"Testing",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public static readonly DiagnosticDescriptor InvalidStatementDescriptor = new DiagnosticDescriptor(
"InvalidStatement",
"Invalid Statement",
"Invalid statement found.",
"Testing",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public static readonly DiagnosticDescriptor IsInvalidDescriptor = new DiagnosticDescriptor(
"IsInvalid",
"Is Invalid",
"Operation found that is invalid.",
"Testing",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public sealed override ImmutableArray SupportedDiagnostics
{
get { return ImmutableArray.Create(InvalidExpressionDescriptor, InvalidStatementDescriptor, IsInvalidDescriptor); }
}
public sealed override void Initialize(AnalysisContext context)
{
context.RegisterOperationAction(
(operationContext) =>
{
operationContext.ReportDiagnostic(Diagnostic.Create(InvalidExpressionDescriptor, operationContext.Operation.Syntax.GetLocation()));
},
OperationKind.InvalidExpression);
context.RegisterOperationAction(
(operationContext) =>
{
operationContext.ReportDiagnostic(Diagnostic.Create(InvalidStatementDescriptor, operationContext.Operation.Syntax.GetLocation()));
},
OperationKind.InvalidStatement);
context.RegisterOperationAction(
(operationContext) =>
{
if (operationContext.Operation.IsInvalid)
{
operationContext.ReportDiagnostic(Diagnostic.Create(IsInvalidDescriptor, operationContext.Operation.Syntax.GetLocation()));
}
},
OperationKind.InvocationExpression,
OperationKind.InvalidExpression,
OperationKind.InvalidStatement);
}
}
/// Analyzer used to test for operations within symbols of certain names.
public class OwningSymbolTestAnalyzer : DiagnosticAnalyzer
{
public static readonly DiagnosticDescriptor ExpressionDescriptor = new DiagnosticDescriptor(
"Expression",
"Expression",
"Expression found.",
"Testing",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public sealed override ImmutableArray SupportedDiagnostics
{
get { return ImmutableArray.Create(ExpressionDescriptor); }
}
public sealed override void Initialize(AnalysisContext context)
{
context.RegisterOperationBlockStartAction(
(operationBlockContext) =>
{
if (operationBlockContext.Compilation.Language != "Stumble")
{
operationBlockContext.RegisterOperationAction(
(operationContext) =>
{
if (operationContext.ContainingSymbol.Name.StartsWith("Funky") && operationContext.Compilation.Language != "Mumble")
{
operationContext.ReportDiagnostic(Diagnostic.Create(ExpressionDescriptor, operationContext.Operation.Syntax.GetLocation()));
}
},
OperationKind.LocalReferenceExpression,
OperationKind.LiteralExpression);
}
});
}
}
/// Analyzer used to test for loop IOperations.
public class BigForTestAnalyzer : DiagnosticAnalyzer
{
/// Diagnostic category "Reliability".
private const string ReliabilityCategory = "Reliability";
public static readonly DiagnosticDescriptor BigForDescriptor = new DiagnosticDescriptor(
"BigForRule",
"Big For Loop",
"For loop iterates more than one million times",
ReliabilityCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
/// Gets the set of supported diagnostic descriptors from this analyzer.
public sealed override ImmutableArray SupportedDiagnostics
{
get { return ImmutableArray.Create(BigForDescriptor); }
}
public sealed override void Initialize(AnalysisContext context)
{
context.RegisterOperationAction(
(operationContext) =>
{
ILoopStatement loop = (ILoopStatement)operationContext.Operation;
if (loop.LoopKind == LoopKind.For)
{
IForLoopStatement forLoop = (IForLoopStatement)loop;
IOperation forCondition = forLoop.Condition;
if (forCondition.Kind == OperationKind.BinaryOperatorExpression)
{
IBinaryOperatorExpression condition = (IBinaryOperatorExpression)forCondition;
IOperation conditionLeft = condition.LeftOperand;
IOperation conditionRight = condition.RightOperand;
if (conditionRight.ConstantValue.HasValue &&
conditionRight.Type.SpecialType == SpecialType.System_Int32 &&
conditionLeft.Kind == OperationKind.LocalReferenceExpression)
{
// Test is known to be a comparison of a local against a constant.
int testValue = (int)conditionRight.ConstantValue.Value;
ILocalSymbol testVariable = ((ILocalReferenceExpression)conditionLeft).Local;
if (forLoop.Before.Length == 1)
{
IOperation setup = forLoop.Before[0];
if (setup.Kind == OperationKind.ExpressionStatement && ((IExpressionStatement)setup).Expression.Kind == OperationKind.AssignmentExpression)
{
IAssignmentExpression setupAssignment = (IAssignmentExpression)((IExpressionStatement)setup).Expression;
if (setupAssignment.Target.Kind == OperationKind.LocalReferenceExpression &&
((ILocalReferenceExpression)setupAssignment.Target).Local == testVariable &&
setupAssignment.Value.ConstantValue.HasValue &&
setupAssignment.Value.Type.SpecialType == SpecialType.System_Int32)
{
// Setup is known to be an assignment of a constant to the local used in the test.
int initialValue = (int)setupAssignment.Value.ConstantValue.Value;
if (forLoop.AtLoopBottom.Length == 1)
{
IOperation advance = forLoop.AtLoopBottom[0];
if (advance.Kind == OperationKind.ExpressionStatement)
{
IOperation advanceExpression = ((IExpressionStatement)advance).Expression;
IOperation advanceIncrement = null;
BinaryOperationKind advanceOperationCode = BinaryOperationKind.None;
if (advanceExpression.Kind == OperationKind.AssignmentExpression)
{
IAssignmentExpression advanceAssignment = (IAssignmentExpression)advanceExpression;
if (advanceAssignment.Target.Kind == OperationKind.LocalReferenceExpression &&
((ILocalReferenceExpression)advanceAssignment.Target).Local == testVariable &&
advanceAssignment.Value.Kind == OperationKind.BinaryOperatorExpression &&
advanceAssignment.Value.Type.SpecialType == SpecialType.System_Int32)
{
// Advance is known to be an assignment of a binary operation to the local used in the test.
IBinaryOperatorExpression advanceOperation = (IBinaryOperatorExpression)advanceAssignment.Value;
if (!advanceOperation.UsesOperatorMethod &&
advanceOperation.LeftOperand.Kind == OperationKind.LocalReferenceExpression &&
((ILocalReferenceExpression)advanceOperation.LeftOperand).Local == testVariable &&
advanceOperation.RightOperand.ConstantValue.HasValue &&
advanceOperation.RightOperand.Type.SpecialType == SpecialType.System_Int32)
{
// Advance binary operation is known to involve a reference to the local used in the test and a constant.
advanceIncrement = advanceOperation.RightOperand;
advanceOperationCode = advanceOperation.BinaryOperationKind;
}
}
}
else if (advanceExpression.Kind == OperationKind.CompoundAssignmentExpression || advanceExpression.Kind == OperationKind.IncrementExpression)
{
ICompoundAssignmentExpression advanceAssignment = (ICompoundAssignmentExpression)advanceExpression;
if (advanceAssignment.Target.Kind == OperationKind.LocalReferenceExpression &&
((ILocalReferenceExpression)advanceAssignment.Target).Local == testVariable &&
advanceAssignment.Value.ConstantValue.HasValue &&
advanceAssignment.Value.Type.SpecialType == SpecialType.System_Int32)
{
// Advance binary operation is known to involve a reference to the local used in the test and a constant.
advanceIncrement = advanceAssignment.Value;
advanceOperationCode = advanceAssignment.BinaryOperationKind;
}
}
if (advanceIncrement != null)
{
int incrementValue = (int)advanceIncrement.ConstantValue.Value;
if (advanceOperationCode == BinaryOperationKind.IntegerSubtract)
{
advanceOperationCode = BinaryOperationKind.IntegerAdd;
incrementValue = -incrementValue;
}
if (advanceOperationCode == BinaryOperationKind.IntegerAdd &&
incrementValue != 0 &&
(condition.BinaryOperationKind == BinaryOperationKind.IntegerLessThan ||
condition.BinaryOperationKind == BinaryOperationKind.IntegerLessThanOrEqual ||
condition.BinaryOperationKind == BinaryOperationKind.IntegerNotEquals ||
condition.BinaryOperationKind == BinaryOperationKind.IntegerGreaterThan ||
condition.BinaryOperationKind == BinaryOperationKind.IntegerGreaterThanOrEqual))
{
int iterationCount = (testValue - initialValue) / incrementValue;
if (iterationCount >= 1000000)
{
Report(operationContext, forLoop.Syntax, BigForDescriptor);
}
}
}
}
}
}
}
}
}
}
}
},
OperationKind.LoopStatement);
}
private static int Abs(int value)
{
return value < 0 ? -value : value;
}
private void Report(OperationAnalysisContext context, SyntaxNode syntax, DiagnosticDescriptor descriptor)
{
context.ReportDiagnostic(Diagnostic.Create(descriptor, syntax.GetLocation()));
}
}
/// Analyzer used to test switch IOperations.
public class SwitchTestAnalyzer : DiagnosticAnalyzer
{
/// Diagnostic category "Reliability".
private const string ReliabilityCategory = "Reliability";
public static readonly DiagnosticDescriptor SparseSwitchDescriptor = new DiagnosticDescriptor(
"SparseSwitchRule",
"Sparse switch",
"Switch has less than one percept density",
ReliabilityCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public static readonly DiagnosticDescriptor NoDefaultSwitchDescriptor = new DiagnosticDescriptor(
"NoDefaultSwitchRule",
"No default switch",
"Switch has no default case",
ReliabilityCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public static readonly DiagnosticDescriptor OnlyDefaultSwitchDescriptor = new DiagnosticDescriptor(
"OnlyDefaultSwitchRule",
"Only default switch",
"Switch only has a default case",
ReliabilityCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
/// Gets the set of supported diagnostic descriptors from this analyzer.
public sealed override ImmutableArray SupportedDiagnostics
{
get
{
return ImmutableArray.Create(SparseSwitchDescriptor,
NoDefaultSwitchDescriptor,
OnlyDefaultSwitchDescriptor);
}
}
public sealed override void Initialize(AnalysisContext context)
{
context.RegisterOperationAction(
(operationContext) =>
{
ISwitchStatement switchOperation = (ISwitchStatement)operationContext.Operation;
long minCaseValue = long.MaxValue;
long maxCaseValue = long.MinValue;
long caseValueCount = 0;
bool hasDefault = false;
bool hasNonDefault = false;
foreach (ISwitchCase switchCase in switchOperation.Cases)
{
foreach (ICaseClause clause in switchCase.Clauses)
{
switch (clause.CaseKind)
{
case CaseKind.SingleValue:
{
hasNonDefault = true;
ISingleValueCaseClause singleValueClause = (ISingleValueCaseClause)clause;
IOperation singleValueExpression = singleValueClause.Value;
if (singleValueExpression != null &&
singleValueExpression.ConstantValue.HasValue &&
singleValueExpression.Type.SpecialType == SpecialType.System_Int32)
{
int singleValue = (int)singleValueExpression.ConstantValue.Value;
caseValueCount += IncludeClause(singleValue, singleValue, ref minCaseValue, ref maxCaseValue);
}
else
{
return;
}
break;
}
case CaseKind.Range:
{
hasNonDefault = true;
IRangeCaseClause rangeClause = (IRangeCaseClause)clause;
IOperation rangeMinExpression = rangeClause.MinimumValue;
IOperation rangeMaxExpression = rangeClause.MaximumValue;
if (rangeMinExpression != null &&
rangeMinExpression.ConstantValue.HasValue &&
rangeMinExpression.Type.SpecialType == SpecialType.System_Int32 &&
rangeMaxExpression != null &&
rangeMaxExpression.ConstantValue.HasValue &&
rangeMaxExpression.Type.SpecialType == SpecialType.System_Int32)
{
int rangeMinValue = (int)rangeMinExpression.ConstantValue.Value;
int rangeMaxValue = (int)rangeMaxExpression.ConstantValue.Value;
caseValueCount += IncludeClause(rangeMinValue, rangeMaxValue, ref minCaseValue, ref maxCaseValue);
}
else
{
return;
}
break;
}
case CaseKind.Relational:
{
hasNonDefault = true;
IRelationalCaseClause relationalClause = (IRelationalCaseClause)clause;
IOperation relationalValueExpression = relationalClause.Value;
if (relationalValueExpression != null &&
relationalValueExpression.ConstantValue.HasValue &&
relationalValueExpression.Type.SpecialType == SpecialType.System_Int32)
{
int rangeMinValue = int.MaxValue;
int rangeMaxValue = int.MinValue;
int relationalValue = (int)relationalValueExpression.ConstantValue.Value;
switch (relationalClause.Relation)
{
case BinaryOperationKind.IntegerEquals:
rangeMinValue = relationalValue;
rangeMaxValue = relationalValue;
break;
case BinaryOperationKind.IntegerNotEquals:
return;
case BinaryOperationKind.IntegerLessThan:
rangeMinValue = int.MinValue;
rangeMaxValue = relationalValue - 1;
break;
case BinaryOperationKind.IntegerLessThanOrEqual:
rangeMinValue = int.MinValue;
rangeMaxValue = relationalValue;
break;
case BinaryOperationKind.IntegerGreaterThanOrEqual:
rangeMinValue = relationalValue;
rangeMaxValue = int.MaxValue;
break;
case BinaryOperationKind.IntegerGreaterThan:
rangeMinValue = relationalValue + 1;
rangeMaxValue = int.MaxValue;
break;
}
caseValueCount += IncludeClause(rangeMinValue, rangeMaxValue, ref minCaseValue, ref maxCaseValue);
}
else
{
return;
}
break;
}
case CaseKind.Default:
{
hasDefault = true;
break;
}
}
}
}
long span = maxCaseValue - minCaseValue + 1;
if (caseValueCount == 0 && !hasDefault ||
caseValueCount != 0 && span / caseValueCount > 100)
{
Report(operationContext, switchOperation.Value.Syntax, SparseSwitchDescriptor);
}
if (!hasDefault)
{
Report(operationContext, switchOperation.Value.Syntax, NoDefaultSwitchDescriptor);
}
if (hasDefault && !hasNonDefault)
{
Report(operationContext, switchOperation.Value.Syntax, OnlyDefaultSwitchDescriptor);
}
},
OperationKind.SwitchStatement);
}
private static int IncludeClause(int clauseMinValue, int clauseMaxValue, ref long minCaseValue, ref long maxCaseValue)
{
if (clauseMinValue < minCaseValue)
{
minCaseValue = clauseMinValue;
}
if (clauseMaxValue > maxCaseValue)
{
maxCaseValue = clauseMaxValue;
}
return clauseMaxValue - clauseMinValue + 1;
}
private void Report(OperationAnalysisContext context, SyntaxNode syntax, DiagnosticDescriptor descriptor)
{
context.ReportDiagnostic(Diagnostic.Create(descriptor, syntax.GetLocation()));
}
}
/// Analyzer used to test invocaton IOperations.
public class InvocationTestAnalyzer : DiagnosticAnalyzer
{
/// Diagnostic category "Reliability".
private const string ReliabilityCategory = "Reliability";
public static readonly DiagnosticDescriptor BigParamArrayArgumentsDescriptor = new DiagnosticDescriptor(
"BigParamarrayRule",
"Big Paramarray",
"Paramarray has more than 10 elements",
ReliabilityCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public static readonly DiagnosticDescriptor OutOfNumericalOrderArgumentsDescriptor = new DiagnosticDescriptor(
"OutOfOrderArgumentsRule",
"Out of order arguments",
"Argument values are not in increasing order",
ReliabilityCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public static readonly DiagnosticDescriptor UseDefaultArgumentDescriptor = new DiagnosticDescriptor(
"UseDefaultArgument",
"Use default argument",
"Invocation uses default argument {0}",
ReliabilityCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public static readonly DiagnosticDescriptor InvalidArgumentDescriptor = new DiagnosticDescriptor(
"InvalidArgument",
"Invalid argument",
"Invocation has invalid argument",
ReliabilityCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
/// Gets the set of supported diagnostic descriptors from this analyzer.
public sealed override ImmutableArray SupportedDiagnostics
{
get
{
return ImmutableArray.Create(BigParamArrayArgumentsDescriptor,
OutOfNumericalOrderArgumentsDescriptor,
UseDefaultArgumentDescriptor,
InvalidArgumentDescriptor);
}
}
public sealed override void Initialize(AnalysisContext context)
{
context.RegisterOperationAction(
(operationContext) =>
{
IInvocationExpression invocation = (IInvocationExpression)operationContext.Operation;
long priorArgumentValue = long.MinValue;
foreach (IArgument argument in invocation.ArgumentsInParameterOrder)
{
if (argument.IsInvalid)
{
operationContext.ReportDiagnostic(Diagnostic.Create(InvalidArgumentDescriptor, argument.Syntax.GetLocation()));
return;
}
if (argument.ArgumentKind == ArgumentKind.DefaultValue)
{
operationContext.ReportDiagnostic(Diagnostic.Create(UseDefaultArgumentDescriptor, invocation.Syntax.GetLocation(), argument.Parameter.Name));
}
TestAscendingArgument(operationContext, argument.Value, ref priorArgumentValue);
if (argument.ArgumentKind == ArgumentKind.ParamArray)
{
IArrayCreationExpression arrayArgument = argument.Value as IArrayCreationExpression;
if (arrayArgument != null)
{
var initializer = arrayArgument.Initializer;
if (initializer != null)
{
if (initializer.ElementValues.Length > 10)
{
Report(operationContext, invocation.Syntax, BigParamArrayArgumentsDescriptor);
}
foreach (IOperation element in initializer.ElementValues)
{
TestAscendingArgument(operationContext, element, ref priorArgumentValue);
}
}
}
}
}
},
OperationKind.InvocationExpression);
}
private static void TestAscendingArgument(OperationAnalysisContext operationContext, IOperation argument, ref long priorArgumentValue)
{
Optional