1+ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+ using System . Collections . Generic ;
4+ using Microsoft . CodeAnalysis ;
5+ using Microsoft . CodeAnalysis . CSharp ;
6+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
7+ using Microsoft . CodeAnalysis . Diagnostics ;
8+ using Microsoft . CodeAnalysis . Operations ;
9+ using Microsoft . NetCore . Analyzers . Performance ;
10+
11+ namespace Microsoft . NetCore . CSharp . Analyzers . Performance
12+ {
13+ /// <inheritdoc/>
14+ [ DiagnosticAnalyzer ( LanguageNames . CSharp ) ]
15+ public sealed class CSharpUseSearchValuesAnalyzer : UseSearchValuesAnalyzer
16+ {
17+ // char[] myField = new char[] { 'a', 'b', 'c' };
18+ // char[] myField = new[] { 'a', 'b', 'c' };
19+ // char[] myField = "abc".ToCharArray();
20+ // char[] myField = ConstString.ToCharArray();
21+ // byte[] myField = new[] { (byte)'a', (byte)'b', (byte)'c' };
22+ protected override bool IsConstantByteOrCharArrayVariableDeclaratorSyntax ( SemanticModel semanticModel , SyntaxNode syntax , out int length )
23+ {
24+ length = 0 ;
25+
26+ return
27+ syntax is VariableDeclaratorSyntax variableDeclarator &&
28+ variableDeclarator . Initializer ? . Value is { } initializer &&
29+ IsConstantByteOrCharArrayCreationExpression ( semanticModel , initializer , values : null , out length ) ;
30+ }
31+
32+ // ReadOnlySpan<char> myProperty => new char[] { 'a', 'b', 'c' };
33+ // ReadOnlySpan<char> myProperty => new[] { 'a', 'b', 'c' };
34+ // ReadOnlySpan<char> myProperty => "abc".ToCharArray();
35+ // ReadOnlySpan<char> myProperty => ConstString.ToCharArray();
36+ // ReadOnlySpan<byte> myProperty => new[] { (byte)'a', (byte)'b', (byte)'c' };
37+ // ReadOnlySpan<byte> myProperty => "abc"u8;
38+ // ReadOnlySpan<byte> myProperty { get => "abc"u8; }
39+ // ReadOnlySpan<byte> myProperty { get { return "abc"u8; } }
40+ protected override bool IsConstantByteOrCharReadOnlySpanPropertyDeclarationSyntax ( SemanticModel semanticModel , SyntaxNode syntax , out int length )
41+ {
42+ length = 0 ;
43+
44+ return
45+ syntax is PropertyDeclarationSyntax propertyDeclaration &&
46+ TryGetPropertyGetterExpression ( propertyDeclaration ) is { } expression &&
47+ ( IsConstantByteOrCharArrayCreationExpression ( semanticModel , expression , values : null , out length ) || IsUtf8StringLiteralExpression ( expression , out length ) ) ;
48+ }
49+
50+ protected override bool IsConstantByteOrCharArrayCreationSyntax ( SemanticModel semanticModel , SyntaxNode syntax , out int length )
51+ {
52+ length = 0 ;
53+
54+ return
55+ syntax is ExpressionSyntax expression &&
56+ IsConstantByteOrCharArrayCreationExpression ( semanticModel , expression , values : null , out length ) ;
57+ }
58+
59+ internal static ExpressionSyntax ? TryGetPropertyGetterExpression ( PropertyDeclarationSyntax propertyDeclaration )
60+ {
61+ var expression = propertyDeclaration . ExpressionBody ? . Expression ;
62+
63+ if ( expression is null &&
64+ propertyDeclaration . AccessorList ? . Accessors is [ var accessor ] &&
65+ accessor . IsKind ( SyntaxKind . GetAccessorDeclaration ) )
66+ {
67+ expression = accessor . ExpressionBody ? . Expression ;
68+
69+ if ( expression is null &&
70+ accessor . Body ? . Statements is [ var statement ] &&
71+ statement is ReturnStatementSyntax returnStatement )
72+ {
73+ expression = returnStatement . Expression ;
74+ }
75+ }
76+
77+ return expression ;
78+ }
79+
80+ // new char[] { 'a', 'b', 'c' };
81+ // new[] { 'a', 'b', 'c' };
82+ // new[] { (byte)'a', (byte)'b', (byte)'c' };
83+ // "abc".ToCharArray()
84+ // ConstString.ToCharArray()
85+ internal static bool IsConstantByteOrCharArrayCreationExpression ( SemanticModel semanticModel , ExpressionSyntax expression , List < char > ? values , out int length )
86+ {
87+ length = 0 ;
88+
89+ InitializerExpressionSyntax ? arrayInitializer = null ;
90+
91+ if ( expression is ArrayCreationExpressionSyntax arrayCreation )
92+ {
93+ arrayInitializer = arrayCreation . Initializer ;
94+ }
95+ else if ( expression is ImplicitArrayCreationExpressionSyntax implicitArrayCreation )
96+ {
97+ arrayInitializer = implicitArrayCreation . Initializer ;
98+ }
99+ else if ( expression is InvocationExpressionSyntax invocation )
100+ {
101+ if ( semanticModel . GetOperation ( invocation ) is IInvocationOperation invocationOperation &&
102+ IsConstantStringToCharArrayInvocation ( invocationOperation , out string ? value ) )
103+ {
104+ values ? . AddRange ( value ) ;
105+ length = value . Length ;
106+ return true ;
107+ }
108+ }
109+
110+ if ( arrayInitializer ? . Expressions is { } valueExpressions )
111+ {
112+ foreach ( var valueExpression in valueExpressions )
113+ {
114+ if ( ! TryGetByteOrCharLiteral ( valueExpression , out char value ) )
115+ {
116+ return false ;
117+ }
118+
119+ values ? . Add ( value ) ;
120+ }
121+
122+ length = valueExpressions . Count ;
123+ return true ;
124+ }
125+
126+ return false ;
127+
128+ // 'a' or (byte)'a'
129+ static bool TryGetByteOrCharLiteral ( ExpressionSyntax ? expression , out char value )
130+ {
131+ if ( expression is not null )
132+ {
133+ if ( expression is CastExpressionSyntax cast &&
134+ cast . Type is PredefinedTypeSyntax predefinedType &&
135+ predefinedType . Keyword . IsKind ( SyntaxKind . ByteKeyword ) )
136+ {
137+ expression = cast . Expression ;
138+ }
139+
140+ if ( expression . IsKind ( SyntaxKind . CharacterLiteralExpression ) &&
141+ expression is LiteralExpressionSyntax characterLiteral &&
142+ characterLiteral . Token . Value is char charValue )
143+ {
144+ value = charValue ;
145+ return true ;
146+ }
147+ }
148+
149+ value = default ;
150+ return false ;
151+ }
152+ }
153+
154+ private static bool IsUtf8StringLiteralExpression ( ExpressionSyntax expression , out int length )
155+ {
156+ const SyntaxKind Utf8StringLiteralExpression = ( SyntaxKind ) 8756 ;
157+ const SyntaxKind Utf8StringLiteralToken = ( SyntaxKind ) 8520 ;
158+
159+ if ( expression . IsKind ( Utf8StringLiteralExpression ) &&
160+ expression is LiteralExpressionSyntax literal &&
161+ literal . Token . IsKind ( Utf8StringLiteralToken ) &&
162+ literal . Token . Value is string value )
163+ {
164+ length = value . Length ;
165+ return true ;
166+ }
167+
168+ length = 0 ;
169+ return false ;
170+ }
171+
172+ protected override bool ArrayFieldUsesAreLikelyReadOnly ( SyntaxNode syntax )
173+ {
174+ if ( syntax is not VariableDeclaratorSyntax variableDeclarator ||
175+ variableDeclarator . Identifier . Value is not string fieldName ||
176+ syntax . FirstAncestorOrSelf < TypeDeclarationSyntax > ( ) is not { } typeDeclaration )
177+ {
178+ return false ;
179+ }
180+
181+ // An optimistic implementation that only looks for simple assignments to the field or its array elements.
182+ foreach ( var member in typeDeclaration . Members )
183+ {
184+ bool isCtor = member . IsKind ( SyntaxKind . ConstructorDeclaration ) ;
185+
186+ foreach ( var node in member . DescendantNodes ( ) )
187+ {
188+ if ( node . IsKind ( SyntaxKind . SimpleAssignmentExpression ) &&
189+ node is AssignmentExpressionSyntax assignment )
190+ {
191+ if ( assignment . Left . IsKind ( SyntaxKind . ElementAccessExpression ) )
192+ {
193+ if ( assignment . Left is ElementAccessExpressionSyntax elementAccess &&
194+ IsFieldReference ( elementAccess . Expression , fieldName ) )
195+ {
196+ // s_array[42] = foo;
197+ return false ;
198+ }
199+ }
200+ else if ( isCtor )
201+ {
202+ if ( IsFieldReference ( assignment . Left , fieldName ) )
203+ {
204+ // s_array = foo;
205+ return false ;
206+ }
207+ }
208+ }
209+ }
210+ }
211+
212+ return true ;
213+
214+ static bool IsFieldReference ( ExpressionSyntax expression , string fieldName ) =>
215+ expression . IsKind ( SyntaxKind . IdentifierName ) &&
216+ expression is IdentifierNameSyntax identifierName &&
217+ identifierName . Identifier . Value is string value &&
218+ value == fieldName ;
219+ }
220+ }
221+ }
0 commit comments