diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Util/SymbolExtensions.cs b/csharp/extractor/Semmle.Extraction.CSharp.Util/SymbolExtensions.cs
index 92d7ecfad6bb..50604e2404e4 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp.Util/SymbolExtensions.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp.Util/SymbolExtensions.cs
@@ -52,6 +52,13 @@ public static string GetName(this ISymbol symbol, bool useMetadataName = false)
{ "op_False", "false" }
});
+ ///
+ /// 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 "=".
+ ///
+ private static bool isIncrementOrDecrement(string operatorName) => operatorName == "++" || operatorName == "--";
+
///
/// Convert an operator method name in to a symbolic name.
/// A return value indicates whether the conversion succeeded.
@@ -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;
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs
index 4ab90def2c16..aa9408693197 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();
}
@@ -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
@@ -281,7 +271,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 +302,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/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..70760590070e 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);
@@ -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,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);
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():
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs
index 051a03e9f8c2..f8e0072eaf9c 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs
@@ -4,28 +4,22 @@
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)
{
- AddOperatorCall(trapFile, Syntax);
trapFile.mutator_invocation_mode(this, 2);
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs
new file mode 100644
index 000000000000..fc251aeecbc0
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs
@@ -0,0 +1,28 @@
+using System.IO;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Extraction.Kinds;
+
+namespace Semmle.Extraction.CSharp.Entities.Expressions
+{
+ internal class PrefixUnary : Expression
+ {
+ 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);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs
deleted file mode 100644
index 699c3810d116..000000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Unary.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.IO;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Semmle.Extraction.Kinds;
-
-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)))
- {
- operatorKind = kind;
- }
-
- private readonly ExprKind operatorKind;
-
- public static Unary Create(ExpressionNodeInfo info)
- {
- var ret = new Unary(info, info.Kind);
- ret.TryPopulate();
- return ret;
- }
-
- 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)
- {
- trapFile.mutator_invocation_mode(this, 1);
- }
- }
- }
-}
diff --git a/csharp/ql/lib/semmle/code/csharp/Callable.qll b/csharp/ql/lib/semmle/code/csharp/Callable.qll
index 9416a7d4d9c7..198ad2af1801 100644
--- a/csharp/ql/lib/semmle/code/csharp/Callable.qll
+++ b/csharp/ql/lib/semmle/code/csharp/Callable.qll
@@ -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()
}
}
diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll b/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll
index 193c48ed3a2b..20f79e4f0d40 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`.
@@ -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
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..2ea3dde4fce3 100644
--- a/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll
+++ b/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll
@@ -234,6 +234,15 @@ 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 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..a8eb5f8079bf 100644
--- a/csharp/ql/lib/semmle/code/csharp/exprs/LogicalOperation.qll
+++ b/csharp/ql/lib/semmle/code/csharp/exprs/LogicalOperation.qll
@@ -18,7 +18,7 @@ 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)`.
diff --git a/csharp/ql/lib/semmlecode.csharp.dbscheme b/csharp/ql/lib/semmlecode.csharp.dbscheme
index 3cabc77473cb..dbcb70797f2a 100644
--- a/csharp/ql/lib/semmlecode.csharp.dbscheme
+++ b/csharp/ql/lib/semmlecode.csharp.dbscheme
@@ -1216,7 +1216,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;
@@ -1279,12 +1279,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_log_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 +1312,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(
@@ -1339,7 +1340,8 @@ dynamic_member_name(
@qualifiable_expr = @member_access_expr
| @method_invocation_expr
| @element_access_expr
- | @assign_op_call_expr;
+ | @assign_op_call_expr
+ | @mut_op_expr;
conditional_access(
unique int id: @qualifiable_expr ref);
diff --git a/csharp/ql/test/library-tests/operators/Operators3.expected b/csharp/ql/test/library-tests/operators/Operators3.expected
index e81aec79ba19..bda7edb99bae 100644
--- a/csharp/ql/test/library-tests/operators/Operators3.expected
+++ b/csharp/ql/test/library-tests/operators/Operators3.expected
@@ -1 +1 @@
-| operators.cs:96:32:96:39 | implicit conversion |
+| operators.cs:118:36:118:43 | implicit conversion |
diff --git a/csharp/ql/test/library-tests/operators/Operators4.expected b/csharp/ql/test/library-tests/operators/Operators4.expected
index 49db993c093d..bee27656f264 100644
--- a/csharp/ql/test/library-tests/operators/Operators4.expected
+++ b/csharp/ql/test/library-tests/operators/Operators4.expected
@@ -1 +1 @@
-| operators.cs:101:32:101:39 | explicit conversion |
+| operators.cs:123:36:123:43 | explicit conversion |
diff --git a/csharp/ql/test/library-tests/operators/Operators5.expected b/csharp/ql/test/library-tests/operators/Operators5.expected
index 8e506e5119df..900b5170c349 100644
--- a/csharp/ql/test/library-tests/operators/Operators5.expected
+++ b/csharp/ql/test/library-tests/operators/Operators5.expected
@@ -1,15 +1,15 @@
-| operators.cs:23:30:23:31 | += | operators.cs:61:13:61:22 | ... += ... |
-| operators.cs:31:38:31:39 | checked += | operators.cs:77:17:77:26 | ... += ... |
-| operators.cs:33:38:33:39 | checked -= | operators.cs:78:17:78:26 | ... -= ... |
-| operators.cs:34:30:34:31 | -= | operators.cs:64:13:64:22 | ... -= ... |
-| operators.cs:36:38:36:39 | checked *= | operators.cs:79:17:79:26 | ... *= ... |
-| operators.cs:37:30:37:31 | *= | operators.cs:65:13:65:22 | ... *= ... |
-| operators.cs:39:38:39:39 | checked /= | operators.cs:80:17:80:26 | ... /= ... |
-| operators.cs:40:30:40:31 | /= | operators.cs:66:13:66:22 | ... /= ... |
-| operators.cs:42:30:42:31 | %= | operators.cs:67:13:67:22 | ... %= ... |
-| operators.cs:43:30:43:31 | &= | operators.cs:68:13:68:22 | ... &= ... |
-| operators.cs:44:30:44:31 | \|= | operators.cs:69:13:69:22 | ... \|= ... |
-| operators.cs:45:30:45:31 | ^= | operators.cs:70:13:70:22 | ... ^= ... |
-| operators.cs:46:30:46:32 | <<= | operators.cs:71:13:71:23 | ... <<= ... |
-| operators.cs:47:30:47:32 | >>= | operators.cs:72:13:72:23 | ... >>= ... |
-| operators.cs:48:30:48:33 | >>>= | operators.cs:73:13:73:24 | ... >>>= ... |
+| operators.cs:23:30:23:31 | += | operators.cs:70:13:70:22 | ... += ... |
+| operators.cs:31:38:31:39 | checked += | operators.cs:86:17:86:26 | ... += ... |
+| operators.cs:33:38:33:39 | checked -= | operators.cs:87:17:87:26 | ... -= ... |
+| operators.cs:34:30:34:31 | -= | operators.cs:73:13:73:22 | ... -= ... |
+| operators.cs:36:38:36:39 | checked *= | operators.cs:88:17:88:26 | ... *= ... |
+| operators.cs:37:30:37:31 | *= | operators.cs:74:13:74:22 | ... *= ... |
+| operators.cs:39:38:39:39 | checked /= | operators.cs:89:17:89:26 | ... /= ... |
+| operators.cs:40:30:40:31 | /= | operators.cs:75:13:75:22 | ... /= ... |
+| operators.cs:42:30:42:31 | %= | operators.cs:76:13:76:22 | ... %= ... |
+| operators.cs:43:30:43:31 | &= | operators.cs:77:13:77:22 | ... &= ... |
+| operators.cs:44:30:44:31 | \|= | operators.cs:78:13:78:22 | ... \|= ... |
+| operators.cs:45:30:45:31 | ^= | operators.cs:79:13:79:22 | ... ^= ... |
+| operators.cs:46:30:46:32 | <<= | operators.cs:80:13:80:23 | ... <<= ... |
+| operators.cs:47:30:47:32 | >>= | operators.cs:81:13:81:23 | ... >>= ... |
+| operators.cs:48:30:48:33 | >>>= | operators.cs:82:13:82:24 | ... >>>= ... |
diff --git a/csharp/ql/test/library-tests/operators/Operators6.expected b/csharp/ql/test/library-tests/operators/Operators6.expected
new file mode 100644
index 000000000000..f0878a511d9b
--- /dev/null
+++ b/csharp/ql/test/library-tests/operators/Operators6.expected
@@ -0,0 +1,10 @@
+| operators.cs:15:42:15:43 | ++ | operators.cs:66:19:66:23 | call to operator ++ |
+| operators.cs:15:42:15:43 | ++ | operators.cs:67:19:67:23 | call to operator ++ |
+| operators.cs:54:38:54:39 | checked ++ | operators.cs:100:17:100:19 | call to operator checked ++ |
+| operators.cs:54:38:54:39 | checked ++ | operators.cs:101:17:101:19 | call to operator checked ++ |
+| operators.cs:55:30:55:31 | ++ | operators.cs:93:13:93:15 | call to operator ++ |
+| operators.cs:55:30:55:31 | ++ | operators.cs:94:13:94:15 | call to operator ++ |
+| operators.cs:56:38:56:39 | checked -- | operators.cs:102:17:102:19 | call to operator checked -- |
+| operators.cs:56:38:56:39 | checked -- | operators.cs:103:17:103:19 | call to operator checked -- |
+| operators.cs:57:30:57:31 | -- | operators.cs:95:13:95:15 | call to operator -- |
+| operators.cs:57:30:57:31 | -- | operators.cs:96:13:96:15 | call to operator -- |
diff --git a/csharp/ql/test/library-tests/operators/Operators6.ql b/csharp/ql/test/library-tests/operators/Operators6.ql
new file mode 100644
index 000000000000..0eb578a11340
--- /dev/null
+++ b/csharp/ql/test/library-tests/operators/Operators6.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Test for operators
+ */
+
+import csharp
+
+from Operator op, OperatorCall call
+where
+ op.fromSource() and
+ (
+ op instanceof IncrementOperator or
+ op instanceof CheckedIncrementOperator or
+ op instanceof DecrementOperator or
+ op instanceof CheckedDecrementOperator
+ ) and
+ call.getTarget() = op
+select op, call
diff --git a/csharp/ql/test/library-tests/operators/PrintAst.expected b/csharp/ql/test/library-tests/operators/PrintAst.expected
index 8ea38d79b148..2087e5f96dc3 100644
--- a/csharp/ql/test/library-tests/operators/PrintAst.expected
+++ b/csharp/ql/test/library-tests/operators/PrintAst.expected
@@ -181,159 +181,204 @@ operators.cs:
# 48| 0: [Parameter] n
# 48| -1: [TypeMention] IntVector
# 48| 4: [BlockStmt] {...}
-# 51| 2: [Class] TestOperator
-# 53| 6: [Method] Main
-# 53| -1: [TypeMention] Void
+# 51| 2: [Class] C
+# 54| 6: [CheckedIncrementOperator] checked ++
+# 54| -1: [TypeMention] Void
# 54| 4: [BlockStmt] {...}
-# 55| 0: [LocalVariableDeclStmt] ... ...;
-# 55| 0: [LocalVariableDeclAndInitExpr] IntVector iv1 = ...
-# 55| -1: [TypeMention] IntVector
-# 55| 0: [LocalVariableAccess] access to local variable iv1
-# 55| 1: [ObjectCreation] object creation of type IntVector
-# 55| -1: [TypeMention] IntVector
-# 55| 0: [IntLiteral] 4
-# 56| 1: [LocalVariableDeclStmt] ... ...;
-# 56| 0: [LocalVariableDeclExpr] IntVector iv2
-# 56| 0: [TypeMention] IntVector
-# 57| 2: [ExprStmt] ...;
-# 57| 0: [AssignExpr] ... = ...
-# 57| 0: [LocalVariableAccess] access to local variable iv2
-# 57| 1: [OperatorCall] call to operator ++
-# 57| 0: [LocalVariableAccess] access to local variable iv1
-# 58| 3: [ExprStmt] ...;
-# 58| 0: [AssignExpr] ... = ...
-# 58| 0: [LocalVariableAccess] access to local variable iv2
-# 58| 1: [OperatorCall] call to operator ++
-# 58| 0: [LocalVariableAccess] access to local variable iv1
-# 60| 4: [LocalVariableDeclStmt] ... ...;
-# 60| 0: [LocalVariableDeclAndInitExpr] IntVector iv3 = ...
-# 60| -1: [TypeMention] IntVector
-# 60| 0: [LocalVariableAccess] access to local variable iv3
-# 60| 1: [ObjectCreation] object creation of type IntVector
-# 60| -1: [TypeMention] IntVector
-# 60| 0: [IntLiteral] 4
-# 61| 5: [ExprStmt] ...;
-# 61| 0: [AssignAddExpr] ... += ...
-# 61| 0: [LocalVariableAccess] access to local variable iv3
-# 61| 1: [LocalVariableAccess] access to local variable iv2
-# 64| 6: [ExprStmt] ...;
-# 64| 0: [AssignSubExpr] ... -= ...
-# 64| 0: [LocalVariableAccess] access to local variable iv3
-# 64| 1: [LocalVariableAccess] access to local variable iv2
-# 65| 7: [ExprStmt] ...;
-# 65| 0: [AssignMulExpr] ... *= ...
-# 65| 0: [LocalVariableAccess] access to local variable iv3
-# 65| 1: [LocalVariableAccess] access to local variable iv2
-# 66| 8: [ExprStmt] ...;
-# 66| 0: [AssignDivExpr] ... /= ...
-# 66| 0: [LocalVariableAccess] access to local variable iv3
-# 66| 1: [LocalVariableAccess] access to local variable iv2
-# 67| 9: [ExprStmt] ...;
-# 67| 0: [AssignRemExpr] ... %= ...
-# 67| 0: [LocalVariableAccess] access to local variable iv3
-# 67| 1: [LocalVariableAccess] access to local variable iv2
-# 68| 10: [ExprStmt] ...;
-# 68| 0: [AssignAndExpr] ... &= ...
-# 68| 0: [LocalVariableAccess] access to local variable iv3
-# 68| 1: [LocalVariableAccess] access to local variable iv2
-# 69| 11: [ExprStmt] ...;
-# 69| 0: [AssignOrExpr] ... |= ...
+# 55| 7: [IncrementOperator] ++
+# 55| -1: [TypeMention] Void
+# 55| 4: [BlockStmt] {...}
+# 56| 8: [CheckedDecrementOperator] checked --
+# 56| -1: [TypeMention] Void
+# 56| 4: [BlockStmt] {...}
+# 57| 9: [DecrementOperator] --
+# 57| -1: [TypeMention] Void
+# 57| 4: [BlockStmt] {...}
+# 60| 3: [Class] TestOperator
+# 62| 6: [Method] Main
+# 62| -1: [TypeMention] Void
+# 63| 4: [BlockStmt] {...}
+# 64| 0: [LocalVariableDeclStmt] ... ...;
+# 64| 0: [LocalVariableDeclAndInitExpr] IntVector iv1 = ...
+# 64| -1: [TypeMention] IntVector
+# 64| 0: [LocalVariableAccess] access to local variable iv1
+# 64| 1: [ObjectCreation] object creation of type IntVector
+# 64| -1: [TypeMention] IntVector
+# 64| 0: [IntLiteral] 4
+# 65| 1: [LocalVariableDeclStmt] ... ...;
+# 65| 0: [LocalVariableDeclExpr] IntVector iv2
+# 65| 0: [TypeMention] IntVector
+# 66| 2: [ExprStmt] ...;
+# 66| 0: [AssignExpr] ... = ...
+# 66| 0: [LocalVariableAccess] access to local variable iv2
+# 66| 1: [OperatorCall] call to operator ++
+# 66| 0: [LocalVariableAccess] access to local variable iv1
+# 67| 3: [ExprStmt] ...;
+# 67| 0: [AssignExpr] ... = ...
+# 67| 0: [LocalVariableAccess] access to local variable iv2
+# 67| 1: [OperatorCall] call to operator ++
+# 67| 0: [LocalVariableAccess] access to local variable iv1
+# 69| 4: [LocalVariableDeclStmt] ... ...;
+# 69| 0: [LocalVariableDeclAndInitExpr] IntVector iv3 = ...
+# 69| -1: [TypeMention] IntVector
# 69| 0: [LocalVariableAccess] access to local variable iv3
-# 69| 1: [LocalVariableAccess] access to local variable iv2
-# 70| 12: [ExprStmt] ...;
-# 70| 0: [AssignXorExpr] ... ^= ...
+# 69| 1: [ObjectCreation] object creation of type IntVector
+# 69| -1: [TypeMention] IntVector
+# 69| 0: [IntLiteral] 4
+# 70| 5: [ExprStmt] ...;
+# 70| 0: [AssignAddExpr] ... += ...
# 70| 0: [LocalVariableAccess] access to local variable iv3
# 70| 1: [LocalVariableAccess] access to local variable iv2
-# 71| 13: [ExprStmt] ...;
-# 71| 0: [AssignLeftShiftExpr] ... <<= ...
-# 71| 0: [LocalVariableAccess] access to local variable iv3
-# 71| 1: [LocalVariableAccess] access to local variable iv2
-# 72| 14: [ExprStmt] ...;
-# 72| 0: [AssignRightShiftExpr] ... >>= ...
-# 72| 0: [LocalVariableAccess] access to local variable iv3
-# 72| 1: [LocalVariableAccess] access to local variable iv2
-# 73| 15: [ExprStmt] ...;
-# 73| 0: [AssignUnsignedRightShiftExpr] ... >>>= ...
+# 73| 6: [ExprStmt] ...;
+# 73| 0: [AssignSubExpr] ... -= ...
# 73| 0: [LocalVariableAccess] access to local variable iv3
# 73| 1: [LocalVariableAccess] access to local variable iv2
-# 75| 16: [CheckedStmt] checked {...}
-# 76| 0: [BlockStmt] {...}
-# 77| 0: [ExprStmt] ...;
-# 77| 0: [AssignAddExpr] ... += ...
-# 77| 0: [LocalVariableAccess] access to local variable iv3
-# 77| 1: [LocalVariableAccess] access to local variable iv2
-# 78| 1: [ExprStmt] ...;
-# 78| 0: [AssignSubExpr] ... -= ...
-# 78| 0: [LocalVariableAccess] access to local variable iv3
-# 78| 1: [LocalVariableAccess] access to local variable iv2
-# 79| 2: [ExprStmt] ...;
-# 79| 0: [AssignMulExpr] ... *= ...
-# 79| 0: [LocalVariableAccess] access to local variable iv3
-# 79| 1: [LocalVariableAccess] access to local variable iv2
-# 80| 3: [ExprStmt] ...;
-# 80| 0: [AssignDivExpr] ... /= ...
-# 80| 0: [LocalVariableAccess] access to local variable iv3
-# 80| 1: [LocalVariableAccess] access to local variable iv2
-# 85| 3: [Struct] Digit
-# 87| 6: [Field] value
-# 87| -1: [TypeMention] byte
-# 89| 7: [InstanceConstructor] Digit
-#-----| 2: (Parameters)
-# 89| 0: [Parameter] value
-# 89| -1: [TypeMention] byte
-# 90| 4: [BlockStmt] {...}
-# 91| 0: [IfStmt] if (...) ...
-# 91| 0: [LogicalOrExpr] ... || ...
-# 91| 0: [LTExpr] ... < ...
-# 91| 0: [CastExpr] (...) ...
-# 91| 1: [ParameterAccess] access to parameter value
-# 91| 1: [IntLiteral] 0
-# 91| 1: [GTExpr] ... > ...
-# 91| 0: [CastExpr] (...) ...
-# 91| 1: [ParameterAccess] access to parameter value
-# 91| 1: [IntLiteral] 9
-# 92| 1: [ThrowStmt] throw ...;
-# 92| 0: [ObjectCreation] object creation of type ArgumentException
-# 92| 0: [TypeMention] ArgumentException
-# 93| 1: [ExprStmt] ...;
-# 93| 0: [AssignExpr] ... = ...
-# 93| 0: [FieldAccess] access to field value
-# 93| -1: [ThisAccess] this access
-# 93| 1: [ParameterAccess] access to parameter value
-# 96| 8: [ImplicitConversionOperator] implicit conversion
-# 96| -1: [TypeMention] byte
-#-----| 2: (Parameters)
-# 96| 0: [Parameter] d
-# 96| -1: [TypeMention] Digit
-# 97| 4: [BlockStmt] {...}
-# 98| 0: [ReturnStmt] return ...;
-# 98| 0: [FieldAccess] access to field value
-# 98| -1: [ParameterAccess] access to parameter d
-# 101| 9: [ExplicitConversionOperator] explicit conversion
-# 101| -1: [TypeMention] Digit
-#-----| 2: (Parameters)
-# 101| 0: [Parameter] b
-# 101| -1: [TypeMention] byte
-# 102| 4: [BlockStmt] {...}
-# 103| 0: [ReturnStmt] return ...;
-# 103| 0: [ObjectCreation] object creation of type Digit
-# 103| -1: [TypeMention] Digit
-# 103| 0: [ParameterAccess] access to parameter b
-# 108| 4: [Class] TestConversionOperator
-# 111| 6: [Method] Main
-# 111| -1: [TypeMention] Void
-# 112| 4: [BlockStmt] {...}
-# 113| 0: [LocalVariableDeclStmt] ... ...;
-# 113| 0: [LocalVariableDeclAndInitExpr] Digit d = ...
-# 113| -1: [TypeMention] Digit
-# 113| 0: [LocalVariableAccess] access to local variable d
-# 113| 1: [OperatorCall] call to operator explicit conversion
-# 113| -1: [TypeMention] Digit
-# 113| 0: [CastExpr] (...) ...
-# 113| 1: [IntLiteral] 8
-# 114| 1: [LocalVariableDeclStmt] ... ...;
-# 114| 0: [LocalVariableDeclAndInitExpr] Byte b = ...
-# 114| -1: [TypeMention] byte
-# 114| 0: [LocalVariableAccess] access to local variable b
-# 114| 1: [OperatorCall] call to operator implicit conversion
-# 114| 0: [LocalVariableAccess] access to local variable d
+# 74| 7: [ExprStmt] ...;
+# 74| 0: [AssignMulExpr] ... *= ...
+# 74| 0: [LocalVariableAccess] access to local variable iv3
+# 74| 1: [LocalVariableAccess] access to local variable iv2
+# 75| 8: [ExprStmt] ...;
+# 75| 0: [AssignDivExpr] ... /= ...
+# 75| 0: [LocalVariableAccess] access to local variable iv3
+# 75| 1: [LocalVariableAccess] access to local variable iv2
+# 76| 9: [ExprStmt] ...;
+# 76| 0: [AssignRemExpr] ... %= ...
+# 76| 0: [LocalVariableAccess] access to local variable iv3
+# 76| 1: [LocalVariableAccess] access to local variable iv2
+# 77| 10: [ExprStmt] ...;
+# 77| 0: [AssignAndExpr] ... &= ...
+# 77| 0: [LocalVariableAccess] access to local variable iv3
+# 77| 1: [LocalVariableAccess] access to local variable iv2
+# 78| 11: [ExprStmt] ...;
+# 78| 0: [AssignOrExpr] ... |= ...
+# 78| 0: [LocalVariableAccess] access to local variable iv3
+# 78| 1: [LocalVariableAccess] access to local variable iv2
+# 79| 12: [ExprStmt] ...;
+# 79| 0: [AssignXorExpr] ... ^= ...
+# 79| 0: [LocalVariableAccess] access to local variable iv3
+# 79| 1: [LocalVariableAccess] access to local variable iv2
+# 80| 13: [ExprStmt] ...;
+# 80| 0: [AssignLeftShiftExpr] ... <<= ...
+# 80| 0: [LocalVariableAccess] access to local variable iv3
+# 80| 1: [LocalVariableAccess] access to local variable iv2
+# 81| 14: [ExprStmt] ...;
+# 81| 0: [AssignRightShiftExpr] ... >>= ...
+# 81| 0: [LocalVariableAccess] access to local variable iv3
+# 81| 1: [LocalVariableAccess] access to local variable iv2
+# 82| 15: [ExprStmt] ...;
+# 82| 0: [AssignUnsignedRightShiftExpr] ... >>>= ...
+# 82| 0: [LocalVariableAccess] access to local variable iv3
+# 82| 1: [LocalVariableAccess] access to local variable iv2
+# 84| 16: [CheckedStmt] checked {...}
+# 85| 0: [BlockStmt] {...}
+# 86| 0: [ExprStmt] ...;
+# 86| 0: [AssignAddExpr] ... += ...
+# 86| 0: [LocalVariableAccess] access to local variable iv3
+# 86| 1: [LocalVariableAccess] access to local variable iv2
+# 87| 1: [ExprStmt] ...;
+# 87| 0: [AssignSubExpr] ... -= ...
+# 87| 0: [LocalVariableAccess] access to local variable iv3
+# 87| 1: [LocalVariableAccess] access to local variable iv2
+# 88| 2: [ExprStmt] ...;
+# 88| 0: [AssignMulExpr] ... *= ...
+# 88| 0: [LocalVariableAccess] access to local variable iv3
+# 88| 1: [LocalVariableAccess] access to local variable iv2
+# 89| 3: [ExprStmt] ...;
+# 89| 0: [AssignDivExpr] ... /= ...
+# 89| 0: [LocalVariableAccess] access to local variable iv3
+# 89| 1: [LocalVariableAccess] access to local variable iv2
+# 92| 17: [LocalVariableDeclStmt] ... ...;
+# 92| 0: [LocalVariableDeclAndInitExpr] C c = ...
+# 92| -1: [TypeMention] C
+# 92| 0: [LocalVariableAccess] access to local variable c
+# 92| 1: [ObjectCreation] object creation of type C
+# 92| 0: [TypeMention] C
+# 93| 18: [ExprStmt] ...;
+# 93| 0: [OperatorCall] call to operator ++
+# 93| 0: [LocalVariableAccess] access to local variable c
+# 94| 19: [ExprStmt] ...;
+# 94| 0: [OperatorCall] call to operator ++
+# 94| 0: [LocalVariableAccess] access to local variable c
+# 95| 20: [ExprStmt] ...;
+# 95| 0: [OperatorCall] call to operator --
+# 95| 0: [LocalVariableAccess] access to local variable c
+# 96| 21: [ExprStmt] ...;
+# 96| 0: [OperatorCall] call to operator --
+# 96| 0: [LocalVariableAccess] access to local variable c
+# 98| 22: [CheckedStmt] checked {...}
+# 99| 0: [BlockStmt] {...}
+# 100| 0: [ExprStmt] ...;
+# 100| 0: [OperatorCall] call to operator checked ++
+# 100| 0: [LocalVariableAccess] access to local variable c
+# 101| 1: [ExprStmt] ...;
+# 101| 0: [OperatorCall] call to operator checked ++
+# 101| 0: [LocalVariableAccess] access to local variable c
+# 102| 2: [ExprStmt] ...;
+# 102| 0: [OperatorCall] call to operator checked --
+# 102| 0: [LocalVariableAccess] access to local variable c
+# 103| 3: [ExprStmt] ...;
+# 103| 0: [OperatorCall] call to operator checked --
+# 103| 0: [LocalVariableAccess] access to local variable c
+# 107| 7: [Struct] Digit
+# 109| 6: [Field] value
+# 109| -1: [TypeMention] byte
+# 111| 7: [InstanceConstructor] Digit
+#-----| 2: (Parameters)
+# 111| 0: [Parameter] value
+# 111| -1: [TypeMention] byte
+# 112| 4: [BlockStmt] {...}
+# 113| 0: [IfStmt] if (...) ...
+# 113| 0: [LogicalOrExpr] ... || ...
+# 113| 0: [LTExpr] ... < ...
+# 113| 0: [CastExpr] (...) ...
+# 113| 1: [ParameterAccess] access to parameter value
+# 113| 1: [IntLiteral] 0
+# 113| 1: [GTExpr] ... > ...
+# 113| 0: [CastExpr] (...) ...
+# 113| 1: [ParameterAccess] access to parameter value
+# 113| 1: [IntLiteral] 9
+# 114| 1: [ThrowStmt] throw ...;
+# 114| 0: [ObjectCreation] object creation of type ArgumentException
+# 114| 0: [TypeMention] ArgumentException
+# 115| 1: [ExprStmt] ...;
+# 115| 0: [AssignExpr] ... = ...
+# 115| 0: [FieldAccess] access to field value
+# 115| -1: [ThisAccess] this access
+# 115| 1: [ParameterAccess] access to parameter value
+# 118| 8: [ImplicitConversionOperator] implicit conversion
+# 118| -1: [TypeMention] byte
+#-----| 2: (Parameters)
+# 118| 0: [Parameter] d
+# 118| -1: [TypeMention] Digit
+# 119| 4: [BlockStmt] {...}
+# 120| 0: [ReturnStmt] return ...;
+# 120| 0: [FieldAccess] access to field value
+# 120| -1: [ParameterAccess] access to parameter d
+# 123| 9: [ExplicitConversionOperator] explicit conversion
+# 123| -1: [TypeMention] Digit
+#-----| 2: (Parameters)
+# 123| 0: [Parameter] b
+# 123| -1: [TypeMention] byte
+# 124| 4: [BlockStmt] {...}
+# 125| 0: [ReturnStmt] return ...;
+# 125| 0: [ObjectCreation] object creation of type Digit
+# 125| -1: [TypeMention] Digit
+# 125| 0: [ParameterAccess] access to parameter b
+# 130| 8: [Class] TestConversionOperator
+# 133| 6: [Method] Main
+# 133| -1: [TypeMention] Void
+# 134| 4: [BlockStmt] {...}
+# 135| 0: [LocalVariableDeclStmt] ... ...;
+# 135| 0: [LocalVariableDeclAndInitExpr] Digit d = ...
+# 135| -1: [TypeMention] Digit
+# 135| 0: [LocalVariableAccess] access to local variable d
+# 135| 1: [OperatorCall] call to operator explicit conversion
+# 135| -1: [TypeMention] Digit
+# 135| 0: [CastExpr] (...) ...
+# 135| 1: [IntLiteral] 8
+# 136| 1: [LocalVariableDeclStmt] ... ...;
+# 136| 0: [LocalVariableDeclAndInitExpr] Byte b = ...
+# 136| -1: [TypeMention] byte
+# 136| 0: [LocalVariableAccess] access to local variable b
+# 136| 1: [OperatorCall] call to operator implicit conversion
+# 136| 0: [LocalVariableAccess] access to local variable d
diff --git a/csharp/ql/test/library-tests/operators/operators.cs b/csharp/ql/test/library-tests/operators/operators.cs
index 3ff2fe1a26bf..22aee92e36d0 100644
--- a/csharp/ql/test/library-tests/operators/operators.cs
+++ b/csharp/ql/test/library-tests/operators/operators.cs
@@ -48,6 +48,15 @@ public IntVector(int length) { }
public void operator >>>=(IntVector n) { }
}
+ public class C
+ {
+ // Unary instance operators.
+ public void operator checked ++() { }
+ public void operator ++() { }
+ public void operator checked --() { }
+ public void operator --() { }
+ }
+
class TestOperator
{
void Main()
@@ -79,41 +88,55 @@ void Main()
iv3 *= iv2;
iv3 /= iv2;
}
- }
- }
- public struct Digit
- {
- byte value;
+ var c = new C();
+ c++;
+ ++c;
+ c--;
+ --c;
- public Digit(byte value)
- {
- if (value < 0 || value > 9)
- throw new ArgumentException();
- this.value = value;
+ checked
+ {
+ c++;
+ ++c;
+ c--;
+ --c;
+ }
}
- public static implicit operator byte(Digit d)
+ public struct Digit
{
- return d.value;
- }
+ byte value;
- public static explicit operator Digit(byte b)
- {
- return new Digit(b);
- }
+ public Digit(byte value)
+ {
+ if (value < 0 || value > 9)
+ throw new ArgumentException();
+ this.value = value;
+ }
- }
+ public static implicit operator byte(Digit d)
+ {
+ return d.value;
+ }
- class TestConversionOperator
- {
+ public static explicit operator Digit(byte b)
+ {
+ return new Digit(b);
+ }
- void Main()
+ }
+
+ class TestConversionOperator
{
- Digit d = (Digit)8;
- byte b = d;
+
+ void Main()
+ {
+ Digit d = (Digit)8;
+ byte b = d;
+ }
+
}
}
-
}