Skip to content

Commit af6e1bd

Browse files
committed
C#: Extract alignment and format clauses of string interpolation expressions.
1 parent 179bae8 commit af6e1bd

File tree

9 files changed

+104
-22
lines changed

9 files changed

+104
-22
lines changed

csharp/extractor/Semmle.Extraction.CSharp/CodeAnalysisExtensions/SymbolExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ public AnnotatedTypeSymbol(ITypeSymbol? symbol, NullableAnnotation nullability)
2929
symbol is null ? (AnnotatedTypeSymbol?)null : new AnnotatedTypeSymbol(symbol, NullableAnnotation.None);
3030
}
3131

32+
internal static class AnnotatedTypeSymbolExtensions
33+
{
34+
/// <summary>
35+
/// Returns true if the type is a string type.
36+
/// </summary>
37+
public static bool IsStringType(this AnnotatedTypeSymbol? type) =>
38+
type.HasValue && type.Value.Symbol?.SpecialType == SpecialType.System_String;
39+
}
40+
3241
internal static class SymbolExtensions
3342
{
3443
/// <summary>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ private Expression CreateChild(Context cx, ExpressionSyntax node, int child)
1818
{
1919
// If this is a "+" expression we might need to wrap the child expressions
2020
// in ToString calls
21-
return Kind == ExprKind.ADD
21+
return Kind == ExprKind.ADD && Type.IsStringType()
2222
? ImplicitToString.Create(cx, node, this, child)
2323
: Create(cx, node, this, child);
2424
}

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,13 @@ private ImplicitToString(ExpressionNodeInfo info, IMethodSymbol toString) : base
3939
Context.TrapWriter.Writer.expr_call(this, target);
4040
}
4141

42-
private static bool IsStringType(AnnotatedTypeSymbol? type) =>
43-
type.HasValue && type.Value.Symbol?.SpecialType == SpecialType.System_String;
44-
4542
/// <summary>
4643
/// Creates a new expression, adding a compiler generated `ToString` call if required.
4744
/// </summary>
48-
public static Expression Create(Context cx, ExpressionSyntax node, Expression parent, int child)
45+
public static Expression Create(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child)
4946
{
5047
var info = new ExpressionNodeInfo(cx, node, parent, child);
51-
return CreateFromNode(info.SetImplicitToString(IsStringType(parent.Type) && !IsStringType(info.Type)));
48+
return CreateFromNode(info.SetImplicitToString(!info.Type.IsStringType()));
5249
}
5350

5451
/// <summary>

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.IO;
2-
using Microsoft.CodeAnalysis;
32
using Microsoft.CodeAnalysis.CSharp;
43
using Microsoft.CodeAnalysis.CSharp.Syntax;
54
using Semmle.Extraction.Kinds;
@@ -21,15 +20,7 @@ protected override void PopulateExpression(TextWriter trapFile)
2120
{
2221
case SyntaxKind.Interpolation:
2322
var interpolation = (InterpolationSyntax)c;
24-
var exp = interpolation.Expression;
25-
if (Context.GetTypeInfo(exp).Type is ITypeSymbol type && !type.ImplementsIFormattable())
26-
{
27-
ImplicitToString.Create(Context, exp, this, child++);
28-
}
29-
else
30-
{
31-
Create(Context, exp, this, child++);
32-
}
23+
new InterpolatedStringInsert(Context, interpolation, this, child++);
3324
break;
3425
case SyntaxKind.InterpolatedStringText:
3526
// Create a string literal
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
using Semmle.Extraction.Kinds;
4+
5+
namespace Semmle.Extraction.CSharp.Entities.Expressions
6+
{
7+
internal class InterpolatedStringInsert : Expression
8+
{
9+
public InterpolatedStringInsert(Context cx, InterpolationSyntax syntax, Expression parent, int child) :
10+
base(new ExpressionInfo(cx, null, cx.CreateLocation(syntax.GetLocation()), ExprKind.INTERPOLATED_STRING_INSERT, parent, child, isCompilerGenerated: false, null))
11+
{
12+
var exp = syntax.Expression;
13+
if (parent.Type.IsStringType() &&
14+
cx.GetTypeInfo(exp).Type is ITypeSymbol type &&
15+
!type.ImplementsIFormattable())
16+
{
17+
ImplicitToString.Create(cx, exp, this, 0);
18+
}
19+
else
20+
{
21+
Create(cx, exp, this, 0);
22+
}
23+
24+
// Hardcode the child number of the optional alignment clause to 1 and format clause to 2.
25+
// This simplifies the logic in QL.
26+
if (syntax.AlignmentClause?.Value is ExpressionSyntax alignment)
27+
{
28+
Create(cx, alignment, this, 1);
29+
}
30+
31+
if (syntax.FormatClause is InterpolationFormatClauseSyntax format)
32+
{
33+
var f = format.FormatStringToken.ValueText;
34+
var t = AnnotatedTypeSymbol.CreateNotAnnotated(cx.Compilation.GetSpecialType(SpecialType.System_String));
35+
new Expression(new ExpressionInfo(cx, t, cx.CreateLocation(format.GetLocation()), ExprKind.UTF16_STRING_LITERAL, this, 2, isCompilerGenerated: false, f));
36+
}
37+
38+
}
39+
}
40+
41+
}

csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public enum ExprKind
132132
UTF8_STRING_LITERAL = 135,
133133
COLLECTION = 136,
134134
SPREAD_ELEMENT = 137,
135+
INTERPOLATED_STRING_INSERT = 138,
135136
DEFINE_SYMBOL = 999,
136137
}
137138
}

