From 2863755920e7a0e5558db2ce4b5568dc46cf9324 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 7 Jan 2025 09:50:33 +0100 Subject: [PATCH] C#: Introduce synthetic ToString calls where appropriate. --- csharp/.vscode/launch.json | 6 +- .../Entities/Expression.cs | 6 +- .../Entities/Expressions/Access.cs | 2 +- .../{ImplicitCast.cs => Implicit.cs} | 63 ++++++++++++------- 4 files changed, 49 insertions(+), 28 deletions(-) rename csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/{ImplicitCast.cs => Implicit.cs} (70%) diff --git a/csharp/.vscode/launch.json b/csharp/.vscode/launch.json index 75a43a6f9aa3..6b68bffeb14b 100644 --- a/csharp/.vscode/launch.json +++ b/csharp/.vscode/launch.json @@ -9,7 +9,7 @@ "program": "${workspaceFolder}/extractor/Semmle.Extraction.CSharp.Standalone/bin/Debug/net9.0/Semmle.Extraction.CSharp.Standalone.dll", "args": [], // Set the path to the folder that should be extracted: - "cwd": "${workspaceFolder}/ql/test/library-tests/standalone/standalonemode", + "cwd": "${workspaceFolder}/ql/test/library-tests/testdataflow", "env": { "CODEQL_THREADS": "1", "CODEQL_EXTRACTOR_CSHARP_OPTION_LOGGING_VERBOSITY": "progress+++", @@ -68,9 +68,9 @@ "preLaunchTask": "dotnet: build", "program": "${workspaceFolder}/extractor/Semmle.Extraction.CSharp.Driver/bin/Debug/net9.0/Semmle.Extraction.CSharp.Driver.dll", // Set the path to the folder that should be extracted: - "cwd": "${workspaceFolder}/ql/test/library-tests/dataflow/local", + "cwd": "${workspaceFolder}/ql/test/library-tests/testdataflow", "args": [ - "LocalDataFlow.cs" + "stringinterpolated.cs" ], "env": {}, "stopAtEntry": true, diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs index 9241528eb752..ceef3b624ed8 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs @@ -108,7 +108,7 @@ public static Expression Create(Context cx, ExpressionSyntax node, IExpressionPa return CreateFromNode(info); } - public static Expression CreateFromNode(ExpressionNodeInfo info) => Expressions.ImplicitCast.Create(info); + public static Expression CreateFromNode(ExpressionNodeInfo info) => Expressions.Implicit.Create(info); /// /// Creates an expression from a syntax node. @@ -208,7 +208,7 @@ private static bool ContainsPattern(SyntaxNode node) => if (type.SpecialType is SpecialType.None) { - return ImplicitCast.CreateGeneratedConversion(cx, parent, childIndex, type, defaultValue, location); + return Implicit.CreateGeneratedConversion(cx, parent, childIndex, type, defaultValue, location); } if (type.SpecialType is SpecialType.System_DateTime) @@ -220,7 +220,7 @@ private static bool ContainsPattern(SyntaxNode node) => type.SpecialType is SpecialType.System_IntPtr || type.SpecialType is SpecialType.System_UIntPtr) { - return ImplicitCast.CreateGenerated(cx, parent, childIndex, type, defaultValue, location); + return Implicit.CreateGenerated(cx, parent, childIndex, type, defaultValue, location); } // const literal: diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Access.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Access.cs index 8ef72f8085cd..1288fd5e9510 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Access.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Access.cs @@ -45,7 +45,7 @@ private static ExprKind AccessKind(Context cx, ISymbol symbol) private Access(ExpressionNodeInfo info, ISymbol symbol, bool implicitThis, IEntity target) : base(info.SetKind(AccessKind(info.Context, symbol))) { - if (!(target is null)) + if (target is not null) { Context.TrapWriter.Writer.expr_access(this, target); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitCast.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Implicit.cs similarity index 70% rename from csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitCast.cs rename to csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Implicit.cs index b0508bf83b7e..d69d62f0709f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitCast.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Implicit.cs @@ -5,36 +5,30 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions { - internal sealed class ImplicitCast : Expression + internal sealed class Implicit : Expression { - public Expression Expr - { - get; - private set; - } - - private ImplicitCast(ExpressionNodeInfo info) + private Implicit(ExpressionNodeInfo info) : base(new ExpressionInfo(info.Context, info.ConvertedType, info.Location, ExprKind.CAST, info.Parent, info.Child, isCompilerGenerated: true, info.ExprValue)) { - Expr = Factory.Create(new ExpressionNodeInfo(Context, info.Node, this, 0)); + Factory.Create(new ExpressionNodeInfo(Context, info.Node, this, 0)); } - private ImplicitCast(ExpressionNodeInfo info, IMethodSymbol method) - : base(new ExpressionInfo(info.Context, info.ConvertedType, info.Location, ExprKind.OPERATOR_INVOCATION, info.Parent, info.Child, isCompilerGenerated: true, info.ExprValue)) + private Implicit(ExpressionNodeInfo info, IMethodSymbol method, ExprKind kind, int child) + : base(new ExpressionInfo(info.Context, info.ConvertedType, info.Location, kind, info.Parent, info.Child, isCompilerGenerated: true, info.ExprValue)) { - Expr = Factory.Create(info.SetParent(this, 0)); + Factory.Create(info.SetParent(this, child)); - AddOperatorCall(method); + AddCall(method); } - private ImplicitCast(ExpressionInfo info, IMethodSymbol method, object value) : base(info) + private Implicit(ExpressionInfo info, IMethodSymbol method, object value) : base(info) { - Expr = Literal.CreateGenerated(Context, this, 0, method.Parameters[0].Type, value, info.Location); + Literal.CreateGenerated(Context, this, 0, method.Parameters[0].Type, value, info.Location); - AddOperatorCall(method); + AddCall(method); } - private void AddOperatorCall(IMethodSymbol method) + private void AddCall(IMethodSymbol method) { var target = Method.Create(Context, method); Context.TrapWriter.Writer.expr_call(this, target); @@ -72,7 +66,7 @@ ExpressionInfo create(ExprKind kind, string? v) => if (method is not null) { var info = create(ExprKind.OPERATOR_INVOCATION, null); - return new ImplicitCast(info, method, value); + return new Implicit(info, method, value); } else { @@ -81,6 +75,21 @@ ExpressionInfo create(ExprKind kind, string? v) => } } + /// + /// Gets the `ToString` method for the given type. + /// + private static IMethodSymbol? GetToStringMethod(ITypeSymbol type) + { + return type + .GetMembers() + .OfType() + .Where(method => + method.GetName() == "ToString" && + method.Parameters.Length == 0 + ) + .FirstOrDefault(); + } + /// /// Creates a new generated cast expression. /// @@ -130,7 +139,7 @@ info.Parent is ExplicitObjectCreation objectCreation && } if (resolvedType.Symbol is not null) - return new ImplicitCast(info, conversion.MethodSymbol); + return new Implicit(info, conversion.MethodSymbol, ExprKind.OPERATOR_INVOCATION, 0); } var implicitUpcast = conversion.IsImplicit && @@ -144,7 +153,19 @@ resolvedType.Symbol is null || if (!conversion.IsIdentity && !implicitUpcast) { - return new ImplicitCast(info); + return new Implicit(info); + } + + // Implicit call to ToString. + if (!conversion.IsIdentity && + resolvedType.Symbol is not null && + implicitUpcast && // Maybe write the condition explicitly. + info.Parent is Expression par && // TODO: Only choose a specific set of parents (maybe BinaryExpression and StringInterpolation expressions?) + par.Type.HasValue && par.Type.Value.Symbol?.SpecialType == SpecialType.System_String) + { + return GetToStringMethod(resolvedType.Symbol) is IMethodSymbol toString + ? new Implicit(info, toString, ExprKind.METHOD_INVOCATION, -1) + : Factory.Create(info); } if (conversion.IsIdentity && conversion.IsImplicit && @@ -153,7 +174,7 @@ convertedType.Symbol is IPointerTypeSymbol && { // int[] -> int* // string -> char* - return new ImplicitCast(info); + return new Implicit(info); } // Default: Just create the expression without a conversion.