From 5cefeb495380957a515a60951fe0d9d2ce7ea616 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 6 May 2026 09:33:48 +0200 Subject: [PATCH 1/8] C#: Add operator calls for postfix unary expression (whenever possible) as we also do for prefix unary expressions. --- .../Entities/Expressions/PostfixUnary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs index 051a03e9f8c2..68d1a9af75c6 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs @@ -21,11 +21,11 @@ private PostfixUnary(ExpressionNodeInfo info, ExprKind kind, ExpressionSyntax op protected override void PopulateExpression(TextWriter trapFile) { Create(Context, operand, this, 0); + AddOperatorCall(trapFile, Syntax); if ((operatorKind == ExprKind.POST_INCR || operatorKind == ExprKind.POST_DECR) && Kind == ExprKind.OPERATOR_INVOCATION) { - AddOperatorCall(trapFile, Syntax); trapFile.mutator_invocation_mode(this, 2); } } From 6545cb35a14f97492f61509f00ae481071f6b73f Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 6 May 2026 09:43:35 +0200 Subject: [PATCH 2/8] C#: Improve the GetCallType method to also take extension operators into account. --- .../Semmle.Extraction.CSharp/Entities/Expression.cs | 12 ++++++------ .../Entities/Expressions/Invocation.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs index 4ab90def2c16..bf02ba49a2bd 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs @@ -234,9 +234,9 @@ type.SpecialType is SpecialType.System_IntPtr || /// /// The expression syntax node. /// Returns the target method symbol, or null if it cannot be resolved. - 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; @@ -255,7 +255,7 @@ 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(); } @@ -281,7 +281,7 @@ public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, Expr /// The expression. 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); @@ -312,9 +312,9 @@ public enum CallType /// The call type. 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) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Invocation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Invocation.cs index 343f288eeafe..5b25e53e8eef 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Invocation.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Invocation.cs @@ -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(): From 8a647ea1321ce69f2f98805683d1f31f61e6080d Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 6 May 2026 10:13:27 +0200 Subject: [PATCH 3/8] C#: Do not change kind of unary expressions to operator invocations expressions and cleanup the implementation. --- .../Entities/Expression.cs | 10 ---------- .../Entities/Expressions/Cast.cs | 12 +++++++++++- .../Entities/Expressions/Factory.cs | 6 +++--- .../Entities/Expressions/PostfixUnary.cs | 18 ++++++------------ .../Entities/Expressions/Unary.cs | 16 ++++------------ 5 files changed, 24 insertions(+), 38 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs index bf02ba49a2bd..aa9408693197 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs @@ -263,16 +263,6 @@ type.SpecialType is SpecialType.System_IntPtr || return si.Symbol as IMethodSymbol; } - /// - /// Adapt the operator kind depending on whether it's a dynamic call or a user-operator call. - /// - /// - /// - /// - /// - public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, ExpressionSyntax node) => - GetCallType(cx, node).AdjustKind(originalKind); - /// /// If the expression calls an operator, add an expr_call() /// to show the target of the call. Also note the dynamic method diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Cast.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Cast.cs index c11711f30926..c24a7914c7c7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Cast.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Cast.cs @@ -10,7 +10,17 @@ internal class Cast : Expression 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))) { } + + /// + /// Adapt the operator kind depending on whether it's a dynamic call or a user-operator call. + /// + /// + /// + /// + /// + 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(); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs index ed8dae3738fc..708e15d89ea9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs @@ -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); @@ -254,7 +254,7 @@ internal static Expression Create(ExpressionNodeInfo info) 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); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs index 68d1a9af75c6..f8e0072eaf9c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs @@ -4,27 +4,21 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions { - internal class PostfixUnary : Expression + internal class PostfixUnary : Expression { - 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) { trapFile.mutator_invocation_mode(this, 2); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs index 699c3810d116..7d1af2f914a8 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs @@ -6,28 +6,20 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions { internal class Unary : Expression { - private Unary(ExpressionNodeInfo info, ExprKind kind) - : base(info.SetKind(UnaryOperatorKind(info.Context, info.Kind, info.Node))) + private Unary(ExpressionNodeInfo info) + : base(info) { - operatorKind = kind; } - private readonly ExprKind operatorKind; - public static Unary Create(ExpressionNodeInfo info) - { - var ret = new Unary(info, info.Kind); - ret.TryPopulate(); - return ret; - } + public static Expression Create(ExpressionNodeInfo info) => new Unary(info).TryPopulate(); protected override void PopulateExpression(TextWriter trapFile) { Create(Context, Syntax.Operand, this, 0); AddOperatorCall(trapFile, Syntax); - if ((operatorKind == ExprKind.PRE_INCR || operatorKind == ExprKind.PRE_DECR) && - Kind == ExprKind.OPERATOR_INVOCATION) + if (Kind == ExprKind.PRE_INCR || Kind == ExprKind.PRE_DECR) { trapFile.mutator_invocation_mode(this, 1); } From 7543472b3a92a7e765092d1fcd9641c6decdfbc9 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 6 May 2026 10:38:55 +0200 Subject: [PATCH 4/8] C#: Rename Unary to PrefixUnary. --- .../Entities/Expressions/Factory.cs | 18 +++++++++--------- .../Expressions/{Unary.cs => PrefixUnary.cs} | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) rename csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/{Unary.cs => PrefixUnary.cs} (79%) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs index 708e15d89ea9..70760590070e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs @@ -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); @@ -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 PrefixUnary.Create(info.SetKind(ExprKind.LOG_NOT)); 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); @@ -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); @@ -248,7 +248,7 @@ 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); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs similarity index 79% rename from csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs rename to csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs index 7d1af2f914a8..fc251aeecbc0 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs @@ -4,15 +4,15 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions { - internal class Unary : Expression + internal class PrefixUnary : Expression { - private Unary(ExpressionNodeInfo info) + private PrefixUnary(ExpressionNodeInfo info) : base(info) { } - public static Expression Create(ExpressionNodeInfo info) => new Unary(info).TryPopulate(); + public static Expression Create(ExpressionNodeInfo info) => new PrefixUnary(info).TryPopulate(); protected override void PopulateExpression(TextWriter trapFile) { From 8cf53dd2d06fd3941a337303b37b111530004d68 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 6 May 2026 10:49:27 +0200 Subject: [PATCH 5/8] C#: Update the DB scheme to consider unary expressions operator calls and add a new kind for NOT expressions. --- .../Semmle.Extraction.CSharp/Kinds/ExprKind.cs | 1 + csharp/ql/lib/semmlecode.csharp.dbscheme | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs index 46a694192842..ba2c53dadb6c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs @@ -133,6 +133,7 @@ public enum ExprKind COLLECTION = 136, SPREAD_ELEMENT = 137, INTERPOLATED_STRING_INSERT = 138, + NOT = 139, DEFINE_SYMBOL = 999, } } diff --git a/csharp/ql/lib/semmlecode.csharp.dbscheme b/csharp/ql/lib/semmlecode.csharp.dbscheme index 3cabc77473cb..a1a00aeec45e 100644 --- a/csharp/ql/lib/semmlecode.csharp.dbscheme +++ b/csharp/ql/lib/semmlecode.csharp.dbscheme @@ -1198,6 +1198,7 @@ case @expr.kind of | 136 = @collection_expr | 137 = @spread_element_expr | 138 = @interpolated_string_insert_expr +| 139 = @not_expr /* Preprocessor */ | 999 = @define_symbol_expr ; @@ -1216,7 +1217,7 @@ case @expr.kind of | @string_literal_expr | @null_literal_expr; @assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; -@assign_op_call_expr = @assign_arith_expr | @assign_bitwise_expr +@assign_op_call_expr = @assign_arith_expr | @assign_bitwise_expr; @assign_op_expr = @assign_op_call_expr | @assign_event_expr | @assign_coalesce_expr; @assign_event_expr = @add_event_expr | @remove_event_expr; @@ -1263,6 +1264,7 @@ case @expr.kind of @ternary_log_op_expr = @conditional_expr; @bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_not_op_expr = @log_not_expr | @not_expr; @un_log_op_expr = @log_not_expr; @log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; @@ -1279,12 +1281,13 @@ case @expr.kind of @ternary_op = @ternary_log_op_expr; @bin_op = @assign_expr | @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; -@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr +@un_op_call_expr = @un_arith_op_expr | @un_not_op_expr | @un_bit_op_expr; +@un_op = @un_op_call_expr | @sizeof_expr | @pointer_indirection_expr | @address_of_expr; @anonymous_function_expr = @lambda_expr | @anonymous_method_expr; -@op_invoke_expr = @operator_invocation_expr | @assign_op_call_expr +@op_invoke_expr = @operator_invocation_expr | @assign_op_call_expr | @un_op_call_expr; @call = @method_invocation_expr | @constructor_init_expr | @op_invoke_expr | @delegate_invocation_expr | @object_creation_expr | @call_access_expr | @local_function_invocation_expr | @function_pointer_invocation_expr; @@ -1311,7 +1314,7 @@ implicitly_typed_object_creation( unique int id: @implicitly_typeable_object_creation_expr ref); mutator_invocation_mode( - unique int id: @operator_invocation_expr ref, + unique int id: @mut_op_expr ref, int mode: int ref /* prefix = 1, postfix = 2*/); expr_value( From 239683e698e40c90dba06c9b9fafdf1959b95a84 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 8 May 2026 15:13:42 +0200 Subject: [PATCH 6/8] C#: Add extractor support for using different expression kinds for NOT expressions. --- .../Entities/Expressions/Factory.cs | 2 +- .../Entities/Expressions/Not.cs | 19 +++++++++++++++++++ .../Entities/Expressions/PrefixUnary.cs | 1 - 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Not.cs diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs index 70760590070e..8ef19df7ab5d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs @@ -146,7 +146,7 @@ internal static Expression Create(ExpressionNodeInfo info) return Name.Create(info); case SyntaxKind.LogicalNotExpression: - return PrefixUnary.Create(info.SetKind(ExprKind.LOG_NOT)); + return Not.Create(info); case SyntaxKind.BitwiseNotExpression: return PrefixUnary.Create(info.SetKind(ExprKind.BIT_NOT)); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Not.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Not.cs new file mode 100644 index 000000000000..785ac9990d81 --- /dev/null +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Not.cs @@ -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)); + } + } +} diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs index fc251aeecbc0..155c8d2c5511 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs @@ -11,7 +11,6 @@ private PrefixUnary(ExpressionNodeInfo info) { } - public static Expression Create(ExpressionNodeInfo info) => new PrefixUnary(info).TryPopulate(); protected override void PopulateExpression(TextWriter trapFile) From 8f22066df62be1fe7e2149f826f89a6034707f39 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 6 May 2026 13:52:39 +0200 Subject: [PATCH 7/8] C#: Update the QL library to reflect the changes to the DB scheme. --- .../code/csharp/exprs/ArithmeticOperation.qll | 3 ++- .../code/csharp/exprs/BitwiseOperation.qll | 2 +- .../ql/lib/semmle/code/csharp/exprs/Call.qll | 4 +-- .../ql/lib/semmle/code/csharp/exprs/Expr.qll | 27 +++++++++++++++++++ .../code/csharp/exprs/LogicalOperation.qll | 6 ++--- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll b/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll index 193c48ed3a2b..67411ed5eff2 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll @@ -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`. diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/BitwiseOperation.qll b/csharp/ql/lib/semmle/code/csharp/exprs/BitwiseOperation.qll index 14bb3d74e2b2..2bc704f68093 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/BitwiseOperation.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/BitwiseOperation.qll @@ -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`. diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll index 9dbf898e2864..42b69b77d038 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll @@ -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 @@ -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. */ diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll index a26afb004901..867b40eefa2c 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll @@ -234,6 +234,33 @@ class UnaryOperation extends Operation, @un_op { override string toString() { result = this.getOperator() + "..." } } +/** + * A unary operator call. Either a unary arithmetic operator call (`UnaryArithmeticOperatorCall`), + * a unary bitwise operator call (`UnaryBitwiseOperatorCall`), or a + * unary logical operator call (`UnaryLogicalOperatorCall`). + */ +class UnaryCallOperation extends OperatorCall, UnaryOperation, @un_op_call_expr { + override string toString() { result = UnaryOperation.super.toString() } +} + +/** + * A logical 'not', for example `!String.IsNullOrEmpty(s)` or a call to a user-defined + * not operator such as `!x` in: + * ```csharp + * class Negatable { + * public static Negatable operator !(Negatable n) => { ...}; + * } + * + * var x = new Negatable(); + * var y = !x; + * ``` + */ +class UnaryNotOperation extends UnaryCallOperation, @un_not_op_expr { + override string getOperator() { result = "!" } + + override string getAPrimaryQlClass() { result = "UnaryNotOperation" } +} + /** * A binary operation. Either a binary arithmetic operation * (`BinaryArithmeticOperation`), a binary bitwise operation diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/LogicalOperation.qll b/csharp/ql/lib/semmle/code/csharp/exprs/LogicalOperation.qll index 4161f734c9b7..ad2af29555ba 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/LogicalOperation.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/LogicalOperation.qll @@ -18,13 +18,13 @@ class LogicalOperation extends Operation, @log_expr { /** * A unary logical operation, that is, a logical 'not' (`LogicalNotExpr`). */ -class UnaryLogicalOperation extends LogicalOperation, UnaryOperation, @un_log_op_expr { } +class UnaryLogicalOperation extends LogicalOperation, UnaryCallOperation, @un_log_op_expr { } /** * A logical 'not', for example `!String.IsNullOrEmpty(s)`. */ -class LogicalNotExpr extends UnaryLogicalOperation, @log_not_expr { - override string getOperator() { result = "!" } +class LogicalNotExpr extends UnaryLogicalOperation, UnaryNotOperation, @log_not_expr { + override string getOperator() { result = UnaryNotOperation.super.getOperator() } override string getAPrimaryQlClass() { result = "LogicalNotExpr" } } From 6246521fdfead08f281c09ec9f09915019333ab9 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 8 May 2026 15:22:07 +0200 Subject: [PATCH 8/8] C#: Continue to use the special handling of LogicalNotExpr in the controlflow implementation. --- .../lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll index c56d3dab420c..f5940d49f31d 100644 --- a/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -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