Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
94e7a0b
C#: Add Increment/Decrement instance operator test example and update…
michaelnebel May 4, 2026
73b5ff5
C#: Adjust the extractor to correctly handle names for user defined i…
michaelnebel May 5, 2026
3a55c9d
C#: Update condition for UnaryOperators to also handle user-defined i…
michaelnebel May 5, 2026
fc28f2f
C#: Update PrintAst expected output.
michaelnebel May 5, 2026
1c38cc4
C#: Add an increment/decrement operator test case.
michaelnebel May 5, 2026
ab5de08
C#: Add operator calls for postfix unary expression (whenever possibl…
michaelnebel May 6, 2026
e2bdae7
C#: Improve the GetCallType method to also take extension operators i…
michaelnebel May 6, 2026
9499571
C#: Do not change kind of increment/decrement expressions to operator…
michaelnebel May 6, 2026
b8e673c
C#: Rename Unary to PrefixUnary.
michaelnebel May 6, 2026
45b6cde
C#: Move the adjust kind to the Cast class.
michaelnebel May 6, 2026
5bc4b9b
C#: Update the DB scheme to consider increment/decrement expressions …
michaelnebel May 6, 2026
a5a621d
C#: Update the DB scheme to consider more unary operators to be opera…
michaelnebel May 6, 2026
442475c
C#: Update the QL library to reflect the changes to the DB scheme.
michaelnebel May 6, 2026
3dedb58
C#: Add a new kind for NOT expressions (to cover non-logical not).
michaelnebel May 8, 2026
94079fb
C#: Add extractor support for using different expression kinds for NO…
michaelnebel May 8, 2026
82d09f6
C#: Add a QL class for UnaryNotOperation.
michaelnebel May 8, 2026
7b5e5cf
C#: Continue to use the special handling of LogicalNotExpr in the con…
michaelnebel May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ public static string GetName(this ISymbol symbol, bool useMetadataName = false)
{ "op_False", "false" }
});

/// <summary>
/// The operatorname for user-defined increment and decrement operators are "op_IncrementAssignment" and
/// "op_DecrementAssignment" respectively.
/// Thus we need to handle this explicitly to avoid postfixing them with an "=".
/// </summary>
private static bool isIncrementOrDecrement(string operatorName) => operatorName == "++" || operatorName == "--";

