diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitToString.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitToString.cs index f424e98a7a51..32c00f8a729e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitToString.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitToString.cs @@ -14,7 +14,12 @@ internal sealed class ImplicitToString : Expression /// private static IMethodSymbol? GetToStringMethod(ITypeSymbol? type) { - return type? + if (type is null) + { + return null; + } + + var toString = type .GetMembers() .OfType() .Where(method => @@ -22,6 +27,8 @@ internal sealed class ImplicitToString : Expression method.Parameters.Length == 0 ) .FirstOrDefault(); + + return toString ?? GetToStringMethod(type.BaseType); } 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)) diff --git a/csharp/ql/test/library-tests/implicittostring/implicitToString.cs b/csharp/ql/test/library-tests/implicittostring/implicitToString.cs index d75044246f8e..8d74d23791ad 100644 --- a/csharp/ql/test/library-tests/implicittostring/implicitToString.cs +++ b/csharp/ql/test/library-tests/implicittostring/implicitToString.cs @@ -10,6 +10,10 @@ public override string ToString() } } + public class Container2 : Container { } + + public class Container3 { } + public class FormattableContainer : IFormattable { public string ToString(string format, IFormatProvider formatProvider) @@ -40,5 +44,11 @@ public void M() y = "Hello" + formattableContainer; // Implicit call to ToString(). y = $"Hello {formattableContainer}"; // Implicit call to ToString(string, IFormatProvider). We don't handle this. y = $"Hello {formattableContainer:D}"; // Implicit call to ToString(string, IFormatProvider). We don't handle this. + + var container2 = new Container2(); + y = "Hello" + container2; // Implicit Container.ToString call. + + var container3 = new Container3(); + y = "Hello" + container3; // Implicit Object.ToString call. } } diff --git a/csharp/ql/test/library-tests/implicittostring/implicitToString.expected b/csharp/ql/test/library-tests/implicittostring/implicitToString.expected index 4878b12a4fdb..14efebf23207 100644 --- a/csharp/ql/test/library-tests/implicittostring/implicitToString.expected +++ b/csharp/ql/test/library-tests/implicittostring/implicitToString.expected @@ -1,4 +1,6 @@ -| implicitToString.cs:31:27:31:35 | call to method ToString | -| implicitToString.cs:33:22:33:30 | call to method ToString | -| implicitToString.cs:35:22:35:30 | call to method ToString | -| implicitToString.cs:40:23:40:42 | call to method ToString | +| implicitToString.cs:35:27:35:35 | call to method ToString | Container | +| implicitToString.cs:37:22:37:30 | call to method ToString | Container | +| implicitToString.cs:39:22:39:30 | call to method ToString | Container | +| implicitToString.cs:44:23:44:42 | call to method ToString | FormattableContainer | +| implicitToString.cs:49:23:49:32 | call to method ToString | Container | +| implicitToString.cs:52:23:52:32 | call to method ToString | Object | diff --git a/csharp/ql/test/library-tests/implicittostring/implicitToString.ql b/csharp/ql/test/library-tests/implicittostring/implicitToString.ql index 4eb518d84c39..fee24b480c9c 100644 --- a/csharp/ql/test/library-tests/implicittostring/implicitToString.ql +++ b/csharp/ql/test/library-tests/implicittostring/implicitToString.ql @@ -2,4 +2,4 @@ import csharp from MethodCall c where c.isImplicit() -select c +select c, c.getTarget().getDeclaringType().toString() diff --git a/csharp/ql/test/query-tests/Nullness/Implications.expected b/csharp/ql/test/query-tests/Nullness/Implications.expected index 45ffbc1d92b0..dbb6ab23a9aa 100644 --- a/csharp/ql/test/query-tests/Nullness/Implications.expected +++ b/csharp/ql/test/query-tests/Nullness/Implications.expected @@ -908,8 +908,8 @@ | D.cs:253:13:253:14 | access to local variable o2 | null | D.cs:249:18:249:38 | ... ? ... : ... | null | | D.cs:266:13:266:27 | ... is ... | true | D.cs:266:13:266:17 | access to local variable other | non-null | | D.cs:310:21:310:26 | ... + ... | non-null | D.cs:310:21:310:22 | "" | non-null | -| D.cs:310:21:310:26 | ... + ... | non-null | D.cs:310:26:310:26 | access to parameter a | non-null | -| D.cs:310:21:310:26 | ... + ... | null | D.cs:310:26:310:26 | access to parameter a | null | +| D.cs:310:21:310:26 | ... + ... | non-null | D.cs:310:26:310:26 | call to method ToString | non-null | +| D.cs:310:21:310:26 | ... + ... | null | D.cs:310:26:310:26 | call to method ToString | null | | D.cs:312:17:312:23 | !... | false | D.cs:312:18:312:23 | access to local variable s_null | true | | D.cs:312:17:312:23 | !... | true | D.cs:312:18:312:23 | access to local variable s_null | false | | D.cs:318:16:318:62 | ... && ... | true | D.cs:318:16:318:36 | ... == ... | true |