Skip to content

Commit 1a793d8

Browse files
committed
C#: Simplify the implementation to avoid introducing synthetic assignments.
1 parent da699b9 commit 1a793d8

1 file changed

Lines changed: 42 additions & 93 deletions

File tree

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ElementAccess.cs

Lines changed: 42 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -32,52 +32,30 @@ private static void SetExprArgument(TextWriter trapFile, Expression left, Expres
3232
trapFile.expr_argument(right, 0);
3333
}
3434

35-
private Expression MakeSubtractionExpression(IExpressionParentEntity parent, int child)
35+
private Expression MakeZeroFromEndExpression(IExpressionParentEntity parent, int child)
3636
{
3737
var info = new ExpressionInfo(
3838
Context,
3939
AnnotatedTypeSymbol.CreateNotAnnotated(Context.Compilation.GetSpecialType(SpecialType.System_Int32)),
4040
Location,
41-
ExprKind.SUB,
41+
ExprKind.INDEX,
4242
parent,
4343
child,
4444
isCompilerGenerated: true,
4545
null);
4646

47-
return new Expression(info);
48-
}
47+
var index = new Expression(info);
4948

50-
private Expression 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-
return length;
49+
MakeZeroLiteral(index, 0);
50+
return index;
6751
}
6852

69-
private Expression CreateFromIndexExpression(TextWriter trapFile, IPropertySymbol lengthPropertySymbol, IExpressionParentEntity parent, int child, PrefixUnaryExpressionSyntax index)
53+
private Expression MakeZeroLiteral(IExpressionParentEntity parent, int child)
7054
{
71-
var sub = MakeSubtractionExpression(parent, child);
72-
MakeLengthPropertyCall(trapFile, lengthPropertySymbol, sub, 0);
73-
var info = new ExpressionNodeInfo(Context, index.Operand, sub, 1)
74-
{
75-
IsCompilerGenerated = true
76-
};
77-
Factory.Create(info);
78-
return sub;
55+
return Literal.CreateGenerated(Context, parent, child, Context.Compilation.GetSpecialType(SpecialType.System_Int32), 0, Location);
7956
}
8057

