Skip to content

Commit

Permalink
C#: Make synthetic ToString calls in binary add expressions.
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelnebel committed Jan 9, 2025
1 parent dad9420 commit e72a53d
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,13 @@ public Location Location

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

public bool IsCompilerGenerated { get; set; }
public bool IsCompilerGenerated { get; init; }

/// <summary>
/// Whether the expression should have a compiler generated `ToString` call added,
/// if there is no suitable implicit cast.
/// </summary>
public bool ImplicitToString { get; private set; }

public ExpressionNodeInfo SetParent(IExpressionParentEntity parent, int child)
{
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
/// Creates an expression from a syntax node.
/// Inserts type conversion as required.
/// Population is deferred to avoid overflowing the stack.
/// </summary>
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,27 @@ public static Expression CreateGenerated(Context cx, IExpressionParentEntity par
return new Expression(info);
}

private static bool IsImplicitUpcast(ExpressionNodeInfo info)
{
var resolvedType = info.ResolvedType;
var convertedType = info.ConvertedType;
var conversion = info.Conversion;

return conversion.IsImplicit &&
convertedType.Symbol is not null &&
!conversion.IsBoxing &&
(
resolvedType.Symbol is null ||
conversion.IsReference ||
convertedType.Symbol.SpecialType == SpecialType.System_Object)
;

}

public static bool IsNontrivialImplicitCast(ExpressionNodeInfo info) =>
!info.Conversion.IsIdentity &&
!IsImplicitUpcast(info);

/// <summary>
/// Creates a new expression, adding casts as required.
/// </summary>
Expand Down Expand Up @@ -133,16 +154,7 @@ info.Parent is ExplicitObjectCreation objectCreation &&
return new ImplicitCast(info, conversion.MethodSymbol);
}

var implicitUpcast = conversion.IsImplicit &&
convertedType.Symbol is not null &&
!conversion.IsBoxing &&
(
resolvedType.Symbol is null ||
conversion.IsReference ||
convertedType.Symbol.SpecialType == SpecialType.System_Object)
;

if (!conversion.IsIdentity && !implicitUpcast)
if (IsNontrivialImplicitCast(info))
{
return new ImplicitCast(info);
}
Expand All @@ -156,6 +168,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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Gets the `ToString` method for the given type.
/// </summary>
private static IMethodSymbol? GetToStringMethod(ITypeSymbol? type)
{
return type?
.GetMembers()
.OfType<IMethodSymbol>()
.Where(method =>
method.GetName() == "ToString" &&
method.Parameters.Length == 0
)
.FirstOrDefault();
}

private ImplicitToString(ExpressionNodeInfo info, IMethodSymbol toString) : base(new ExpressionInfo(info.Context, info.ConvertedType, 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;

/// <summary>
/// Creates a new expression, adding a compiler generated `ToString` call if required.
/// </summary>
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)));
}

/// <summary>
/// Wraps the resulting expression in a `ToString` call, if a suitable `ToString` method is available.
/// </summary>
public static Expression Wrap(ExpressionNodeInfo info)
{
if (GetToStringMethod(info.ConvertedType.Symbol) is IMethodSymbol toString)
{
return new ImplicitToString(info, toString);
}
return Factory.Create(info);
}
}
}

0 comments on commit e72a53d

Please sign in to comment.