Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C#: Extract string interpolation alignment and format. #19089

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,460 changes: 1,460 additions & 0 deletions csharp/downgrades/66044cfa5bbf2ecfabd06ead25e91db2bdd79764/old.dbscheme

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class Expr extends @expr {
string toString() { none() }
}

class TypeOrRef extends @type_or_ref {
string toString() { none() }
}

class InterpolatedStringInsertExpr extends Expr, @interpolated_string_insert_expr { }

private predicate remove_expr(Expr e) {
exists(InterpolatedStringInsertExpr ie |
e = ie
or
// Alignment
expr_parent(e, 1, ie)
or
// Format
expr_parent(e, 2, ie)
)
}

query predicate new_expressions(Expr e, int kind, TypeOrRef t) {
expressions(e, kind, t) and
// Remove the syntheetic intert expression and previously un-extracted children
not remove_expr(e)
}

query predicate new_expr_parent(Expr e, int child, Expr parent) {
expr_parent(e, child, parent) and
not remove_expr(e) and
not remove_expr(parent)
or
// Use the string interpolation as parent instead of the synthetic insert expression
exists(InterpolatedStringInsertExpr ie |
expr_parent(e, 0, ie) and
expr_parent(ie, child, parent)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
description: Remove `interpolated_string_insert_expr` kind.
compatibility: backwards
expressions.rel: run string_interpol_insert.qlo new_expressions
expr_parent.rel: run string_interpol_insert.qlo new_expr_parent
Original file line number Diff line number Diff line change
@@ -29,6 +29,15 @@ public AnnotatedTypeSymbol(ITypeSymbol? symbol, NullableAnnotation nullability)
symbol is null ? (AnnotatedTypeSymbol?)null : new AnnotatedTypeSymbol(symbol, NullableAnnotation.None);
}

internal static class AnnotatedTypeSymbolExtensions
{
/// <summary>
/// Returns true if the type is a string type.
/// </summary>
public static bool IsStringType(this AnnotatedTypeSymbol? type) =>
type.HasValue && type.Value.Symbol?.SpecialType == SpecialType.System_String;
}

internal static class SymbolExtensions
{
/// <summary>
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ private Expression CreateChild(Context cx, ExpressionSyntax node, int child)
{
// If this is a "+" expression we might need to wrap the child expressions
// in ToString calls
return Kind == ExprKind.ADD
return Kind == ExprKind.ADD && Type.IsStringType()
? ImplicitToString.Create(cx, node, this, child)
: Create(cx, node, this, child);
}
Original file line number Diff line number Diff line change
@@ -39,16 +39,13 @@ private ImplicitToString(ExpressionNodeInfo info, IMethodSymbol toString) : base
Context.TrapWriter.Writer.expr_call(this, target);
}

private static bool IsStringType(AnnotatedTypeSymbol? type) =>
type.HasValue && type.Value.Symbol?.SpecialType == SpecialType.System_String;

/// <summary>
/// Creates a new expression, adding a compiler generated `ToString` call if required.
/// </summary>
public static Expression Create(Context cx, ExpressionSyntax node, Expression parent, int child)
public static Expression Create(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child)
{
var info = new ExpressionNodeInfo(cx, node, parent, child);
return CreateFromNode(info.SetImplicitToString(IsStringType(parent.Type) && !IsStringType(info.Type)));
return CreateFromNode(info.SetImplicitToString(!info.Type.IsStringType()));
}

/// <summary>
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.Kinds;
@@ -21,15 +20,7 @@ protected override void PopulateExpression(TextWriter trapFile)
{
case SyntaxKind.Interpolation:
var interpolation = (InterpolationSyntax)c;
var exp = interpolation.Expression;
if (Context.GetTypeInfo(exp).Type is ITypeSymbol type && !type.ImplementsIFormattable())
{
ImplicitToString.Create(Context, exp, this, child++);
}
else
{
Create(Context, exp, this, child++);
}
new InterpolatedStringInsert(Context, interpolation, this, child++);
break;
case SyntaxKind.InterpolatedStringText:
// Create a string literal
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.Kinds;

namespace Semmle.Extraction.CSharp.Entities.Expressions
{
internal class InterpolatedStringInsert : Expression
{
public InterpolatedStringInsert(Context cx, InterpolationSyntax syntax, Expression parent, int child) :
base(new ExpressionInfo(cx, null, cx.CreateLocation(syntax.GetLocation()), ExprKind.INTERPOLATED_STRING_INSERT, parent, child, isCompilerGenerated: false, null))
{
var exp = syntax.Expression;
if (parent.Type.IsStringType() &&
cx.GetTypeInfo(exp).Type is ITypeSymbol type &&
!type.ImplementsIFormattable())
{
ImplicitToString.Create(cx, exp, this, 0);
}
else
{
Create(cx, exp, this, 0);
}

// Hardcode the child number of the optional alignment clause to 1 and format clause to 2.
// This simplifies the logic in QL.
if (syntax.AlignmentClause?.Value is ExpressionSyntax alignment)
{
Create(cx, alignment, this, 1);
}

if (syntax.FormatClause is InterpolationFormatClauseSyntax format)
{
var f = format.FormatStringToken.ValueText;
var t = AnnotatedTypeSymbol.CreateNotAnnotated(cx.Compilation.GetSpecialType(SpecialType.System_String));
new Expression(new ExpressionInfo(cx, t, cx.CreateLocation(format.GetLocation()), ExprKind.UTF16_STRING_LITERAL, this, 2, isCompilerGenerated: false, f));
}

}
}

}
Original file line number Diff line number Diff line change
@@ -132,6 +132,7 @@ public enum ExprKind
UTF8_STRING_LITERAL = 135,
COLLECTION = 136,
SPREAD_ELEMENT = 137,
INTERPOLATED_STRING_INSERT = 138,
DEFINE_SYMBOL = 999,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The *alignment* and *format* clauses in string interpolation expressions are now extracted. That is, in `$"Hello {name,align:format}"` *name*, *align* and *format* are extracted as children of the string interpolation *insert* `{name,align:format}`.
Original file line number Diff line number Diff line change
@@ -66,6 +66,9 @@ private class LocalTaintExprStepConfiguration extends ControlFlowReachabilityCon
e1 = e2.(InterpolatedStringExpr).getAChild() and
scope = e2
or
e1 = e2.(InterpolatedStringInsertExpr).getInsert() and
scope = e2
or
e2 =
any(OperatorCall oc |
oc.getTarget().(ConversionOperator).fromLibrary() and
51 changes: 45 additions & 6 deletions csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll
Original file line number Diff line number Diff line change
@@ -34,8 +34,9 @@ private import semmle.code.csharp.TypeRef
* `as` expression (`AsExpr`), a cast (`CastExpr`), a `typeof` expression
* (`TypeofExpr`), a `default` expression (`DefaultValueExpr`), an `await`
* expression (`AwaitExpr`), a `nameof` expression (`NameOfExpr`), an
* interpolated string (`InterpolatedStringExpr`), a qualifiable expression
* (`QualifiableExpr`), or a literal (`Literal`).
* interpolated string (`InterpolatedStringExpr`), an interpolated string
* insert (`InterpolatedStringInsertExpr`), a qualifiable expression (`QualifiableExpr`),
* or a literal (`Literal`).
*/
class Expr extends ControlFlowElement, @expr {
override Location getALocation() { expr_location(this, result) }
@@ -917,6 +918,40 @@ class NameOfExpr extends Expr, @nameof_expr {
override string getAPrimaryQlClass() { result = "NameOfExpr" }
}

/**
* An interpolated string insert, for example `{pi, align:F3}` on line 3 in
*
* ```csharp
* void Pi() {
* float pi = 3.14159f;
* Console.WriteLine($"Hello Pi, {pi,6:F3}");
* }
* ```
*/
class InterpolatedStringInsertExpr extends Expr, @interpolated_string_insert_expr {
override string toString() { result = "{...}" }

override string getAPrimaryQlClass() { result = "InterpolatedStringInsertExpr" }

/**
* Gets the insert expression in this interpolated string insert. For
* example, the insert expression in `{pi, align:F3}` is `pi`.
*/
Expr getInsert() { result = this.getChild(0) }

/**
* Gets the alignment expression in this interpolated string insert, if any.
* For example, the alignment expression in `{pi, align:F3}` is `align`.
*/
Expr getAlignment() { result = this.getChild(1) }

/**
* Gets the format expression in this interpolated string insert, if any.
* For example, the format expression in `{pi, align:F3}` is `F3`.
*/
StringLiteral getFormat() { result = this.getChild(2) }
}

/**
* An interpolated string, for example `$"Hello, {name}!"` on line 2 in
*
@@ -931,16 +966,20 @@ class InterpolatedStringExpr extends Expr, @interpolated_string_expr {

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

/**
* Gets the interpolated string insert at index `i` in this interpolated string, if any.
* For example, the interpolated string insert at index `i = 1` is `{pi, align:F3}`
* in `$"Hello, {pi, align:F3}!"`.
*/
InterpolatedStringInsertExpr getInterpolatedInsert(int i) { result = this.getChild(i) }

/**
* Gets the insert at index `i` in this interpolated string, if any. For
* example, the insert at index `i = 1` is `name` in `$"Hello, {name}!"`.
* Note that there is no insert at index `i = 0`, but instead a text
* element (`getText(0)` gets the text).
*/
Expr getInsert(int i) {
result = this.getChild(i) and
not result instanceof StringLiteral
}
Expr getInsert(int i) { result = this.getInterpolatedInsert(i).getInsert() }

/**
* Gets the text element at index `i` in this interpolated string, if any.
1 change: 1 addition & 0 deletions csharp/ql/lib/semmlecode.csharp.dbscheme
Original file line number Diff line number Diff line change
@@ -1168,6 +1168,7 @@ case @expr.kind of
/* C# 12.0 */
| 136 = @collection_expr
| 137 = @spread_element_expr
| 138 = @interpolated_string_insert_expr
/* Preprocessor */
| 999 = @define_symbol_expr
;
Loading
Oops, something went wrong.