/// <summary>
/// Convert an operator method name in to a symbolic name.
/// A return value indicates whether the conversion succeeded.
Expand All @@ -72,7 +79,7 @@ public static bool TryGetOperatorSymbol(this ISymbol symbol, out string operator
if (match.Success && methodToOperator.TryGetValue($"op_{match.Groups[2]}", out var rawOperatorName))
{
var prefix = match.Groups[1].Success ? "checked " : "";
var postfix = match.Groups[3].Success ? "=" : "";
var postfix = match.Groups[3].Success && !isIncrementOrDecrement(rawOperatorName) ? "=" : "";
operatorName = $"{prefix}{rawOperatorName}{postfix}";
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ type.SpecialType is SpecialType.System_IntPtr ||
/// </summary>
/// <param name="node">The expression syntax node.</param>
/// <returns>Returns the target method symbol, or null if it cannot be resolved.</returns>
protected IMethodSymbol? GetTargetSymbol(ExpressionSyntax node)
protected static IMethodSymbol? GetTargetSymbol(Context cx, ExpressionSyntax node)
{
var si = Context.GetSymbolInfo(node);
var si = cx.GetSymbolInfo(node);
if (si.Symbol is ISymbol symbol)
{
var method = symbol as IMethodSymbol;
Expand All @@ -255,24 +255,14 @@ type.SpecialType is SpecialType.System_IntPtr ||
.Where(method => method.Parameters.Length >= syntax.ArgumentList.Arguments.Count)
.Where(method => method.Parameters.Count(p => !p.HasExplicitDefaultValue) <= syntax.ArgumentList.Arguments.Count);

return Context.ExtractionContext.IsStandalone ?
return cx.ExtractionContext.IsStandalone ?
candidates.FirstOrDefault() :
candidates.SingleOrDefault();
}

return si.Symbol as IMethodSymbol;
}

/// <summary>
/// Adapt the operator kind depending on whether it's a dynamic call or a user-operator call.
/// </summary>
/// <param name="cx"></param>
/// <param name="node"></param>
/// <param name="originalKind"></param>
/// <returns></returns>
public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, ExpressionSyntax node) =>
GetCallType(cx, node).AdjustKind(originalKind);

/// <summary>
/// If the expression calls an operator, add an expr_call()
/// to show the target of the call. Also note the dynamic method
Expand All @@ -281,7 +271,7 @@ public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, Expr
/// <param name="node">The expression.</param>
public void AddOperatorCall(TextWriter trapFile, ExpressionSyntax node)
{
var @operator = GetTargetSymbol(node);
var @operator = GetTargetSymbol(Context, node);
if (@operator is IMethodSymbol method)
{
var callType = GetCallType(Context, node);
Expand Down Expand Up @@ -312,9 +302,9 @@ public enum CallType
/// <returns>The call type.</returns>
public static CallType GetCallType(Context cx, ExpressionSyntax node)
{
var @operator = cx.GetSymbolInfo(node);
var @operator = GetTargetSymbol(cx, node);

if (@operator.Symbol is IMethodSymbol method)
if (@operator is IMethodSymbol method)
{
if (method.ContainingSymbol is ITypeSymbol containingSymbol && containingSymbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Dynamic)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@ internal class Cast : Expression<CastExpressionSyntax>
private const int ExpressionIndex = 0;
private const int TypeAccessIndex = 1;

private Cast(ExpressionNodeInfo info) : base(info.SetKind(UnaryOperatorKind(info.Context, ExprKind.CAST, info.Node))) { }
private Cast(ExpressionNodeInfo info) : base(info.SetKind(GetKind(info.Context, ExprKind.CAST, info.Node))) { }

/// <summary>
/// Adapt the operator kind depending on whether it's a dynamic call or a user-operator call.
/// </summary>
/// <param name="cx"></param>
/// <param name="node"></param>
/// <param name="originalKind"></param>
/// <returns></returns>
public static ExprKind GetKind(Context cx, ExprKind originalKind, ExpressionSyntax node) =>
GetCallType(cx, node).AdjustKind(originalKind);

public static Expression Create(ExpressionNodeInfo info) => new Cast(info).TryPopulate();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ internal static Expression Create(ExpressionNodeInfo info)
return Invocation.Create(info);

case SyntaxKind.PostIncrementExpression:
return PostfixUnary.Create(info.SetKind(ExprKind.POST_INCR), ((PostfixUnaryExpressionSyntax)info.Node).Operand);
return PostfixUnary.Create(info.SetKind(ExprKind.POST_INCR));

case SyntaxKind.PostDecrementExpression:
return PostfixUnary.Create(info.SetKind(ExprKind.POST_DECR), ((PostfixUnaryExpressionSyntax)info.Node).Operand);
return PostfixUnary.Create(info.SetKind(ExprKind.POST_DECR));

case SyntaxKind.AwaitExpression:
return Await.Create(info);
Expand Down Expand Up @@ -109,10 +109,10 @@ internal static Expression Create(ExpressionNodeInfo info)
return MemberAccess.Create(info, (MemberAccessExpressionSyntax)info.Node);

case SyntaxKind.UnaryMinusExpression:
return Unary.Create(info.SetKind(ExprKind.MINUS));
return PrefixUnary.Create(info.SetKind(ExprKind.MINUS));

case SyntaxKind.UnaryPlusExpression:
return Unary.Create(info.SetKind(ExprKind.PLUS));
return PrefixUnary.Create(info.SetKind(ExprKind.PLUS));

case SyntaxKind.SimpleLambdaExpression:
return Lambda.Create(info, (SimpleLambdaExpressionSyntax)info.Node);
Expand Down Expand Up @@ -146,16 +146,16 @@ internal static Expression Create(ExpressionNodeInfo info)
return Name.Create(info);

case SyntaxKind.LogicalNotExpression:
return Unary.Create(info.SetKind(ExprKind.LOG_NOT));
return Not.Create(info);

case SyntaxKind.BitwiseNotExpression:
return Unary.Create(info.SetKind(ExprKind.BIT_NOT));
return PrefixUnary.Create(info.SetKind(ExprKind.BIT_NOT));

case SyntaxKind.PreIncrementExpression:
return Unary.Create(info.SetKind(ExprKind.PRE_INCR));
return PrefixUnary.Create(info.SetKind(ExprKind.PRE_INCR));

case SyntaxKind.PreDecrementExpression:
return Unary.Create(info.SetKind(ExprKind.PRE_DECR));
return PrefixUnary.Create(info.SetKind(ExprKind.PRE_DECR));

case SyntaxKind.ThisExpression:
return This.CreateExplicit(info);
Expand All @@ -164,10 +164,10 @@ internal static Expression Create(ExpressionNodeInfo info)
return PropertyFieldAccess.Create(info);

case SyntaxKind.AddressOfExpression:
return Unary.Create(info.SetKind(ExprKind.ADDRESS_OF));
return PrefixUnary.Create(info.SetKind(ExprKind.ADDRESS_OF));

case SyntaxKind.PointerIndirectionExpression:
return Unary.Create(info.SetKind(ExprKind.POINTER_INDIRECTION));
return PrefixUnary.Create(info.SetKind(ExprKind.POINTER_INDIRECTION));

case SyntaxKind.DefaultExpression:
return Default.Create(info);
Expand Down Expand Up @@ -248,13 +248,13 @@ internal static Expression Create(ExpressionNodeInfo info)
return RangeExpression.Create(info);

case SyntaxKind.IndexExpression:
return Unary.Create(info.SetKind(ExprKind.INDEX));
return PrefixUnary.Create(info.SetKind(ExprKind.INDEX));

case SyntaxKind.SwitchExpression:
return Switch.Create(info);

case SyntaxKind.SuppressNullableWarningExpression:
return PostfixUnary.Create(info.SetKind(ExprKind.SUPPRESS_NULLABLE_WARNING), ((PostfixUnaryExpressionSyntax)info.Node).Operand);
return PostfixUnary.Create(info.SetKind(ExprKind.SUPPRESS_NULLABLE_WARNING));

case SyntaxKind.WithExpression:
return WithExpression.Create(info);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected override void PopulateExpression(TextWriter trapFile)

var child = -1;
string? memberName = null;
var target = GetTargetSymbol(Syntax);
var target = GetTargetSymbol(Context, Syntax);
switch (Syntax.Expression)
{
case MemberAccessExpressionSyntax memberAccess when IsValidMemberAccessKind():
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.CodeAnalysis;
using Semmle.Extraction.Kinds;

namespace Semmle.Extraction.CSharp.Entities.Expressions
{
internal static class Not
{
public static Expression Create(ExpressionNodeInfo info)
{
var cx = info.Context;
if (cx.GetSymbolInfo(info.Node).Symbol is IMethodSymbol @operator &&
@operator.MethodKind == MethodKind.BuiltinOperator)
{
return PrefixUnary.Create(info.SetKind(ExprKind.LOG_NOT));
}
return PrefixUnary.Create(info.SetKind(ExprKind.NOT));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,22 @@

namespace Semmle.Extraction.CSharp.Entities.Expressions
{
internal class PostfixUnary : Expression<ExpressionSyntax>
internal class PostfixUnary : Expression<PostfixUnaryExpressionSyntax>
{
private PostfixUnary(ExpressionNodeInfo info, ExprKind kind, ExpressionSyntax operand)
: base(info.SetKind(UnaryOperatorKind(info.Context, kind, info.Node)))
private PostfixUnary(ExpressionNodeInfo info)
: base(info)
{
this.operand = operand;
operatorKind = kind;
}

private readonly ExpressionSyntax operand;
private readonly ExprKind operatorKind;

public static Expression Create(ExpressionNodeInfo info, ExpressionSyntax operand) => new PostfixUnary(info, info.Kind, operand).TryPopulate();
public static Expression Create(ExpressionNodeInfo info) => new PostfixUnary(info).TryPopulate();

protected override void PopulateExpression(TextWriter trapFile)
{
Create(Context, operand, this, 0);
Create(Context, Syntax.Operand, this, 0);
AddOperatorCall(trapFile, Syntax);

if ((operatorKind == ExprKind.POST_INCR || operatorKind == ExprKind.POST_DECR) &&
Kind == ExprKind.OPERATOR_INVOCATION)
if (Kind == ExprKind.POST_INCR || Kind == ExprKind.POST_DECR)
{
AddOperatorCall(trapFile, Syntax);
trapFile.mutator_invocation_mode(this, 2);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.IO;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.Kinds;

namespace Semmle.Extraction.CSharp.Entities.Expressions
{
internal class PrefixUnary : Expression<PrefixUnaryExpressionSyntax>
{
private PrefixUnary(ExpressionNodeInfo info)
: base(info)
{
}

public static Expression Create(ExpressionNodeInfo info) => new PrefixUnary(info).TryPopulate();

protected override void PopulateExpression(TextWriter trapFile)
{
Create(Context, Syntax.Operand, this, 0);
AddOperatorCall(trapFile, Syntax);

if (Kind == ExprKind.PRE_INCR || Kind == ExprKind.PRE_DECR)
{
trapFile.mutator_invocation_mode(this, 1);
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public enum ExprKind
COLLECTION = 136,
SPREAD_ELEMENT = 137,
INTERPOLATED_STRING_INSERT = 138,
NOT = 139,
DEFINE_SYMBOL = 999,
}
}
3 changes: 3 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/Callable.qll
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,9 @@ class UnaryOperator extends Operator {
this.getNumberOfParameters() = 1 and
not this instanceof ConversionOperator and
not this instanceof CompoundAssignmentOperator
or
// Instance increment and decrement operators don't have a parameter (only a qualifier).
this.getNumberOfParameters() = 0 and not this.isStatic()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@ private module Input implements InputSig1, InputSig2 {
exists(QualifiableExpr qe | qe.isConditional() | n = getQualifier(qe))
}

predicate postOrInOrder(Ast::AstNode n) { n instanceof YieldStmt or n instanceof Call }
predicate postOrInOrder(Ast::AstNode n) {
n instanceof YieldStmt
or
n instanceof Call and not n instanceof LogicalNotExpr
}

predicate beginAbruptCompletion(
Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class ArithmeticOperation extends Operation, @arith_op_expr {
* (`UnaryMinusExpr`), a unary plus operation (`UnaryPlusExpr`),
* or a mutator operation (`MutatorOperation`).
*/
class UnaryArithmeticOperation extends ArithmeticOperation, UnaryOperation, @un_arith_op_expr { }
class UnaryArithmeticOperation extends ArithmeticOperation, UnaryCallOperation, @un_arith_op_expr {
}

/**
* A unary minus operation, for example `-x`.
Expand All @@ -44,7 +45,7 @@ class UnaryPlusExpr extends UnaryArithmeticOperation, @plus_expr {
* A mutator operation. Either an increment operation (`IncrementOperation`)
* or a decrement operation (`DecrementOperation`).
*/
class MutatorOperation extends UnaryArithmeticOperation, @mut_op_expr { }
class MutatorOperation extends UnaryArithmeticOperation, QualifiableExpr, @mut_op_expr { }

/**
* An increment operation. Either a postfix increment operation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class BitwiseOperation extends Operation, @bit_expr { }
* A unary bitwise operation, that is, a bitwise complement operation
* (`ComplementExpr`).
*/
class UnaryBitwiseOperation extends BitwiseOperation, UnaryOperation, @un_bit_op_expr { }
class UnaryBitwiseOperation extends BitwiseOperation, UnaryCallOperation, @un_bit_op_expr { }

/**
* A bitwise complement operation, for example `~x`.
Expand Down
4 changes: 2 additions & 2 deletions csharp/ql/lib/semmle/code/csharp/exprs/Call.qll
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ class ExtensionOperatorCall extends OperatorCall {
}

/**
* A call to a user-defined mutator operator, for example `a++` on
* A call to a mutator operator, for example `a++` on
* line 7 in
*
* ```csharp
Expand All @@ -560,7 +560,7 @@ class ExtensionOperatorCall extends OperatorCall {
* }
* ```
*/
class MutatorOperatorCall extends OperatorCall {
class MutatorOperatorCall extends MutatorOperation {
MutatorOperatorCall() { mutator_invocation_mode(this, _) }

/** Holds if the operator is in prefix position. */
Expand Down
Loading
Loading