csharp/ql/lib/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ private class LocalTaintExprStepConfiguration extends ControlFlowReachabilityCon
6666
e1 = e2.(InterpolatedStringExpr).getAChild() and
6767
scope = e2
6868
or
69+
e1 = e2.(InterpolatedStringInsertExpr).getInsert() and
70+
scope = e2
71+
or
6972
e2 =
7073
any(OperatorCall oc |
7174
oc.getTarget().(ConversionOperator).fromLibrary() and

csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ private import semmle.code.csharp.TypeRef
3434
* `as` expression (`AsExpr`), a cast (`CastExpr`), a `typeof` expression
3535
* (`TypeofExpr`), a `default` expression (`DefaultValueExpr`), an `await`
3636
* expression (`AwaitExpr`), a `nameof` expression (`NameOfExpr`), an
37-
* interpolated string (`InterpolatedStringExpr`), a qualifiable expression
38-
* (`QualifiableExpr`), or a literal (`Literal`).
37+
* interpolated string (`InterpolatedStringExpr`), an interpolated string
38+
* insert (`InterpolatedStringInsertExpr`), a qualifiable expression (`QualifiableExpr`),
39+
* or a literal (`Literal`).
3940
*/
4041
class Expr extends ControlFlowElement, @expr {
4142
override Location getALocation() { expr_location(this, result) }
@@ -917,6 +918,40 @@ class NameOfExpr extends Expr, @nameof_expr {
917918
override string getAPrimaryQlClass() { result = "NameOfExpr" }
918919
}
919920

921+
/**
922+
* An interpolated string insert, for example `{pi, align:F3}` on line 3 in
923+
*
924+
* ```csharp
925+
* void Pi() {
926+
* float pi = 3.14159f;
927+
* Console.WriteLine($"Hello Pi, {pi,6:F3}");
928+
* }
929+
* ```
930+
*/
931+
class InterpolatedStringInsertExpr extends Expr, @interpolated_string_insert_expr {
932+
override string toString() { result = "{...}" }
933+
934+
override string getAPrimaryQlClass() { result = "InterpolatedStringInsertExpr" }
935+
936+
/**
937+
* Gets the insert expression in this interpolated string insert. For
938+
* example, the insert expression in `{pi, align:F3}` is `pi`.
939+
*/
940+
Expr getInsert() { result = this.getChild(0) }
941+
942+
/**
943+
* Gets the alignment expression in this interpolated string insert, if any.
944+
* For example, the alignment expression in `{pi, align:F3}` is `align`.
945+
*/
946+
Expr getAlignment() { result = this.getChild(1) }
947+
948+
/**
949+
* Gets the format expression in this interpolated string insert, if any.
950+
* For example, the format expression in `{pi, align:F3}` is `F3`.
951+
*/
952+
StringLiteral getFormat() { result = this.getChild(2) }
953+
}
954+
920955
/**
921956
* An interpolated string, for example `$"Hello, {name}!"` on line 2 in
922957
*
@@ -931,16 +966,20 @@ class InterpolatedStringExpr extends Expr, @interpolated_string_expr {
931966

932967
override string getAPrimaryQlClass() { result = "InterpolatedStringExpr" }
933968

969+
/**
970+
* Gets the interpolated string insert at index `i` in this interpolated string, if any.
971+
* For example, the interpolated string insert at index `i = 1` is `{pi, align:F3}`
972+
* in `$"Hello, {pi, align:F3}!"`.
973+
*/
974+
InterpolatedStringInsertExpr getInterpolatedInsert(int i) { result = this.getChild(i) }
975+
934976
/**
935977
* Gets the insert at index `i` in this interpolated string, if any. For
936978
* example, the insert at index `i = 1` is `name` in `$"Hello, {name}!"`.
937979
* Note that there is no insert at index `i = 0`, but instead a text
938980
* element (`getText(0)` gets the text).
939981
*/
940-
Expr getInsert(int i) {
941-
result = this.getChild(i) and
942-
not result instanceof StringLiteral
943-
}
982+
Expr getInsert(int i) { result = this.getInterpolatedInsert(i).getInsert() }
944983

945984
/**
946985
* Gets the text element at index `i` in this interpolated string, if any.

csharp/ql/lib/semmlecode.csharp.dbscheme

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,7 @@ case @expr.kind of
11681168
/* C# 12.0 */
11691169
| 136 = @collection_expr
11701170
| 137 = @spread_element_expr
1171+
| 138 = @interpolated_string_insert_expr
11711172
/* Preprocessor */
11721173
| 999 = @define_symbol_expr
11731174
;

0 commit comments

Comments
 (0)