diff --git a/csharp/extractor/Semmle.Extraction.CSharp/CodeAnalysisExtensions/SymbolExtensions.cs b/csharp/extractor/Semmle.Extraction.CSharp/CodeAnalysisExtensions/SymbolExtensions.cs index bb61efe332c8..cb1f36f8a2de 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/CodeAnalysisExtensions/SymbolExtensions.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/CodeAnalysisExtensions/SymbolExtensions.cs @@ -532,6 +532,12 @@ attribute.AttributeClass is INamedTypeSymbol nt && return isInline; } + /// + /// Returns true if this type implements `System.IFormattable`. + /// + public static bool ImplementsIFormattable(this ITypeSymbol type) => + type.AllInterfaces.Any(i => i.Name == "IFormattable" && i.ContainingNamespace.ToString() == "System"); + /// /// Holds if this type is of the form System.ReadOnlySpan<byte>. /// diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs index 9241528eb752..a5cb6e316f41 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs @@ -129,7 +129,7 @@ public static void CreateDeferred(Context cx, ExpressionSyntax node, IExpression cx.PopulateLater(() => Create(cx, node, parent, child)); } - private static bool ContainsPattern(SyntaxNode node) => + protected static bool ContainsPattern(SyntaxNode node) => node is PatternSyntax || node is VariableDesignationSyntax || node.ChildNodes().Any(ContainsPattern); /// diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/ExpressionNodeInfo.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/ExpressionNodeInfo.cs index 924382a55507..5a2cad8a520a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/ExpressionNodeInfo.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/ExpressionNodeInfo.cs @@ -129,7 +129,13 @@ public Location Location public ExprKind Kind { get; set; } = ExprKind.UNKNOWN; - public bool IsCompilerGenerated { get; set; } + public bool IsCompilerGenerated { get; init; } + + /// + /// Whether the expression should have a compiler generated `ToString` call added, + /// if there is no suitable implicit cast. + /// + public bool ImplicitToString { get; private set; } public ExpressionNodeInfo SetParent(IExpressionParentEntity parent, int child) { @@ -157,6 +163,12 @@ public ExpressionNodeInfo SetNode(ExpressionSyntax node) return this; } + public ExpressionNodeInfo SetImplicitToString(bool value) + { + ImplicitToString = value; + return this; + } + private SymbolInfo cachedSymbolInfo; public SymbolInfo SymbolInfo diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Binary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Binary.cs index 3cdfb32277b2..eeb1b9ba63b2 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Binary.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Binary.cs @@ -14,11 +14,35 @@ private Binary(ExpressionNodeInfo info) public static Expression Create(ExpressionNodeInfo info) => new Binary(info).TryPopulate(); + 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 + ? ImplicitToString.Create(cx, node, this, child) + : Create(cx, node, this, child); + } + + /// + /// Creates an expression from a syntax node. + /// Inserts type conversion as required. + /// Population is deferred to avoid overflowing the stack. + /// + private void CreateDeferred(Context cx, ExpressionSyntax node, int child) + { + if (ContainsPattern(node)) + // Expressions with patterns should be created right away, as they may introduce + // local variables referenced in `LocalVariable::GetAlreadyCreated()` + CreateChild(cx, node, child); + else + cx.PopulateLater(() => CreateChild(cx, node, child)); + } + protected override void PopulateExpression(TextWriter trapFile) { OperatorCall(trapFile, Syntax); - CreateDeferred(Context, Syntax.Left, this, 0); - CreateDeferred(Context, Syntax.Right, this, 1); + CreateDeferred(Context, Syntax.Left, 0); + CreateDeferred(Context, Syntax.Right, 1); } private static ExprKind GetKind(Context cx, BinaryExpressionSyntax node) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitCast.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitCast.cs index b0508bf83b7e..c1ce5dcbfe99 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitCast.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitCast.cs @@ -156,6 +156,12 @@ convertedType.Symbol is IPointerTypeSymbol && return new ImplicitCast(info); } + if (info.ImplicitToString) + { + // x -> x.ToString() in "abc" + x + return ImplicitToString.Wrap(info); + } + // Default: Just create the expression without a conversion. return Factory.Create(info); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitToString.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitToString.cs new file mode 100644 index 000000000000..f424e98a7a51 --- /dev/null +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ImplicitToString.cs @@ -0,0 +1,59 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Semmle.Extraction.CSharp.Util; +using Semmle.Extraction.Kinds; + + +namespace Semmle.Extraction.CSharp.Entities.Expressions +{ + internal sealed class ImplicitToString : Expression + { + /// + /// 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(); + } + + 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)) + { + Factory.Create(info.SetParent(this, -1)); + + var target = Method.Create(Context, toString); + Context.TrapWriter.Writer.expr_call(this, target); + } + + private static bool IsStringType(AnnotatedTypeSymbol? type) => + type.HasValue && type.Value.Symbol?.SpecialType == SpecialType.System_String; + + /// + /// Creates a new expression, adding a compiler generated `ToString` call if required. + /// + public static Expression Create(Context cx, ExpressionSyntax node, Expression parent, int child) + { + var info = new ExpressionNodeInfo(cx, node, parent, child); + return CreateFromNode(info.SetImplicitToString(IsStringType(parent.Type) && !IsStringType(info.Type))); + } + + /// + /// Wraps the resulting expression in a `ToString` call, if a suitable `ToString` method is available. + /// + public static Expression Wrap(ExpressionNodeInfo info) + { + if (GetToStringMethod(info.Type?.Symbol) is IMethodSymbol toString) + { + return new ImplicitToString(info, toString); + } + return Factory.Create(info); + } + } +} diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/InterpolatedString.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/InterpolatedString.cs index 2dfe4976391d..6d17d1e7f176 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/InterpolatedString.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/InterpolatedString.cs @@ -1,4 +1,5 @@ using System.IO; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Semmle.Extraction.Kinds; @@ -20,7 +21,15 @@ protected override void PopulateExpression(TextWriter trapFile) { case SyntaxKind.Interpolation: var interpolation = (InterpolationSyntax)c; - Create(Context, interpolation.Expression, this, child++); + 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++); + } break; case SyntaxKind.InterpolatedStringText: // Create a string literal diff --git a/csharp/ql/examples/snippets/ternary_conditional.ql b/csharp/ql/examples/snippets/ternary_conditional.ql index 22e26e71ea8f..009c56fb2afe 100644 --- a/csharp/ql/examples/snippets/ternary_conditional.ql +++ b/csharp/ql/examples/snippets/ternary_conditional.ql @@ -11,7 +11,7 @@ import csharp from ConditionalExpr e where - e.getThen().stripImplicitCasts() != e.getElse().stripImplicitCasts() and + e.getThen().stripImplicit() != e.getElse().stripImplicit() and not e.getThen().getType() instanceof NullType and not e.getElse().getType() instanceof NullType select e diff --git a/csharp/ql/lib/change-notes/2025-01-09-implicit-to-string.md b/csharp/ql/lib/change-notes/2025-01-09-implicit-to-string.md new file mode 100644 index 000000000000..2956898841ee --- /dev/null +++ b/csharp/ql/lib/change-notes/2025-01-09-implicit-to-string.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added extractor support for extracting implicit `ToString` calls in binary `+` expressions and string interpolation expressions. diff --git a/csharp/ql/lib/ext/Microsoft.AspNetCore.Http.model.yml b/csharp/ql/lib/ext/Microsoft.AspNetCore.Http.model.yml new file mode 100644 index 000000000000..b4716f100a2e --- /dev/null +++ b/csharp/ql/lib/ext/Microsoft.AspNetCore.Http.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/csharp-all + extensible: summaryModel + data: + - ["Microsoft.AspNetCore.Http", "PathString", True, "ToString", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] diff --git a/csharp/ql/lib/semmle/code/csharp/PrintAst.qll b/csharp/ql/lib/semmle/code/csharp/PrintAst.qll index 281e157975ab..fd4bf1cb86b0 100644 --- a/csharp/ql/lib/semmle/code/csharp/PrintAst.qll +++ b/csharp/ql/lib/semmle/code/csharp/PrintAst.qll @@ -32,7 +32,9 @@ private predicate shouldPrint(Element e, Location l) { } private predicate isImplicitExpression(ControlFlowElement element) { - element.(Expr).isImplicit() and + // Include compiler generated cast expressions and `ToString` calls if + // they wrap actual source expressions. + element.(Expr).stripImplicit().isImplicit() and not element instanceof CastExpr and not element.(OperatorCall).getTarget() instanceof ImplicitConversionOperator and not element instanceof ElementInitializer diff --git a/csharp/ql/lib/semmle/code/csharp/commons/Constants.qll b/csharp/ql/lib/semmle/code/csharp/commons/Constants.qll index 1d6e67aa488d..508ba0e5e87d 100644 --- a/csharp/ql/lib/semmle/code/csharp/commons/Constants.qll +++ b/csharp/ql/lib/semmle/code/csharp/commons/Constants.qll @@ -43,7 +43,7 @@ predicate isConstantComparison(ComparisonOperation co, boolean b) { private module ConstantComparisonOperation { private import semmle.code.csharp.commons.ComparisonTest - private SimpleType convertedType(Expr expr) { result = expr.stripImplicitCasts().getType() } + private SimpleType convertedType(Expr expr) { result = expr.stripImplicit().getType() } private int maxValue(Expr expr) { if convertedType(expr) instanceof IntegralType and exists(expr.getValue()) diff --git a/csharp/ql/lib/semmle/code/csharp/commons/Strings.qll b/csharp/ql/lib/semmle/code/csharp/commons/Strings.qll index 4e007d61737e..908d1c2fb5ab 100644 --- a/csharp/ql/lib/semmle/code/csharp/commons/Strings.qll +++ b/csharp/ql/lib/semmle/code/csharp/commons/Strings.qll @@ -44,11 +44,11 @@ class ImplicitToStringExpr extends Expr { ) or exists(AddExpr add, Expr o | o = add.getAnOperand() | - o.stripImplicitCasts().getType() instanceof StringType and - this = add.getOtherOperand(o) + o.stripImplicit().getType() instanceof StringType and + this = add.getOtherOperand(o).stripImplicit() ) or - this = any(InterpolatedStringExpr ise).getAnInsert() + this = any(InterpolatedStringExpr ise).getAnInsert().stripImplicit() } } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll index f72f2dc59a13..a990455f4307 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll @@ -526,7 +526,11 @@ class Dereference extends G::DereferenceableExpr { not underlyingType instanceof NullableType ) ) else ( - this = any(QualifiableExpr qe | not qe.isConditional()).getQualifier() and + this = + any(QualifiableExpr qe | + not qe.isConditional() and + not qe.(MethodCall).isImplicit() + ).getQualifier() and not this instanceof ThisAccess and not this instanceof BaseAccess and not this instanceof TypeAccess diff --git a/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll b/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll index 080b7d5c7325..7de6c30eb13c 100644 --- a/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll +++ b/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll @@ -857,7 +857,7 @@ private module Internal { private predicate hasDynamicArg(int i, Type argumentType) { exists(Expr argument | argument = this.getArgument(i) and - argument.stripImplicitCasts().getType() instanceof DynamicType and + argument.stripImplicit().getType() instanceof DynamicType and argumentType = getAPossibleType(argument, _) ) } diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll index be4577d760eb..eecbc35900aa 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll @@ -281,6 +281,10 @@ class MethodCall extends Call, QualifiableExpr, LateBindableExpr, @method_invoca result = this.getArgument(i - 1) else result = this.getArgument(i) } + + override Expr stripImplicit() { + if this.isImplicit() then result = this.getQualifier().stripImplicit() else result = this + } } /** diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll index 935162523a16..85676bbd2701 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll @@ -82,10 +82,18 @@ class Expr extends ControlFlowElement, @expr { Expr stripCasts() { result = this } /** + * DEPRECATED: Use `stripImplicit` instead. + * * Gets an expression that is the result of stripping (recursively) all * implicit casts from this expression, if any. */ - Expr stripImplicitCasts() { result = this } + deprecated Expr stripImplicitCasts() { result = this.stripImplicit() } + + /** + * Gets an expression that is the result of stripping (recursively) all + * implicit casts and implicit ToString calls from this expression, if any. + */ + Expr stripImplicit() { result = this } /** * Gets the explicit parameter name used to pass this expression as an @@ -714,8 +722,8 @@ class Cast extends Expr { override Expr stripCasts() { result = this.getExpr().stripCasts() } - override Expr stripImplicitCasts() { - if this.isImplicit() then result = this.getExpr().stripImplicitCasts() else result = this + override Expr stripImplicit() { + if this.isImplicit() then result = this.getExpr().stripImplicit() else result = this } } diff --git a/csharp/ql/src/Bad Practices/VirtualCallInConstructorOrDestructor.ql b/csharp/ql/src/Bad Practices/VirtualCallInConstructorOrDestructor.ql index 24fccf035b40..d754f1a03c59 100644 --- a/csharp/ql/src/Bad Practices/VirtualCallInConstructorOrDestructor.ql +++ b/csharp/ql/src/Bad Practices/VirtualCallInConstructorOrDestructor.ql @@ -34,7 +34,9 @@ predicate overriddenSealed(RefType t, Virtualizable d) { } predicate virtualAccessWithThisQualifier(Expr e, Member d) { - exists(VirtualMethodCall c | c = e and c.getTarget() = d and c.hasThisQualifier()) + exists(VirtualMethodCall c | + c = e and c.getTarget() = d and c.hasThisQualifier() and not c.isImplicit() + ) or exists(VirtualMethodAccess c | c = e and c.getTarget() = d and c.hasThisQualifier()) or diff --git a/csharp/ql/src/Likely Bugs/Dynamic/BadDynamicCall.ql b/csharp/ql/src/Likely Bugs/Dynamic/BadDynamicCall.ql index 6044ebbbb5eb..2efac6773f72 100644 --- a/csharp/ql/src/Likely Bugs/Dynamic/BadDynamicCall.ql +++ b/csharp/ql/src/Likely Bugs/Dynamic/BadDynamicCall.ql @@ -44,11 +44,11 @@ abstract class BadDynamicCall extends DynamicExpr { ultimateSsaDef = ssaDef.getAnUltimateDefinition() | ultimateSsaDef.getADefinition() = - any(AssignableDefinition def | source = def.getSource().stripImplicitCasts()) + any(AssignableDefinition def | source = def.getSource().stripImplicit()) or ultimateSsaDef.getADefinition() = any(AssignableDefinitions::ImplicitParameterDefinition p | - source = p.getParameter().getAnAssignedValue().stripImplicitCasts() + source = p.getParameter().getAnAssignedValue().stripImplicit() ) ) } diff --git a/csharp/ql/src/Likely Bugs/ObjectComparison.ql b/csharp/ql/src/Likely Bugs/ObjectComparison.ql index 53b525b6072c..eec1961fbf50 100644 --- a/csharp/ql/src/Likely Bugs/ObjectComparison.ql +++ b/csharp/ql/src/Likely Bugs/ObjectComparison.ql @@ -28,7 +28,7 @@ class ReferenceEqualityTestOnObject extends EqualityOperation { exists(getObjectOperand(this)) and // Neither operand is 'null'. not this.getAnOperand() instanceof NullLiteral and - not exists(Type t | t = this.getAnOperand().stripImplicitCasts().getType() | + not exists(Type t | t = this.getAnOperand().stripImplicit().getType() | t instanceof NullType or t instanceof ValueType ) and diff --git a/csharp/ql/test/library-tests/dataflow/global/GetAnOutNode.expected b/csharp/ql/test/library-tests/dataflow/global/GetAnOutNode.expected index ca13350d527b..643b3e813503 100644 --- a/csharp/ql/test/library-tests/dataflow/global/GetAnOutNode.expected +++ b/csharp/ql/test/library-tests/dataflow/global/GetAnOutNode.expected @@ -176,6 +176,7 @@ | GlobalDataFlowStringBuilder.cs:36:21:36:34 | call to method ToString | normal | GlobalDataFlowStringBuilder.cs:36:21:36:34 | call to method ToString | | GlobalDataFlowStringBuilder.cs:39:19:39:37 | object creation of type StringBuilder | normal | GlobalDataFlowStringBuilder.cs:39:19:39:37 | object creation of type StringBuilder | | GlobalDataFlowStringBuilder.cs:40:9:40:27 | call to method Append | normal | GlobalDataFlowStringBuilder.cs:40:9:40:27 | call to method Append | +| GlobalDataFlowStringBuilder.cs:40:23:40:24 | call to method ToString | normal | GlobalDataFlowStringBuilder.cs:40:23:40:24 | call to method ToString | | GlobalDataFlowStringBuilder.cs:41:21:41:34 | call to method ToString | normal | GlobalDataFlowStringBuilder.cs:41:21:41:34 | call to method ToString | | GlobalDataFlowStringBuilder.cs:44:9:44:18 | call to method Clear | normal | GlobalDataFlowStringBuilder.cs:44:9:44:18 | call to method Clear | | GlobalDataFlowStringBuilder.cs:45:23:45:35 | call to method ToString | normal | GlobalDataFlowStringBuilder.cs:45:23:45:35 | call to method ToString | diff --git a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected index f90f71d1ea95..6aa705824b79 100644 --- a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected +++ b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected @@ -514,7 +514,7 @@ edges | GlobalDataFlowStringBuilder.cs:24:19:24:26 | (...) ... : AppendInterpolatedStringHandler | GlobalDataFlowStringBuilder.cs:24:9:24:10 | [post] access to parameter sb : StringBuilder | provenance | MaD:16 | | GlobalDataFlowStringBuilder.cs:30:31:30:32 | [post] access to local variable sb : StringBuilder | GlobalDataFlowStringBuilder.cs:31:21:31:22 | access to local variable sb : StringBuilder | provenance | | | GlobalDataFlowStringBuilder.cs:30:31:30:32 | [post] access to local variable sb : StringBuilder | GlobalDataFlowStringBuilder.cs:35:20:35:21 | access to local variable sb : StringBuilder | provenance | | -| GlobalDataFlowStringBuilder.cs:30:31:30:32 | [post] access to local variable sb : StringBuilder | GlobalDataFlowStringBuilder.cs:40:20:40:26 | (...) ... : AppendInterpolatedStringHandler | provenance | | +| GlobalDataFlowStringBuilder.cs:30:31:30:32 | [post] access to local variable sb : StringBuilder | GlobalDataFlowStringBuilder.cs:40:23:40:24 | access to local variable sb : StringBuilder | provenance | | | GlobalDataFlowStringBuilder.cs:30:35:30:48 | "taint source" : String | GlobalDataFlowStringBuilder.cs:17:64:17:64 | s : String | provenance | | | GlobalDataFlowStringBuilder.cs:30:35:30:48 | "taint source" : String | GlobalDataFlowStringBuilder.cs:30:31:30:32 | [post] access to local variable sb : StringBuilder | provenance | MaD:14 | | GlobalDataFlowStringBuilder.cs:31:13:31:17 | access to local variable sink0 : String | GlobalDataFlowStringBuilder.cs:32:15:32:19 | access to local variable sink0 | provenance | | @@ -527,6 +527,8 @@ edges | GlobalDataFlowStringBuilder.cs:36:21:36:34 | call to method ToString : String | GlobalDataFlowStringBuilder.cs:36:13:36:17 | access to local variable sink1 : String | provenance | | | GlobalDataFlowStringBuilder.cs:40:9:40:11 | [post] access to local variable sb2 : StringBuilder | GlobalDataFlowStringBuilder.cs:41:21:41:23 | access to local variable sb2 : StringBuilder | provenance | | | GlobalDataFlowStringBuilder.cs:40:20:40:26 | (...) ... : AppendInterpolatedStringHandler | GlobalDataFlowStringBuilder.cs:40:9:40:11 | [post] access to local variable sb2 : StringBuilder | provenance | MaD:16 | +| GlobalDataFlowStringBuilder.cs:40:23:40:24 | access to local variable sb : StringBuilder | GlobalDataFlowStringBuilder.cs:40:23:40:24 | call to method ToString : String | provenance | MaD:17 | +| GlobalDataFlowStringBuilder.cs:40:23:40:24 | call to method ToString : String | GlobalDataFlowStringBuilder.cs:40:20:40:26 | (...) ... : AppendInterpolatedStringHandler | provenance | | | GlobalDataFlowStringBuilder.cs:41:13:41:17 | access to local variable sink2 : String | GlobalDataFlowStringBuilder.cs:42:15:42:19 | access to local variable sink2 | provenance | | | GlobalDataFlowStringBuilder.cs:41:21:41:23 | access to local variable sb2 : StringBuilder | GlobalDataFlowStringBuilder.cs:41:21:41:34 | call to method ToString : String | provenance | MaD:17 | | GlobalDataFlowStringBuilder.cs:41:21:41:34 | call to method ToString : String | GlobalDataFlowStringBuilder.cs:41:13:41:17 | access to local variable sink2 : String | provenance | | @@ -1046,6 +1048,8 @@ nodes | GlobalDataFlowStringBuilder.cs:37:15:37:19 | access to local variable sink1 | semmle.label | access to local variable sink1 | | GlobalDataFlowStringBuilder.cs:40:9:40:11 | [post] access to local variable sb2 : StringBuilder | semmle.label | [post] access to local variable sb2 : StringBuilder | | GlobalDataFlowStringBuilder.cs:40:20:40:26 | (...) ... : AppendInterpolatedStringHandler | semmle.label | (...) ... : AppendInterpolatedStringHandler | +| GlobalDataFlowStringBuilder.cs:40:23:40:24 | access to local variable sb : StringBuilder | semmle.label | access to local variable sb : StringBuilder | +| GlobalDataFlowStringBuilder.cs:40:23:40:24 | call to method ToString : String | semmle.label | call to method ToString : String | | GlobalDataFlowStringBuilder.cs:41:13:41:17 | access to local variable sink2 : String | semmle.label | access to local variable sink2 : String | | GlobalDataFlowStringBuilder.cs:41:21:41:23 | access to local variable sb2 : StringBuilder | semmle.label | access to local variable sb2 : StringBuilder | | GlobalDataFlowStringBuilder.cs:41:21:41:34 | call to method ToString : String | semmle.label | call to method ToString : String | diff --git a/csharp/ql/test/library-tests/dataflow/implicittostring/PrintAst.expected b/csharp/ql/test/library-tests/dataflow/implicittostring/PrintAst.expected new file mode 100644 index 000000000000..bf2a515a8895 --- /dev/null +++ b/csharp/ql/test/library-tests/dataflow/implicittostring/PrintAst.expected @@ -0,0 +1,95 @@ +implicitToString.cs: +# 3| [Class] TestClass +# 5| 5: [Class] MyClass +# 5| 4: [InstanceConstructor,PrimaryConstructor] MyClass +# 7| 5: [Method] ToString +# 7| -1: [TypeMention] string +# 8| 4: [BlockStmt] {...} +# 9| 0: [ReturnStmt] return ...; +# 9| 0: [StringLiteralUtf16] "tainted" +# 13| 6: [Method] Sink +# 13| -1: [TypeMention] Void +#-----| 2: (Parameters) +# 13| 0: [Parameter] o +# 13| -1: [TypeMention] object +# 13| 4: [BlockStmt] {...} +# 15| 7: [Method] M1 +# 15| -1: [TypeMention] Void +# 16| 4: [BlockStmt] {...} +# 17| 0: [LocalVariableDeclStmt] ... ...; +# 17| 0: [LocalVariableDeclAndInitExpr] MyClass x1 = ... +# 17| -1: [TypeMention] MyClass +# 17| 0: [LocalVariableAccess] access to local variable x1 +# 17| 1: [ObjectCreation] object creation of type MyClass +# 17| 0: [TypeMention] MyClass +# 18| 1: [LocalVariableDeclStmt] ... ...; +# 18| 0: [LocalVariableDeclAndInitExpr] String x2 = ... +# 18| -1: [TypeMention] string +# 18| 0: [LocalVariableAccess] access to local variable x2 +# 18| 1: [AddExpr] ... + ... +# 18| 0: [StringLiteralUtf16] "Hello" +# 18| 1: [MethodCall] call to method ToString +# 18| -1: [LocalVariableAccess] access to local variable x1 +# 19| 2: [ExprStmt] ...; +# 19| 0: [MethodCall] call to method Sink +# 19| 0: [LocalVariableAccess] access to local variable x2 +# 22| 8: [Method] M2 +# 22| -1: [TypeMention] Void +# 23| 4: [BlockStmt] {...} +# 24| 0: [LocalVariableDeclStmt] ... ...; +# 24| 0: [LocalVariableDeclAndInitExpr] MyClass x1 = ... +# 24| -1: [TypeMention] MyClass +# 24| 0: [LocalVariableAccess] access to local variable x1 +# 24| 1: [ObjectCreation] object creation of type MyClass +# 24| 0: [TypeMention] MyClass +# 25| 1: [LocalVariableDeclStmt] ... ...; +# 25| 0: [LocalVariableDeclAndInitExpr] String x2 = ... +# 25| -1: [TypeMention] string +# 25| 0: [LocalVariableAccess] access to local variable x2 +# 25| 1: [AddExpr] ... + ... +# 25| 0: [StringLiteralUtf16] "Hello" +# 25| 1: [MethodCall] call to method ToString +# 25| -1: [LocalVariableAccess] access to local variable x1 +# 26| 2: [ExprStmt] ...; +# 26| 0: [MethodCall] call to method Sink +# 26| 0: [LocalVariableAccess] access to local variable x2 +# 29| 9: [Method] M3 +# 29| -1: [TypeMention] Void +# 30| 4: [BlockStmt] {...} +# 31| 0: [LocalVariableDeclStmt] ... ...; +# 31| 0: [LocalVariableDeclAndInitExpr] MyClass x1 = ... +# 31| -1: [TypeMention] MyClass +# 31| 0: [LocalVariableAccess] access to local variable x1 +# 31| 1: [ObjectCreation] object creation of type MyClass +# 31| 0: [TypeMention] MyClass +# 32| 1: [LocalVariableDeclStmt] ... ...; +# 32| 0: [LocalVariableDeclAndInitExpr] String x2 = ... +# 32| -1: [TypeMention] string +# 32| 0: [LocalVariableAccess] access to local variable x2 +# 32| 1: [InterpolatedStringExpr] $"..." +# 32| 0: [StringLiteralUtf16] "Hello " +# 32| 1: [MethodCall] call to method ToString +# 32| -1: [LocalVariableAccess] access to local variable x1 +# 33| 2: [ExprStmt] ...; +# 33| 0: [MethodCall] call to method Sink +# 33| 0: [LocalVariableAccess] access to local variable x2 +# 36| 10: [Method] M4 +# 36| -1: [TypeMention] Void +# 37| 4: [BlockStmt] {...} +# 38| 0: [LocalVariableDeclStmt] ... ...; +# 38| 0: [LocalVariableDeclAndInitExpr] MyClass x1 = ... +# 38| -1: [TypeMention] MyClass +# 38| 0: [LocalVariableAccess] access to local variable x1 +# 38| 1: [ObjectCreation] object creation of type MyClass +# 38| 0: [TypeMention] MyClass +# 39| 1: [LocalVariableDeclStmt] ... ...; +# 39| 0: [LocalVariableDeclAndInitExpr] String x2 = ... +# 39| -1: [TypeMention] string +# 39| 0: [LocalVariableAccess] access to local variable x2 +# 39| 1: [InterpolatedStringExpr] $"..." +# 39| 0: [StringLiteralUtf16] "Hello " +# 39| 1: [MethodCall] call to method ToString +# 39| -1: [LocalVariableAccess] access to local variable x1 +# 40| 2: [ExprStmt] ...; +# 40| 0: [MethodCall] call to method Sink +# 40| 0: [LocalVariableAccess] access to local variable x2 diff --git a/csharp/ql/test/library-tests/dataflow/implicittostring/PrintAst.qlref b/csharp/ql/test/library-tests/dataflow/implicittostring/PrintAst.qlref new file mode 100644 index 000000000000..f867dd01f9f8 --- /dev/null +++ b/csharp/ql/test/library-tests/dataflow/implicittostring/PrintAst.qlref @@ -0,0 +1 @@ +shared/PrintAst.ql \ No newline at end of file diff --git a/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.cs b/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.cs new file mode 100644 index 000000000000..84c8737204d8 --- /dev/null +++ b/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.cs @@ -0,0 +1,43 @@ +using System; + +public class TestClass +{ + public class MyClass() + { + public override string ToString() + { + return "tainted"; + } + } + + public static void Sink(object o) { } + + public void M1() + { + var x1 = new MyClass(); + var x2 = "Hello" + x1.ToString(); + Sink(x2); + } + + public void M2() + { + var x1 = new MyClass(); + var x2 = "Hello" + x1; + Sink(x2); + } + + public void M3() + { + var x1 = new MyClass(); + var x2 = $"Hello {x1.ToString()}"; + Sink(x2); + } + + public void M4() + { + var x1 = new MyClass(); + var x2 = $"Hello {x1}"; + Sink(x2); + } + +} diff --git a/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.expected b/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.expected new file mode 100644 index 000000000000..aa77f131e890 --- /dev/null +++ b/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.expected @@ -0,0 +1,34 @@ +models +edges +| implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:18:28:18:40 | call to method ToString : String | provenance | | +| implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:25:28:25:29 | call to method ToString : String | provenance | | +| implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:32:27:32:39 | call to method ToString : String | provenance | | +| implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:39:27:39:28 | call to method ToString : String | provenance | | +| implicitToString.cs:18:13:18:14 | access to local variable x2 : String | implicitToString.cs:19:14:19:15 | access to local variable x2 | provenance | | +| implicitToString.cs:18:28:18:40 | call to method ToString : String | implicitToString.cs:18:13:18:14 | access to local variable x2 : String | provenance | | +| implicitToString.cs:25:13:25:14 | access to local variable x2 : String | implicitToString.cs:26:14:26:15 | access to local variable x2 | provenance | | +| implicitToString.cs:25:28:25:29 | call to method ToString : String | implicitToString.cs:25:13:25:14 | access to local variable x2 : String | provenance | | +| implicitToString.cs:32:13:32:14 | access to local variable x2 : String | implicitToString.cs:33:14:33:15 | access to local variable x2 | provenance | | +| implicitToString.cs:32:27:32:39 | call to method ToString : String | implicitToString.cs:32:13:32:14 | access to local variable x2 : String | provenance | | +| implicitToString.cs:39:13:39:14 | access to local variable x2 : String | implicitToString.cs:40:14:40:15 | access to local variable x2 | provenance | | +| implicitToString.cs:39:27:39:28 | call to method ToString : String | implicitToString.cs:39:13:39:14 | access to local variable x2 : String | provenance | | +nodes +| implicitToString.cs:9:20:9:28 | "tainted" : String | semmle.label | "tainted" : String | +| implicitToString.cs:18:13:18:14 | access to local variable x2 : String | semmle.label | access to local variable x2 : String | +| implicitToString.cs:18:28:18:40 | call to method ToString : String | semmle.label | call to method ToString : String | +| implicitToString.cs:19:14:19:15 | access to local variable x2 | semmle.label | access to local variable x2 | +| implicitToString.cs:25:13:25:14 | access to local variable x2 : String | semmle.label | access to local variable x2 : String | +| implicitToString.cs:25:28:25:29 | call to method ToString : String | semmle.label | call to method ToString : String | +| implicitToString.cs:26:14:26:15 | access to local variable x2 | semmle.label | access to local variable x2 | +| implicitToString.cs:32:13:32:14 | access to local variable x2 : String | semmle.label | access to local variable x2 : String | +| implicitToString.cs:32:27:32:39 | call to method ToString : String | semmle.label | call to method ToString : String | +| implicitToString.cs:33:14:33:15 | access to local variable x2 | semmle.label | access to local variable x2 | +| implicitToString.cs:39:13:39:14 | access to local variable x2 : String | semmle.label | access to local variable x2 : String | +| implicitToString.cs:39:27:39:28 | call to method ToString : String | semmle.label | call to method ToString : String | +| implicitToString.cs:40:14:40:15 | access to local variable x2 | semmle.label | access to local variable x2 | +subpaths +#select +| implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:19:14:19:15 | access to local variable x2 | $@ | implicitToString.cs:19:14:19:15 | access to local variable x2 | access to local variable x2 | +| implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:26:14:26:15 | access to local variable x2 | $@ | implicitToString.cs:26:14:26:15 | access to local variable x2 | access to local variable x2 | +| implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:33:14:33:15 | access to local variable x2 | $@ | implicitToString.cs:33:14:33:15 | access to local variable x2 | access to local variable x2 | +| implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:9:20:9:28 | "tainted" : String | implicitToString.cs:40:14:40:15 | access to local variable x2 | $@ | implicitToString.cs:40:14:40:15 | access to local variable x2 | access to local variable x2 | diff --git a/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.ql b/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.ql new file mode 100644 index 000000000000..85797414ee5d --- /dev/null +++ b/csharp/ql/test/library-tests/dataflow/implicittostring/implicitToString.ql @@ -0,0 +1,19 @@ +import csharp +import utils.test.ProvenancePathGraph::ShowProvenance + +module TtConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr().(StringLiteral).getValue() = "tainted" } + + predicate isSink(DataFlow::Node sink) { + exists(MethodCall mc | + mc.getTarget().hasUndecoratedName("Sink") and + mc.getAnArgument() = sink.asExpr() + ) + } +} + +module Tt = TaintTracking::Global; + +from Tt::PathNode source, Tt::PathNode sink +where Tt::flowPath(source, sink) +select source, source, sink, "$@", sink, sink.toString() diff --git a/csharp/ql/test/library-tests/dataflow/library/FlowSummaries.expected b/csharp/ql/test/library-tests/dataflow/library/FlowSummaries.expected index 6c5524bfd2d9..50d4b3562337 100644 --- a/csharp/ql/test/library-tests/dataflow/library/FlowSummaries.expected +++ b/csharp/ql/test/library-tests/dataflow/library/FlowSummaries.expected @@ -1258,6 +1258,7 @@ summary | Microsoft.AspNetCore.Http;HttpResponse;OnStarting;(System.Func);Argument[0];Argument[0].Parameter[delegate-self];value;hq-generated | | Microsoft.AspNetCore.Http;IEndpointFilter;InvokeAsync;(Microsoft.AspNetCore.Http.EndpointFilterInvocationContext,Microsoft.AspNetCore.Http.EndpointFilterDelegate);Argument[1];Argument[1].Parameter[delegate-self];value;hq-generated | | Microsoft.AspNetCore.Http;IMiddleware;InvokeAsync;(Microsoft.AspNetCore.Http.HttpContext,Microsoft.AspNetCore.Http.RequestDelegate);Argument[1];Argument[1].Parameter[delegate-self];value;hq-generated | +| Microsoft.AspNetCore.Http;PathString;ToString;();Argument[this];ReturnValue;taint;manual | | Microsoft.AspNetCore.Http;ProblemDetailsOptions;set_CustomizeProblemDetails;(System.Action);Argument[0];Argument[0].Parameter[delegate-self];value;hq-generated | | Microsoft.AspNetCore.Http;QueryCollection+Enumerator;get_Current;();Argument[this];ReturnValue;taint;df-generated | | Microsoft.AspNetCore.Http;QueryCollection;GetEnumerator;();Argument[this].Element;ReturnValue.Property[System.Collections.Generic.IEnumerator`1.Current];value;manual | diff --git a/csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.expected b/csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.expected index f6fe3b940435..f99f76e9273b 100644 --- a/csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.expected +++ b/csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.expected @@ -415,6 +415,7 @@ | Microsoft.AspNetCore.Http;HttpResponse;OnStarting;(System.Func);Argument[0];Argument[0].Parameter[delegate-self];value;hq-generated | | Microsoft.AspNetCore.Http;IEndpointFilter;InvokeAsync;(Microsoft.AspNetCore.Http.EndpointFilterInvocationContext,Microsoft.AspNetCore.Http.EndpointFilterDelegate);Argument[1];Argument[1].Parameter[delegate-self];value;hq-generated | | Microsoft.AspNetCore.Http;IMiddleware;InvokeAsync;(Microsoft.AspNetCore.Http.HttpContext,Microsoft.AspNetCore.Http.RequestDelegate);Argument[1];Argument[1].Parameter[delegate-self];value;hq-generated | +| Microsoft.AspNetCore.Http;PathString;ToString;();Argument[this];ReturnValue;taint;manual | | Microsoft.AspNetCore.Http;ProblemDetailsOptions;set_CustomizeProblemDetails;(System.Action);Argument[0];Argument[0].Parameter[delegate-self];value;hq-generated | | Microsoft.AspNetCore.Http;RequestDelegate;BeginInvoke;(Microsoft.AspNetCore.Http.HttpContext,System.AsyncCallback,System.Object);Argument[1];Argument[1].Parameter[delegate-self];value;hq-generated | | Microsoft.AspNetCore.Http;RequestDelegateFactory;Create;(System.Reflection.MethodInfo,System.Func,Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions);Argument[1];Argument[1].Parameter[delegate-self];value;hq-generated | diff --git a/csharp/ql/test/library-tests/implicittostring/implicitToString.cs b/csharp/ql/test/library-tests/implicittostring/implicitToString.cs new file mode 100644 index 000000000000..d75044246f8e --- /dev/null +++ b/csharp/ql/test/library-tests/implicittostring/implicitToString.cs @@ -0,0 +1,44 @@ +using System; + +public class TestImplicitToString +{ + public class Container + { + public override string ToString() + { + return "Container"; + } + } + + public class FormattableContainer : IFormattable + { + public string ToString(string format, IFormatProvider formatProvider) + { + return "Formatted container"; + } + + + public override string ToString() + { + return "Container"; + } + } + + public void M() + { + var container = new Container(); + + var y = "Hello" + container; // Implicit ToString call + y = "Hello" + container.ToString(); + y = $"Hello {container}"; // Implicit ToString() call + y = $"Hello {container.ToString()}"; + y = $"Hello {container:D}"; // Implicit ToString() call. + + var z = "Hello" + y; // No implicit ToString call as `y` is already a string. + + var formattableContainer = new FormattableContainer(); + 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. + } +} diff --git a/csharp/ql/test/library-tests/implicittostring/implicitToString.expected b/csharp/ql/test/library-tests/implicittostring/implicitToString.expected new file mode 100644 index 000000000000..4878b12a4fdb --- /dev/null +++ b/csharp/ql/test/library-tests/implicittostring/implicitToString.expected @@ -0,0 +1,4 @@ +| 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 | diff --git a/csharp/ql/test/library-tests/implicittostring/implicitToString.ql b/csharp/ql/test/library-tests/implicittostring/implicitToString.ql new file mode 100644 index 000000000000..4eb518d84c39 --- /dev/null +++ b/csharp/ql/test/library-tests/implicittostring/implicitToString.ql @@ -0,0 +1,5 @@ +import csharp + +from MethodCall c +where c.isImplicit() +select c diff --git a/csharp/ql/test/library-tests/security/dataflow/flowsources/StoredFlowSources.expected b/csharp/ql/test/library-tests/security/dataflow/flowsources/StoredFlowSources.expected index 28156f12272e..e27ea53adbd1 100644 --- a/csharp/ql/test/library-tests/security/dataflow/flowsources/StoredFlowSources.expected +++ b/csharp/ql/test/library-tests/security/dataflow/flowsources/StoredFlowSources.expected @@ -6,6 +6,7 @@ | data.cs:28:35:28:71 | ... + ... | | data.cs:28:51:28:64 | access to local variable customerReader | | data.cs:28:51:28:71 | access to indexer | +| data.cs:28:51:28:71 | call to method ToString | | data.cs:30:13:30:26 | access to local variable customerReader | | entity.cs:31:29:31:82 | DbRawSqlQuery blogs = ... | | entity.cs:31:37:31:82 | call to method SqlQuery | diff --git a/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.cs b/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.cs index 95ec516ddbe5..767c4e484a14 100644 --- a/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.cs +++ b/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.cs @@ -47,6 +47,7 @@ class C : B f_nonvirtual(); // GOOD f_interface(); // GOOD ((I)this).f_interface(); // GOOD + var x = $"{this}"; // GOOD // Method access Action a; @@ -70,5 +71,10 @@ class C : B e_sealed += f_nonvirtual; // GOOD e_nonvirtual += f_nonvirtual; // GOOD } + + public override string ToString() + { + return "C"; + } } } diff --git a/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.expected b/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.expected index a2f6281ae4ab..8a97e61bf8cf 100644 --- a/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.expected +++ b/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.expected @@ -1,5 +1,5 @@ | VirtualCallInConstructorOrDestructor.cs:45:13:45:23 | call to method f_virtual | Avoid virtual calls in a constructor or destructor. | -| VirtualCallInConstructorOrDestructor.cs:53:17:53:25 | access to method f_virtual | Avoid virtual calls in a constructor or destructor. | -| VirtualCallInConstructorOrDestructor.cs:59:21:59:29 | access to property p_virtual | Avoid virtual calls in a constructor or destructor. | -| VirtualCallInConstructorOrDestructor.cs:64:17:64:23 | access to indexer | Avoid virtual calls in a constructor or destructor. | -| VirtualCallInConstructorOrDestructor.cs:69:13:69:21 | access to event e_virtual | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:54:17:54:25 | access to method f_virtual | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:60:21:60:29 | access to property p_virtual | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:65:17:65:23 | access to indexer | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:70:13:70:21 | access to event e_virtual | Avoid virtual calls in a constructor or destructor. | diff --git a/csharp/ql/test/query-tests/Nullness/Implications.expected b/csharp/ql/test/query-tests/Nullness/Implications.expected index b9c994d08250..45ffbc1d92b0 100644 --- a/csharp/ql/test/query-tests/Nullness/Implications.expected +++ b/csharp/ql/test/query-tests/Nullness/Implications.expected @@ -1451,6 +1451,8 @@ | GuardedString.cs:35:31:35:31 | access to local variable s | null | GuardedString.cs:7:20:7:32 | ... ? ... : ... | null | | GuardedString.cs:37:31:37:31 | access to local variable s | non-null | GuardedString.cs:7:20:7:32 | ... ? ... : ... | non-null | | GuardedString.cs:37:31:37:31 | access to local variable s | null | GuardedString.cs:7:20:7:32 | ... ? ... : ... | null | +| ImplicitToString.cs:8:23:8:23 | access to local variable o | non-null | ImplicitToString.cs:7:20:7:23 | null | non-null | +| ImplicitToString.cs:8:23:8:23 | access to local variable o | null | ImplicitToString.cs:7:20:7:23 | null | null | | NullAlwaysBad.cs:9:17:9:25 | ... != ... | false | NullAlwaysBad.cs:9:17:9:17 | access to parameter s | null | | NullAlwaysBad.cs:9:17:9:25 | ... != ... | true | NullAlwaysBad.cs:9:17:9:17 | access to parameter s | non-null | | NullAlwaysBad.cs:9:17:9:41 | ... \|\| ... | false | NullAlwaysBad.cs:9:17:9:25 | ... != ... | false | diff --git a/csharp/ql/test/query-tests/Nullness/ImplicitToString.cs b/csharp/ql/test/query-tests/Nullness/ImplicitToString.cs new file mode 100644 index 000000000000..304021520c2e --- /dev/null +++ b/csharp/ql/test/query-tests/Nullness/ImplicitToString.cs @@ -0,0 +1,10 @@ +using System; + +class ImplicitToStringTest +{ + void InterpolatedStringImplicitToString() + { + object o = null; + string s = $"{o}"; // GOOD + } +} diff --git a/csharp/ql/test/query-tests/Useless Code/RedundantToStringCall/RedundantToStringCall.cs b/csharp/ql/test/query-tests/Useless Code/RedundantToStringCall/RedundantToStringCall.cs index 2f863966f351..981b36002663 100644 --- a/csharp/ql/test/query-tests/Useless Code/RedundantToStringCall/RedundantToStringCall.cs +++ b/csharp/ql/test/query-tests/Useless Code/RedundantToStringCall/RedundantToStringCall.cs @@ -9,5 +9,8 @@ public void M(object o) Console.WriteLine($"Hello: {o.ToString()}"); // BAD Console.WriteLine($"Hello: {o}"); // GOOD + + Console.WriteLine("Hello: " + o.ToString()); // BAD + Console.WriteLine("Hello: " + o); // GOOD } } diff --git a/csharp/ql/test/query-tests/Useless Code/RedundantToStringCall/RedundantToStringCall.expected b/csharp/ql/test/query-tests/Useless Code/RedundantToStringCall/RedundantToStringCall.expected index 8021c21df2ae..28775378f049 100644 --- a/csharp/ql/test/query-tests/Useless Code/RedundantToStringCall/RedundantToStringCall.expected +++ b/csharp/ql/test/query-tests/Useless Code/RedundantToStringCall/RedundantToStringCall.expected @@ -1,3 +1,4 @@ | RedundantToStringCall.cs:7:27:7:38 | call to method ToString | Redundant call to 'ToString' on a String object. | | RedundantToStringCall.cs:10:37:10:48 | call to method ToString | Redundant call to 'ToString' on a String object. | +| RedundantToStringCall.cs:13:39:13:50 | call to method ToString | Redundant call to 'ToString' on a String object. | | RedundantToStringCallBad.cs:7:45:7:56 | call to method ToString | Redundant call to 'ToString' on a String object. |