Skip to content

Commit 908a3e3

Browse files
committed
C#: Make synthetic ToString calls in binary add expressions.
1 parent f905be4 commit 908a3e3

File tree

5 files changed

+105
-4
lines changed

5 files changed

+105
-4
lines changed

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public static void CreateDeferred(Context cx, ExpressionSyntax node, IExpression
129129
cx.PopulateLater(() => Create(cx, node, parent, child));
130130
}
131131

132-
private static bool ContainsPattern(SyntaxNode node) =>
132+
protected static bool ContainsPattern(SyntaxNode node) =>
133133
node is PatternSyntax || node is VariableDesignationSyntax || node.ChildNodes().Any(ContainsPattern);
134134

135135
/// <summary>

csharp/extractor/Semmle.Extraction.CSharp/Entities/ExpressionNodeInfo.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,13 @@ public Location Location
129129

130130
public ExprKind Kind { get; set; } = ExprKind.UNKNOWN;
131131

132-
public bool IsCompilerGenerated { get; set; }
132+
public bool IsCompilerGenerated { get; init; }
133+
134+
/// <summary>
135+
/// Whether the expression should have a compiler generated `ToString` call added,
136+
/// if there is no suitable implicit cast.
137+
/// </summary>
138+
public bool ImplicitToString { get; private set; }
133139

134140
public ExpressionNodeInfo SetParent(IExpressionParentEntity parent, int child)
135141
{
@@ -157,6 +163,12 @@ public ExpressionNodeInfo SetNode(ExpressionSyntax node)
157163
return this;
158164
}
159165

166+
public ExpressionNodeInfo SetImplicitToString(bool value)
167+
{
168+
ImplicitToString = value;
169+
return this;
170+
}
171+
160172
private SymbolInfo cachedSymbolInfo;
161173

162174
public SymbolInfo SymbolInfo

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,35 @@ private Binary(ExpressionNodeInfo info)
1414

1515
public static Expression Create(ExpressionNodeInfo info) => new Binary(info).TryPopulate();
1616

17+
private Expression CreateChild(Context cx, ExpressionSyntax node, int child)
18+
{
19+
// If this is a "+" expression we might need to wrap the child expressions
20+
// in ToString calls
21+
return Kind == ExprKind.ADD
22+
? ImplicitToString.Create(cx, node, this, child)
23+
: Create(cx, node, this, child);
24+
}
25+
26+
/// <summary>
27+
/// Creates an expression from a syntax node.
28+
/// Inserts type conversion as required.
29+
/// Population is deferred to avoid overflowing the stack.
30+
/// </summary>
31+
private void CreateDeferred(Context cx, ExpressionSyntax node, int child)
32+
{
33+
if (ContainsPattern(node))
34+
// Expressions with patterns should be created right away, as they may introduce
35+
// local variables referenced in `LocalVariable::GetAlreadyCreated()`
36+
CreateChild(cx, node, child);
37+
else
38+
cx.PopulateLater(() => CreateChild(cx, node, child));
39+
}
40+
1741
protected override void PopulateExpression(TextWriter trapFile)
1842
{
1943
OperatorCall(trapFile, Syntax);
20-
CreateDeferred(Context, Syntax.Left, this, 0);
21-
CreateDeferred(Context, Syntax.Right, this, 1);
44+
CreateDeferred(Context, Syntax.Left, 0);
45+
CreateDeferred(Context, Syntax.Right, 1);
2246
}
2347

2448
private static ExprKind GetKind(Context cx, BinaryExpressionSyntax node)

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ convertedType.Symbol is IPointerTypeSymbol &&
156156
return new ImplicitCast(info);
157157
}
158158

159+
if (info.ImplicitToString)
160+
{
161+
// x -> x.ToString() in "abc" + x
162+
return ImplicitToString.Wrap(info);
163+
}
164+
159165
// Default: Just create the expression without a conversion.
160166
return Factory.Create(info);
161167
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System.Linq;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using Semmle.Extraction.CSharp.Util;
5+
using Semmle.Extraction.Kinds;
6+
7+
8+
namespace Semmle.Extraction.CSharp.Entities.Expressions
9+
{
10+
internal sealed class ImplicitToString : Expression
11+
{
12+
/// <summary>
13+
/// Gets the `ToString` method for the given type.
14+
/// </summary>
15+
private static IMethodSymbol? GetToStringMethod(ITypeSymbol? type)
16+
{
17+
return type?
18+
.GetMembers()
19+
.OfType<IMethodSymbol>()
20+
.Where(method =>
21+
method.GetName() == "ToString" &&
22+
method.Parameters.Length == 0
23+
)
24+
.FirstOrDefault();
25+
}
26+
27+
private ImplicitToString(ExpressionNodeInfo info, IMethodSymbol toString) : base(new ExpressionInfo(info.Context, AnnotatedTypeSymbol.CreateNotAnnotated(toString.ReturnType), info.Location, ExprKind.METHOD_INVOCATION, info.Parent, info.Child, isCompilerGenerated: true, info.ExprValue))
28+
{
29+
Factory.Create(info.SetParent(this, -1));
30+
31+
var target = Method.Create(Context, toString);
32+
Context.TrapWriter.Writer.expr_call(this, target);
33+
}
34+
35+
private static bool IsStringType(AnnotatedTypeSymbol? type) =>
36+
type.HasValue && type.Value.Symbol?.SpecialType == SpecialType.System_String;
37+
38+
/// <summary>
39+
/// Creates a new expression, adding a compiler generated `ToString` call if required.
40+
/// </summary>
41+
public static Expression Create(Context cx, ExpressionSyntax node, Expression parent, int child)
42+
{
43+
var info = new ExpressionNodeInfo(cx, node, parent, child);
44+
return CreateFromNode(info.SetImplicitToString(IsStringType(parent.Type) && !IsStringType(info.Type)));
45+
}
46+
47+
/// <summary>
48+
/// Wraps the resulting expression in a `ToString` call, if a suitable `ToString` method is available.
49+
/// </summary>
50+
public static Expression Wrap(ExpressionNodeInfo info)
51+
{
52+
if (GetToStringMethod(info.Type?.Symbol) is IMethodSymbol toString)
53+
{
54+
return new ImplicitToString(info, toString);
55+
}
56+
return Factory.Create(info);
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)