Skip to content

Commit 2863755

Browse files
committed
C#: Introduce synthetic ToString calls where appropriate.
1 parent b0062fc commit 2863755

File tree

4 files changed

+49
-28
lines changed

4 files changed

+49
-28
lines changed

csharp/.vscode/launch.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"program": "${workspaceFolder}/extractor/Semmle.Extraction.CSharp.Standalone/bin/Debug/net9.0/Semmle.Extraction.CSharp.Standalone.dll",
1010
"args": [],
1111
// Set the path to the folder that should be extracted:
12-
"cwd": "${workspaceFolder}/ql/test/library-tests/standalone/standalonemode",
12+
"cwd": "${workspaceFolder}/ql/test/library-tests/testdataflow",
1313
"env": {
1414
"CODEQL_THREADS": "1",
1515
"CODEQL_EXTRACTOR_CSHARP_OPTION_LOGGING_VERBOSITY": "progress+++",
@@ -68,9 +68,9 @@
6868
"preLaunchTask": "dotnet: build",
6969
"program": "${workspaceFolder}/extractor/Semmle.Extraction.CSharp.Driver/bin/Debug/net9.0/Semmle.Extraction.CSharp.Driver.dll",
7070
// Set the path to the folder that should be extracted:
71-
"cwd": "${workspaceFolder}/ql/test/library-tests/dataflow/local",
71+
"cwd": "${workspaceFolder}/ql/test/library-tests/testdataflow",
7272
"args": [
73-
"LocalDataFlow.cs"
73+
"stringinterpolated.cs"
7474
],
7575
"env": {},
7676
"stopAtEntry": true,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public static Expression Create(Context cx, ExpressionSyntax node, IExpressionPa
108108
return CreateFromNode(info);
109109
}
110110

111-
public static Expression CreateFromNode(ExpressionNodeInfo info) => Expressions.ImplicitCast.Create(info);
111+
public static Expression CreateFromNode(ExpressionNodeInfo info) => Expressions.Implicit.Create(info);
112112

113113
/// <summary>
114114
/// Creates an expression from a syntax node.
@@ -208,7 +208,7 @@ private static bool ContainsPattern(SyntaxNode node) =>
208208

209209
if (type.SpecialType is SpecialType.None)
210210
{
211-
return ImplicitCast.CreateGeneratedConversion(cx, parent, childIndex, type, defaultValue, location);
211+
return Implicit.CreateGeneratedConversion(cx, parent, childIndex, type, defaultValue, location);
212212
}
213213

214214
if (type.SpecialType is SpecialType.System_DateTime)
@@ -220,7 +220,7 @@ private static bool ContainsPattern(SyntaxNode node) =>
220220
type.SpecialType is SpecialType.System_IntPtr ||
221221
type.SpecialType is SpecialType.System_UIntPtr)
222222
{
223-
return ImplicitCast.CreateGenerated(cx, parent, childIndex, type, defaultValue, location);
223+
return Implicit.CreateGenerated(cx, parent, childIndex, type, defaultValue, location);
224224
}
225225