58+
8159
/// <summary>
8260
/// It is assumed that either the input is
8361
/// 1. A normal expression that can be used as endpoint (e.g a constant like "3").
@@ -87,18 +65,16 @@ private Expression CreateFromIndexExpression(TextWriter trapFile, IPropertySymbo
8765
/// <param name="parent">The parent expression entity.</param>
8866
/// <param name="child">The child index within the parent.</param>
8967
/// <returns>An expression representing the endpoint of a range to be used in conjunction with a slice operation.</returns>
90-
private Expression CreateFromRangeEndpoint(TextWriter trapFile, IPropertySymbol lengthPropertySymbol, ExpressionSyntax syntax, IExpressionParentEntity parent, int child)
68+
private Expression MakeFromRangeEndpoint(ExpressionSyntax syntax, IExpressionParentEntity parent, int child)
9169
{
92-
if (syntax.Kind() == SyntaxKind.IndexExpression && syntax is PrefixUnaryExpressionSyntax index)
93-
{
94-
return CreateFromIndexExpression(trapFile, lengthPropertySymbol, parent, child, index);
95-
}
96-
9770
var info = new ExpressionNodeInfo(Context, syntax, parent, child)
9871
{
9972
IsCompilerGenerated = true
10073
};
101-
return Factory.Create(info);
74+
75+
return syntax.Kind() == SyntaxKind.IndexExpression
76+
? PrefixUnary.Create(info.SetKind(ExprKind.INDEX))
77+
: Factory.Create(info);
10278
}
10379

10480
/// <summary>
@@ -107,14 +83,9 @@ private Expression CreateFromRangeEndpoint(TextWriter trapFile, IPropertySymbol
10783
/// </summary>
10884
/// <param name="method">The method symbol to check.</param>
10985
/// <returns>True if the method is a slice method; false otherwise.</returns>
110-
private bool IsSliceWithRange(IMethodSymbol method, [NotNullWhen(true)] out IPropertySymbol? lengthPropertySymbol, [NotNullWhen(true)] out RangeExpressionSyntax? range)
86+
private bool IsSliceWithRange(IMethodSymbol method, [NotNullWhen(true)] out RangeExpressionSyntax? range)
11187
{
11288
range = null;
113-
lengthPropertySymbol = method
114-
.ContainingType
115-
.GetMembers("Length")
116-
.OfType<IPropertySymbol>()
117-
.FirstOrDefault();
11889

11990
if (argumentList.Arguments.Count == 1)
12091
{
@@ -123,63 +94,42 @@ private bool IsSliceWithRange(IMethodSymbol method, [NotNullWhen(true)] out IPro
12394

12495
return (method.Name == "Slice" || method.Name == "Substring")
12596
&& method.Parameters.Length == 2
126-
&& lengthPropertySymbol is not null
12797
&& range is not null;
12898
}
12999

130100
/// <summary>
131-
/// Populates a slice method call based on the given range and length property symbol.
101+
/// Populates a slice method call based on the given range.
102+
/// Roslyn translates indexer accesses with range expressions in the following way.
103+
/// 1. s[a..b] -> s.Slice(a, b - a)
104+
/// 2. s[..b] -> s.Slice(0, b)
105+
/// 3. s[a..] -> s.Slice(a, s.Length - a)
106+
/// 4. s[..] -> s.Slice(0, s.Length)
107+
/// However, it is possible that both the qualifier or the index endpoints may contain method calls.
108+
/// If we want to translate this accurately, we would need to introduce synthetic statements for qualifier and
109+
/// the endpoints, which should then be used in the slice method call.
110+
/// To avoid this, we translate as follows.
111+
/// 1. s[a..b] -> s.Slice(a, b)
112+
/// 2. s[..b] -> s.Slice(0, b)
113+
/// 3. s[a..] -> s.Slice(a, ^0)
114+
/// 4. s[..] -> s.Slice(0, ^0)
115+
///
116+
/// Even though index expressions can't technically be used in this way, they signal that we
117+
/// could perceive ^b as "length - b".
132118
/// </summary>
133119
/// <param name="trapFile">The trap file to write to.</param>
134-
/// <param name="lengthPropertySymbol">The length property symbol.</param>
135120
/// <param name="slice">The slice method symbol.</param>
136121
/// <param name="range">The range expression syntax.</param>
137-
private void PopulateSlice(TextWriter trapFile, IPropertySymbol lengthPropertySymbol, IMethodSymbol slice, RangeExpressionSyntax range)
122+
private void PopulateSlice(TextWriter trapFile, IMethodSymbol slice, RangeExpressionSyntax range)
138123
{
139-
// 1. s[a..b] -> s.Slice(a, b - a)
140-
// 2. s[..b] -> s.Slice(0, b)
141-
// 3. s[a..] -> s.Slice(a, s.Length - a)
142-
// 4. s[..] -> s.Slice(0, s.Length)
143-
// Furthermore, note that uses of index expressions (e.g. s[2..^1]) within the range
144-
// get translated to length - index, so we need to handle this as well.
145-
switch (range.LeftOperand, range.RightOperand)
146-
{
147-
case (ExpressionSyntax lsyntax, ExpressionSyntax rsyntax):
148-
{
149-
var left = CreateFromRangeEndpoint(trapFile, lengthPropertySymbol, lsyntax, this, 0);
150-
var right = MakeSubtractionExpression(this, 1);
151-
152-
CreateFromRangeEndpoint(trapFile, lengthPropertySymbol, rsyntax, right, 0);
153-
CreateFromRangeEndpoint(trapFile, lengthPropertySymbol, lsyntax, right, 1);
154-
SetExprArgument(trapFile, left, right);
155-
break;
156-
}
157-
case (null, ExpressionSyntax rsyntax):
158-
{
159-
var left = Literal.CreateGenerated(Context, this, 0, Context.Compilation.GetSpecialType(SpecialType.System_Int32), 0, Location);
160-
var right = CreateFromRangeEndpoint(trapFile, lengthPropertySymbol, rsyntax, this, 1);
161-
SetExprArgument(trapFile, left, right);
162-
break;
163-
}
164-
case (ExpressionSyntax lsyntax, null):
165-
{
166-
167-
var left = CreateFromRangeEndpoint(trapFile, lengthPropertySymbol, lsyntax, this, 0);
168-
var right = MakeSubtractionExpression(this, 1);
169-
MakeLengthPropertyCall(trapFile, lengthPropertySymbol, right, 0);
170-
CreateFromRangeEndpoint(trapFile, lengthPropertySymbol, lsyntax, right, 1);
171-
SetExprArgument(trapFile, left, right);
172-
break;
173-
}
174-
case (null, null):
175-
{
176-
var left = Literal.CreateGenerated(Context, this, 0, Context.Compilation.GetSpecialType(SpecialType.System_Int32), 0, Location);
177-
var right = MakeLengthPropertyCall(trapFile, lengthPropertySymbol, this, 1);
178-
SetExprArgument(trapFile, left, right);
179-
break;
180-
}
181-
}
124+
var left = range.LeftOperand is ExpressionSyntax lsyntax
125+
? MakeFromRangeEndpoint(lsyntax, this, 0)
126+
: MakeZeroLiteral(this, 0);
127+
128+
var right = range.RightOperand is ExpressionSyntax rsyntax
129+
? MakeFromRangeEndpoint(rsyntax, this, 1)
130+
: MakeZeroFromEndExpression(this, 1);
182131

132+
SetExprArgument(trapFile, left, right);
183133
trapFile.expr_call(this, Method.Create(Context, slice));
184134
}
185135

@@ -198,13 +148,12 @@ protected override void PopulateExpression(TextWriter trapFile)
198148
Create(Context, qualifier, this, -1);
199149

200150
var target = GetTargetSymbol();
201-
if (target is IMethodSymbol method && IsSliceWithRange(method, out var lengthPropertySymbol, out var range))
151+
if (target is IMethodSymbol method && IsSliceWithRange(method, out var range))
202152
{
203153
// When an indexer on a span or string is used in conjunction with a range expression, the compiler translates
204154
// this into a call to the "Slice" or "Substring" method.
205155
// In this case, we want to populate a slice/substring method call instead of an indexer access.
206-
// E.g s[1..4] gets translated to s.Slice(1, 4 - 1) if s is a span.
207-
PopulateSlice(trapFile, lengthPropertySymbol, method, range);
156+
PopulateSlice(trapFile, method, range);
208157
return;
209158
}
210159

0 commit comments

Comments
 (0)