1+ using System . Diagnostics . CodeAnalysis ;
12using System . IO ;
23using System . Linq ;
34using Microsoft . CodeAnalysis ;
5+ using Microsoft . CodeAnalysis . CSharp ;
46using Microsoft . CodeAnalysis . CSharp . Syntax ;
57using Semmle . Extraction . Kinds ;
68
@@ -9,7 +11,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
911 internal abstract class ElementAccess : Expression < ExpressionSyntax >
1012 {
1113 protected ElementAccess ( ExpressionNodeInfo info , ExpressionSyntax qualifier , BracketedArgumentListSyntax argumentList )
12- : base ( info . SetKind ( GetKind ( info . Context , qualifier ) ) )
14+ : base ( info . SetKind ( GetKind ( info . Context , info . Node , qualifier ) ) )
1315 {
1416 this . qualifier = qualifier ;
1517 this . argumentList = argumentList ;
@@ -19,32 +21,156 @@ protected ElementAccess(ExpressionNodeInfo info, ExpressionSyntax qualifier, Bra
1921 private readonly BracketedArgumentListSyntax argumentList ;
2022
2123
22- private IPropertySymbol ? GetIndexerSymbol ( )
24+ private ISymbol ? GetTargetSymbol ( )
2325 {
24- var symbol = Context . GetSymbolInfo ( base . Syntax ) . Symbol ;
26+ return Context . GetSymbolInfo ( base . Syntax ) . Symbol ;
27+ }
28+
29+ private static void SetExprArgument ( TextWriter trapFile , Expression left , Expression right )
30+ {
31+ trapFile . expr_argument ( left , 0 ) ;
32+ trapFile . expr_argument ( right , 0 ) ;
33+ }
34+
35+ private Expression MakeSubtractionExpression ( IExpressionParentEntity parent , int child )
36+ {
37+ var info = new ExpressionInfo (
38+ Context ,
39+ AnnotatedTypeSymbol . CreateNotAnnotated ( Context . Compilation . GetSpecialType ( SpecialType . System_Int32 ) ) ,
40+ Location ,
41+ ExprKind . SUB ,
42+ parent ,
43+ child ,
44+ isCompilerGenerated : true ,
45+ null ) ;
46+
47+ return new Expression ( info ) ;
48+ }
49+
50+ private void MakeLengthPropertyCall ( TextWriter trapFile , IPropertySymbol lengthPropertySymbol , IExpressionParentEntity parent , int child )
51+ {
52+ var lengthInfo = new ExpressionInfo (
53+ Context ,
54+ AnnotatedTypeSymbol . CreateNotAnnotated ( Context . Compilation . GetSpecialType ( SpecialType . System_Int32 ) ) ,
55+ Location ,
56+ ExprKind . PROPERTY_ACCESS ,
57+ parent ,
58+ child ,
59+ isCompilerGenerated : true ,
60+ null ) ;
61+ var length = new Expression ( lengthInfo ) ;
62+ Create ( Context , qualifier , length , - 1 ) ;
63+
64+ var lengthProp = Property . Create ( Context , lengthPropertySymbol ) ;
65+ trapFile . expr_access ( length , lengthProp ) ;
66+ }
67+
68+ private Expression CreateFromIndexExpression ( TextWriter trapFile , IPropertySymbol lengthPropertySymbol , IExpressionParentEntity parent , int child , PrefixUnaryExpressionSyntax index )
69+ {
70+ var sub = MakeSubtractionExpression ( parent , child ) ;
71+ MakeLengthPropertyCall ( trapFile , lengthPropertySymbol , sub , 0 ) ;
72+ var info = new ExpressionNodeInfo ( Context , index . Operand , sub , 1 )
73+ {
74+ IsCompilerGenerated = true
75+ } ;
76+ Factory . Create ( info ) ;
77+ return sub ;
78+ }
79+
80+ /// <summary>
81+ /// It is assumed that either the input is
82+ /// 1. A normal expression that can be used as endpoint (e.g a constant like "3").
83+ /// 2. An index expression indicating that we should read from the end (e.g "^1").
84+ /// </summary>
85+ /// <param name="syntax">The syntax node representing the range endpoint.</param>
86+ /// <param name="parent">The parent expression entity.</param>
87+ /// <param name="child">The child index within the parent.</param>
88+ /// <returns>An expression representing the endpoint of a range to be used in conjunction with a slice operation.</returns>
89+ private Expression CreateFromRangeEndpoint ( TextWriter trapFile , IPropertySymbol lengthPropertySymbol , ExpressionSyntax syntax , IExpressionParentEntity parent , int child )
90+ {
91+ if ( syntax . Kind ( ) == SyntaxKind . IndexExpression && syntax is PrefixUnaryExpressionSyntax index )
92+ {
93+ return CreateFromIndexExpression ( trapFile , lengthPropertySymbol , parent , child , index ) ;
94+ }
95+
96+ var info = new ExpressionNodeInfo ( Context , syntax , parent , child )
97+ {
98+ IsCompilerGenerated = true
99+ } ;
100+ return Factory . Create ( info ) ;
101+ }
102+
103+ /// <summary>
104+ /// Determines whether the given method is a slice method, which is defined as a method with
105+ /// the name "Slice" or "SubString" and two parameters.
106+ /// </summary> <param name="method">The method symbol to check.</param>
107+ /// <returns>True if the method is a slice method, false otherwise.</returns>
108+ private bool IsSliceWithRange ( IMethodSymbol method , [ NotNullWhen ( true ) ] out IPropertySymbol ? lengthPropertySymbol , [ NotNullWhen ( true ) ] out RangeExpressionSyntax ? range )
109+ {
110+ range = null ;
111+ lengthPropertySymbol = method
112+ . ContainingType
113+ . GetMembers ( "Length" )
114+ . OfType < IPropertySymbol > ( )
115+ . FirstOrDefault ( ) ;
116+
117+ if ( argumentList . Arguments . Count == 1 )
118+ {
119+ range = argumentList . Arguments [ 0 ] . Expression as RangeExpressionSyntax ;
120+ }
25121
26- if ( symbol is IPropertySymbol { IsIndexer : true } indexer )
27- return indexer ;
122+ return ( method . Name == "Slice" || method . Name == "Substring" )
123+ && method . Parameters . Length == 2
124+ && lengthPropertySymbol is not null
125+ && range is not null ;
126+ }
28127
29- // In some cases, Roslyn translates the use of range expressions directly into method calls.
30- // E.g. `a[0..3]` is translated into `a.Slice(0, 3)`, if `a` is a `Span<T>`.
31- // In this case, we want to populate the indexer access as normal (as this reflects the source code more accurately).
32- if ( symbol is IMethodSymbol method )
128+ /// <summary>
129+ /// Populates a slice method call based on the given range and length property symbol.
130+ /// </summary>
131+ /// <param name="trapFile">The trap file to write to.</param>
132+ /// <param name="lengthPropertySymbol">The length property symbol.</param>
133+ /// <param name="slice">The slice method symbol.</param>
134+ /// <param name="range">The range expression syntax.</param>
135+ private void PopulateSlice ( TextWriter trapFile , IPropertySymbol lengthPropertySymbol , IMethodSymbol slice , RangeExpressionSyntax range )
136+ {
137+ // 1. s[a..b] -> s.Slice(a, b - a)
138+ // 2. s[..b] -> s.Slice(0, b)
139+ // 3. s[a..] -> s.Slice(a, s.Length - a)
140+ // Furthermore, note that uses of index expressions (e.g. s[2..^1]) within the range
141+ // get translated to length - index, so we need to handle this as well.
142+ switch ( range . LeftOperand , range . RightOperand )
33143 {
34- var indexers = method
35- . ContainingType
36- . GetMembers ( )
37- . OfType < IPropertySymbol > ( )
38- . Where ( p => p . IsIndexer ) ;
144+ case ( ExpressionSyntax lsyntax , ExpressionSyntax rsyntax ) :
145+ {
146+ var left = CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , lsyntax , this , 0 ) ;
147+ var right = MakeSubtractionExpression ( this , 1 ) ;
39148
40- var intIndexer = indexers
41- . Where ( i => i . Parameters . Length == 1 && i . Parameters [ 0 ] . Type . SpecialType == SpecialType . System_Int32 )
42- . FirstOrDefault ( ) ;
149+ CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , rsyntax , right , 0 ) ;
150+ CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , lsyntax , right , 1 ) ;
151+ SetExprArgument ( trapFile , left , right ) ;
152+ break ;
153+ }
154+ case ( null , ExpressionSyntax rsyntax ) :
155+ {
156+ var left = Literal . CreateGenerated ( Context , this , 0 , Context . Compilation . GetSpecialType ( SpecialType . System_Int32 ) , 0 , Location ) ;
157+ var right = CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , rsyntax , this , 1 ) ;
158+ SetExprArgument ( trapFile , left , right ) ;
159+ break ;
160+ }
161+ case ( ExpressionSyntax lsyntax , null ) :
162+ {
43163
44- return intIndexer ;
164+ var left = CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , lsyntax , this , 0 ) ;
165+ var right = MakeSubtractionExpression ( this , 1 ) ;
166+ MakeLengthPropertyCall ( trapFile , lengthPropertySymbol , right , 0 ) ;
167+ CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , lsyntax , right , 1 ) ;
168+ SetExprArgument ( trapFile , left , right ) ;
169+ break ;
170+ }
45171 }
46172
47- return null ;
173+ trapFile . expr_call ( this , Method . Create ( Context , slice ) ) ;
48174 }
49175
50176 protected override void PopulateExpression ( TextWriter trapFile )
@@ -60,10 +186,20 @@ protected override void PopulateExpression(TextWriter trapFile)
60186 else
61187 {
62188 Create ( Context , qualifier , this , - 1 ) ;
63- PopulateArguments ( trapFile , argumentList , 0 ) ;
64189
65- var indexer = GetIndexerSymbol ( ) ;
66- if ( indexer is not null )
190+ var target = GetTargetSymbol ( ) ;
191+ if ( target is IMethodSymbol method && IsSliceWithRange ( method , out var lengthPropertySymbol , out var range ) )
192+ {
193+ // When an indexer on a span or string is used in conjunction with a range expression, the compiler translates
194+ // this into a call to the "Slice" or "Substring" method.
195+ // In this case, we want to populate a slice/substring method call instead of an indexer access.
196+ // E.g s[1..4] gets translated to s.Slice(1, 4 - 1) if s is a span.
197+ PopulateSlice ( trapFile , lengthPropertySymbol , method , range ) ;
198+ return ;
199+ }
200+
201+ PopulateArguments ( trapFile , argumentList , 0 ) ;
202+ if ( target is IPropertySymbol { IsIndexer : true } indexer )
67203 {
68204 trapFile . expr_access ( this , Indexer . Create ( Context , indexer ) ) ;
69205 }
@@ -75,8 +211,11 @@ protected override void PopulateExpression(TextWriter trapFile)
75211 private static bool IsArray ( ITypeSymbol symbol ) =>
76212 symbol . TypeKind == Microsoft . CodeAnalysis . TypeKind . Array || symbol . IsInlineArray ( ) ;
77213
78- private static ExprKind GetKind ( Context cx , ExpressionSyntax qualifier )
214+ private static ExprKind GetKind ( Context cx , ExpressionSyntax syntax , ExpressionSyntax qualifier )
79215 {
216+ if ( cx . GetSymbolInfo ( syntax ) . Symbol is IMethodSymbol )
217+ return ExprKind . METHOD_INVOCATION ;
218+
80219 var qualifierType = cx . GetType ( qualifier ) ;
81220
82221 // This is a compilation error, so make a guess and continue.
0 commit comments