226226
// const literal:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private static ExprKind AccessKind(Context cx, ISymbol symbol)
4545
private Access(ExpressionNodeInfo info, ISymbol symbol, bool implicitThis, IEntity target)
4646
: base(info.SetKind(AccessKind(info.Context, symbol)))
4747
{
48-
if (!(target is null))
48+
if (target is not null)
4949
{
5050
Context.TrapWriter.Writer.expr_access(this, target);
5151
}

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitCast.cs renamed to csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Implicit.cs

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,30 @@
55

66
namespace Semmle.Extraction.CSharp.Entities.Expressions
77
{
8-
internal sealed class ImplicitCast : Expression
8+
internal sealed class Implicit : Expression
99
{
10-
public Expression Expr
11-
{
12-
get;
13-
private set;
14-
}
15-
16-
private ImplicitCast(ExpressionNodeInfo info)
10+
private Implicit(ExpressionNodeInfo info)
1711
: base(new ExpressionInfo(info.Context, info.ConvertedType, info.Location, ExprKind.CAST, info.Parent, info.Child, isCompilerGenerated: true, info.ExprValue))
1812
{
19-
Expr = Factory.Create(new ExpressionNodeInfo(Context, info.Node, this, 0));
13+
Factory.Create(new ExpressionNodeInfo(Context, info.Node, this, 0));
2014
}
2115

22-
private ImplicitCast(ExpressionNodeInfo info, IMethodSymbol method)
23-
: base(new ExpressionInfo(info.Context, info.ConvertedType, info.Location, ExprKind.OPERATOR_INVOCATION, info.Parent, info.Child, isCompilerGenerated: true, info.ExprValue))
16+
private Implicit(ExpressionNodeInfo info, IMethodSymbol method, ExprKind kind, int child)
17+
: base(new ExpressionInfo(info.Context, info.ConvertedType, info.Location, kind, info.Parent, info.Child, isCompilerGenerated: true, info.ExprValue))
2418
{
25-
Expr = Factory.Create(info.SetParent(this, 0));
19+
Factory.Create(info.SetParent(this, child));
2620

27-
AddOperatorCall(method);
21+
AddCall(method);
2822
}
2923

30-
private ImplicitCast(ExpressionInfo info, IMethodSymbol method, object value) : base(info)
24+
private Implicit(ExpressionInfo info, IMethodSymbol method, object value) : base(info)
3125
{
32-
Expr = Literal.CreateGenerated(Context, this, 0, method.Parameters[0].Type, value, info.Location);
26+
Literal.CreateGenerated(Context, this, 0, method.Parameters[0].Type, value, info.Location);
3327

34-
AddOperatorCall(method);
28+
AddCall(method);
3529
}
3630

37-
private void AddOperatorCall(IMethodSymbol method)
31+
private void AddCall(IMethodSymbol method)
3832
{
3933
var target = Method.Create(Context, method);
4034
Context.TrapWriter.Writer.expr_call(this, target);
@@ -72,7 +66,7 @@ ExpressionInfo create(ExprKind kind, string? v) =>
7266
if (method is not null)
7367
{
7468
var info = create(ExprKind.OPERATOR_INVOCATION, null);
75-
return new ImplicitCast(info, method, value);
69+
return new Implicit(info, method, value);
7670
}
7771
else
7872
{
@@ -81,6 +75,21 @@ ExpressionInfo create(ExprKind kind, string? v) =>
8175
}
8276
}
8377

78+
/// <summary>
79+
/// Gets the `ToString` method for the given type.
80+
/// </summary>
81+
private static IMethodSymbol? GetToStringMethod(ITypeSymbol type)
82+
{
83+
return type
84+
.GetMembers()
85+
.OfType<IMethodSymbol>()
86+
.Where(method =>
87+
method.GetName() == "ToString" &&
88+
method.Parameters.Length == 0
89+
)
90+
.FirstOrDefault();
91+
}
92+
8493
/// <summary>
8594
/// Creates a new generated cast expression.
8695
/// </summary>
@@ -130,7 +139,7 @@ info.Parent is ExplicitObjectCreation objectCreation &&
130139
}
131140

132141
if (resolvedType.Symbol is not null)
133-
return new ImplicitCast(info, conversion.MethodSymbol);
142+
return new Implicit(info, conversion.MethodSymbol, ExprKind.OPERATOR_INVOCATION, 0);
134143
}
135144

136145
var implicitUpcast = conversion.IsImplicit &&
@@ -144,7 +153,19 @@ resolvedType.Symbol is null ||
144153

145154
if (!conversion.IsIdentity && !implicitUpcast)
146155
{
147-
return new ImplicitCast(info);
156+
return new Implicit(info);
157+
}
158+
159+
// Implicit call to ToString.
160+
if (!conversion.IsIdentity &&
161+
resolvedType.Symbol is not null &&
162+
implicitUpcast && // Maybe write the condition explicitly.
163+
info.Parent is Expression par && // TODO: Only choose a specific set of parents (maybe BinaryExpression and StringInterpolation expressions?)
164+
par.Type.HasValue && par.Type.Value.Symbol?.SpecialType == SpecialType.System_String)
165+
{
166+
return GetToStringMethod(resolvedType.Symbol) is IMethodSymbol toString
167+
? new Implicit(info, toString, ExprKind.METHOD_INVOCATION, -1)
168+
: Factory.Create(info);
148169
}
149170

150171
if (conversion.IsIdentity && conversion.IsImplicit &&
@@ -153,7 +174,7 @@ convertedType.Symbol is IPointerTypeSymbol &&
153174
{
154175
// int[] -> int*
155176
// string -> char*
156-
return new ImplicitCast(info);
177+
return new Implicit(info);
157178
}
158179

159180
// Default: Just create the expression without a conversion.

0 commit comments

Comments
 (0)