|
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
using System.Collections.Generic;
using Analyzer.Utilities.Lightup;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.NetCore.Analyzers.Performance;
namespace Microsoft.NetCore.CSharp.Analyzers.Performance
{
/// <inheritdoc/>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpUseSearchValuesAnalyzer : UseSearchValuesAnalyzer
{
// char[] myField = new char[] { 'a', 'b', 'c' };
// char[] myField = new[] { 'a', 'b', 'c' };
// char[] myField = "abc".ToCharArray();
// char[] myField = ConstString.ToCharArray();
// byte[] myField = new[] { (byte)'a', (byte)'b', (byte)'c' };
// char[] myField = ['a', 'b', 'c'];
// byte[] myField = [(byte)'a', (byte)'b', (byte)'c'];
protected override bool IsConstantByteOrCharArrayVariableDeclaratorSyntax(SemanticModel semanticModel, SyntaxNode syntax, out int length)
{
length = 0;
return
syntax is VariableDeclaratorSyntax variableDeclarator &&
variableDeclarator.Initializer?.Value is { } initializer &&
IsConstantByteOrCharArrayCreationExpression(semanticModel, initializer, values: null, out length);
}
// ReadOnlySpan<char> myProperty => new char[] { 'a', 'b', 'c' };
// ReadOnlySpan<char> myProperty => new[] { 'a', 'b', 'c' };
// ReadOnlySpan<char> myProperty => "abc".ToCharArray();
// ReadOnlySpan<char> myProperty => ConstString.ToCharArray();
// ReadOnlySpan<byte> myProperty => new[] { (byte)'a', (byte)'b', (byte)'c' };
// ReadOnlySpan<byte> myProperty => "abc"u8;
// ReadOnlySpan<byte> myProperty { get => "abc"u8; }
// ReadOnlySpan<byte> myProperty { get { return "abc"u8; } }
// ReadOnlySpan<char> myProperty => ['a', 'b', 'c'];
// ReadOnlySpan<byte> myProperty => [(byte)'a', (byte)'b', (byte)'c'];
protected override bool IsConstantByteOrCharReadOnlySpanPropertyDeclarationSyntax(SemanticModel semanticModel, SyntaxNode syntax, out int length)
{
length = 0;
return
syntax is PropertyDeclarationSyntax propertyDeclaration &&
TryGetPropertyGetterExpression(propertyDeclaration) is { } expression &&
(IsConstantByteOrCharArrayCreationExpression(semanticModel, expression, values: null, out length) ||
IsUtf8StringLiteralExpression(expression, out length) ||
(semanticModel.GetOperation(expression) is { } operation && IsConstantByteOrCharCollectionExpression(operation, values: null, out length)));
}
protected override bool IsConstantByteOrCharArrayCreationSyntax(SemanticModel semanticModel, SyntaxNode syntax, out int length)
{
length = 0;
return
syntax is ExpressionSyntax expression &&
IsConstantByteOrCharArrayCreationExpression(semanticModel, expression, values: null, out length);
}
internal static ExpressionSyntax? TryGetPropertyGetterExpression(PropertyDeclarationSyntax propertyDeclaration)
{
var expression = propertyDeclaration.ExpressionBody?.Expression;
if (expression is null &&
propertyDeclaration.AccessorList?.Accessors is [var accessor] &&
accessor.IsKind(SyntaxKind.GetAccessorDeclaration))
{
expression = accessor.ExpressionBody?.Expression;
if (expression is null &&
accessor.Body?.Statements is [var statement] &&
statement is ReturnStatementSyntax returnStatement)
{
expression = returnStatement.Expression;
}
}
return expression;
}
// new char[] { 'a', 'b', 'c' };
// new[] { 'a', 'b', 'c' };
// new[] { (byte)'a', (byte)'b', (byte)'c' };
// "abc".ToCharArray()
// ConstString.ToCharArray()
internal static bool IsConstantByteOrCharArrayCreationExpression(SemanticModel semanticModel, ExpressionSyntax expression, List<char>? values, out int length)
{
length = 0;
InitializerExpressionSyntax? arrayInitializer = null;
if (expression is ArrayCreationExpressionSyntax arrayCreation)
{
arrayInitializer = arrayCreation.Initializer;
}
else if (expression is ImplicitArrayCreationExpressionSyntax implicitArrayCreation)
{
arrayInitializer = implicitArrayCreation.Initializer;
}
else if (expression is InvocationExpressionSyntax invocation)
{
if (semanticModel.GetOperation(invocation) is IInvocationOperation invocationOperation &&
IsConstantStringToCharArrayInvocation(invocationOperation, out string? value))
{
values?.AddRange(value);
length = value.Length;
return true;
}
}
else if (expression.IsKind(SyntaxKindEx.CollectionExpression))
{
return
semanticModel.GetOperation(expression) is { } operation &&
IsConstantByteOrCharCollectionExpression(operation, values, out length);
}
if (arrayInitializer?.Expressions is { } valueExpressions)
{
foreach (var valueExpression in valueExpressions)
{
if (!TryGetByteOrCharLiteral(valueExpression, out char value))
{
return false;
}
values?.Add(value);
}
length = valueExpressions.Count;
return true;
}
return false;
// 'a' or (byte)'a'
static bool TryGetByteOrCharLiteral(ExpressionSyntax? expression, out char value)
{
if (expression is not null)
{
if (expression is CastExpressionSyntax cast &&
cast.Type is PredefinedTypeSyntax predefinedType &&
predefinedType.Keyword.IsKind(SyntaxKind.ByteKeyword))
{
expression = cast.Expression;
}
if (expression.IsKind(SyntaxKind.CharacterLiteralExpression) &&
expression is LiteralExpressionSyntax characterLiteral &&
characterLiteral.Token.Value is char charValue)
{
value = charValue;
return true;
}
}
value = default;
return false;
}
}
private static bool IsUtf8StringLiteralExpression(ExpressionSyntax expression, out int length)
{
if (expression.IsKind(SyntaxKindEx.Utf8StringLiteralExpression) &&
expression is LiteralExpressionSyntax literal &&
literal.Token.IsKind(SyntaxKindEx.Utf8StringLiteralToken) &&
literal.Token.Value is string value)
{
length = value.Length;
return true;
}
length = 0;
return false;
}
protected override bool ArrayFieldUsesAreLikelyReadOnly(SyntaxNode syntax)
{
if (syntax is not VariableDeclaratorSyntax variableDeclarator ||
variableDeclarator.Identifier.Value is not string fieldName ||
syntax.FirstAncestorOrSelf<TypeDeclarationSyntax>() is not { } typeDeclaration)
{
return false;
}
// An optimistic implementation that only looks for simple assignments to the field or its array elements.
foreach (var member in typeDeclaration.Members)
{
bool isCtor = member.IsKind(SyntaxKind.ConstructorDeclaration);
foreach (var node in member.DescendantNodes())
{
if (node.IsKind(SyntaxKind.SimpleAssignmentExpression) &&
node is AssignmentExpressionSyntax assignment)
{
if (assignment.Left.IsKind(SyntaxKind.ElementAccessExpression))
{
if (assignment.Left is ElementAccessExpressionSyntax elementAccess &&
IsFieldReference(elementAccess.Expression, fieldName))
{
// s_array[42] = foo;
return false;
}
}
else if (isCtor)
{
if (IsFieldReference(assignment.Left, fieldName))
{
// s_array = foo;
return false;
}
}
}
}
}
return true;
static bool IsFieldReference(ExpressionSyntax expression, string fieldName) =>
expression.IsKind(SyntaxKind.IdentifierName) &&
expression is IdentifierNameSyntax identifierName &&
identifierName.Identifier.Value is string value &&
value == fieldName;
}
}
} |