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..8ef19df7ab5d 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 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); @@ -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/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/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..155c8d2c5511 --- /dev/null +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PrefixUnary.cs @@ -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 + { + 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/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/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 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" } } 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(