From 327757dbcba657f58d25bb969d97c3ead51d9c17 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 20 Mar 2026 12:33:57 +0100 Subject: [PATCH] C#: Update the child indices for assignments, update Assign classes to extend OperatorCall and add AssignOperation classes. --- csharp/ql/lib/semmle/code/csharp/Property.qll | 2 +- csharp/ql/lib/semmle/code/csharp/Variable.qll | 2 +- .../semmle/code/csharp/exprs/Assignment.qll | 59 ++++----- .../ql/lib/semmle/code/csharp/exprs/Call.qll | 8 +- .../lib/semmle/code/csharp/exprs/Dynamic.qll | 8 +- .../ql/lib/semmle/code/csharp/exprs/Expr.qll | 21 +-- .../semmle/code/csharp/exprs/Operation.qll | 122 ++++++++++++++++++ 7 files changed, 163 insertions(+), 59 deletions(-) create mode 100644 csharp/ql/lib/semmle/code/csharp/exprs/Operation.qll diff --git a/csharp/ql/lib/semmle/code/csharp/Property.qll b/csharp/ql/lib/semmle/code/csharp/Property.qll index 88665280d5b..bbd4fdd9d8e 100644 --- a/csharp/ql/lib/semmle/code/csharp/Property.qll +++ b/csharp/ql/lib/semmle/code/csharp/Property.qll @@ -226,7 +226,7 @@ class Property extends DeclarationWithGetSetAccessors, @property { * } * ``` */ - Expr getInitializer() { result = this.getChildExpr(1).getChildExpr(0) } + Expr getInitializer() { result = this.getChildExpr(1).getChildExpr(1) } /** * Holds if this property has an initial value. For example, the initial diff --git a/csharp/ql/lib/semmle/code/csharp/Variable.qll b/csharp/ql/lib/semmle/code/csharp/Variable.qll index 746ea6acd2f..6d59816373d 100644 --- a/csharp/ql/lib/semmle/code/csharp/Variable.qll +++ b/csharp/ql/lib/semmle/code/csharp/Variable.qll @@ -408,7 +408,7 @@ class Field extends Variable, AssignableMember, Attributable, TopLevelExprParent * } * ``` */ - final override Expr getInitializer() { result = this.getChildExpr(0).getChildExpr(0) } + final override Expr getInitializer() { result = this.getChildExpr(0).getChildExpr(1) } /** * Holds if this field has an initial value. For example, the initial diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Assignment.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Assignment.qll index 9fa2a93724d..baf366be1ba 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/Assignment.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Assignment.qll @@ -17,18 +17,14 @@ class Assignment extends BinaryOperation, @assign_expr { implies // Same as `this.(LocalVariableDeclExpr).hasInitializer()` but avoids // negative recursion - expr_parent(_, 0, this) + expr_parent(_, 1, this) } - override Expr getLeftOperand() { result = this.getChild(1) } - - override Expr getRightOperand() { result = this.getChild(0) } - /** Gets the left operand of this assignment. */ - Expr getLValue() { result = this.getChild(1) } + Expr getLValue() { result = this.getLeftOperand() } /** Gets the right operand of this assignment. */ - Expr getRValue() { result = this.getChild(0) } + Expr getRValue() { result = this.getRightOperand() } /** Gets the variable being assigned to, if any. */ Variable getTargetVariable() { result.getAnAccess() = this.getLValue() } @@ -64,37 +60,33 @@ class AssignExpr extends Assignment, @simple_assign_expr { /** * An assignment operation. Either an arithmetic assignment operation - * (`AssignArithmeticOperation`), a bitwise assignment operation - * (`AssignBitwiseOperation`), or an event assignment (`AddOrRemoveEventExpr`). + * (`AssignArithmeticOperation`), a bitwise assignment operation or + * (`AssignBitwiseOperation`), an event assignment (`AddOrRemoveEventExpr`), or + * a null-coalescing assignment (`AssignCoalesceExpr`). */ class AssignOperation extends Assignment, @assign_op_expr { override string getOperator() { none() } /** - * Gets the expanded version of this assignment operation, if any. - * - * For example, if this assignment operation is `x += y` then - * the expanded assignment is `x = x + y`. - * - * If an expanded version exists, then it is used in the control - * flow graph. + * Expanded versions of compound assignments are no longer extracted. */ - AssignExpr getExpandedAssignment() { expr_parent(result, 2, this) } + deprecated AssignExpr getExpandedAssignment() { none() } /** - * Holds if this assignment operation has an expanded version. - * - * For example, if this assignment operation is `x += y` then - * it has the expanded version `x = x + y`. - * - * If an expanded version exists, then it is used in the control - * flow graph. + * Expanded versions of compound assignments are no longer extracted. */ - predicate hasExpandedAssignment() { exists(this.getExpandedAssignment()) } + deprecated predicate hasExpandedAssignment() { none() } override string toString() { result = "... " + this.getOperator() + " ..." } } +/** + * An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`. + */ +class AssignCallOperation extends AssignOperation, OperatorCall, @assign_op_call_expr { + override string toString() { result = "... " + this.getOperator() + " ..." } +} + /** * An arithmetic assignment operation. Either an addition assignment operation * (`AssignAddExpr`), a subtraction assignment operation (`AssignSubExpr`), a @@ -102,7 +94,7 @@ class AssignOperation extends Assignment, @assign_op_expr { * operation (`AssignDivExpr`), or a remainder assignment operation * (`AssignRemExpr`). */ -class AssignArithmeticOperation extends AssignOperation, @assign_arith_expr { } +class AssignArithmeticOperation extends AssignCallOperation, @assign_arith_expr { } /** * An addition assignment operation, for example `x += y`. @@ -158,7 +150,7 @@ class AssignRemExpr extends AssignArithmeticOperation, @assign_rem_expr { * operation (`AssignRightShiftExpr`), or an unsigned right-shift assignment * operation (`AssignUnsignedRightShiftExpr`). */ -class AssignBitwiseOperation extends AssignOperation, @assign_bitwise_expr { } +class AssignBitwiseOperation extends AssignCallOperation, @assign_bitwise_expr { } /** * A bitwise-and assignment operation, for example `x &= y`. @@ -208,12 +200,17 @@ class AssignRightShiftExpr extends AssignBitwiseOperation, @assign_rshift_expr { /** * An unsigned right-shift assignment operation, for example `x >>>= y`. */ -class AssignUnsighedRightShiftExpr extends AssignBitwiseOperation, @assign_urshift_expr { +class AssignUnsignedRightShiftExpr extends AssignBitwiseOperation, @assign_urshift_expr { override string getOperator() { result = ">>>=" } - override string getAPrimaryQlClass() { result = "AssignUnsighedRightShiftExpr" } + override string getAPrimaryQlClass() { result = "AssignUnsignedRightShiftExpr" } } +/** + * DEPRECATED: Use `AssignUnsignedRightShiftExpr` instead. + */ +deprecated class AssignUnsighedRightShiftExpr = AssignUnsignedRightShiftExpr; + /** * An event assignment. Either an event addition (`AddEventExpr`) or an event * removal (`RemoveEventExpr`). @@ -222,9 +219,9 @@ class AddOrRemoveEventExpr extends AssignOperation, @assign_event_expr { /** Gets the event targeted by this event assignment. */ Event getTarget() { result = this.getLValue().getTarget() } - override EventAccess getLValue() { result = this.getChild(1) } + override EventAccess getLValue() { result = this.getChild(0) } - override Expr getRValue() { result = this.getChild(0) } + override EventAccess getLeftOperand() { result = this.getChild(0) } } /** diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll index f8b51a990ed..912cb23e06b 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll @@ -493,12 +493,16 @@ class ConstructorInitializer extends Call, @constructor_init_expr { * } * ``` */ -class OperatorCall extends Call, LateBindableExpr, @operator_invocation_expr { +class OperatorCall extends Call, LateBindableExpr, @op_invoke_expr { override Operator getTarget() { expr_call(this, result) } override Operator getARuntimeTarget() { result = Call.super.getARuntimeTarget() } - override string toString() { result = "call to operator " + this.getTarget().getName() } + override string toString() { + if this instanceof DynamicOperatorCall + then result = "dynamic call to operator " + this.getLateBoundTargetName() + else result = "call to operator " + this.getTarget().getName() + } override string getAPrimaryQlClass() { result = "OperatorCall" } } diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Dynamic.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Dynamic.qll index 04ea9f062a5..bfc5c36ff37 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/Dynamic.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Dynamic.qll @@ -96,13 +96,7 @@ class DynamicMethodCall extends DynamicExpr, MethodCall { * Unlike an ordinary call to a user-defined operator (`OperatorCall`), the * target operator may not be known at compile-time (as in the example above). */ -class DynamicOperatorCall extends DynamicExpr, OperatorCall { - override string toString() { - result = "dynamic call to operator " + this.getLateBoundTargetName() - } - - override string getAPrimaryQlClass() { result = "DynamicOperatorCall" } -} +class DynamicOperatorCall extends DynamicExpr, OperatorCall { } /** * A call to a user-defined mutator operator where the operand is a `dynamic` diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll index c3b9bcc363b..66764d06479 100644 --- a/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll @@ -14,6 +14,7 @@ import Creation import Dynamic import Literal import LogicalOperation +import Operation import semmle.code.csharp.controlflow.ControlFlowElement import semmle.code.csharp.Location import semmle.code.csharp.Stmt @@ -65,25 +66,11 @@ class Expr extends ControlFlowElement, @expr { /** Gets the enclosing callable of this expression, if any. */ override Callable getEnclosingCallable() { enclosingCallable(this, result) } - pragma[nomagic] - private predicate isExpandedAssignmentRValueDescendant() { - this = - any(AssignOperation op).getExpandedAssignment().getRValue().getChildExpr(0).getAChildExpr() - or - exists(Expr parent | - parent.isExpandedAssignmentRValueDescendant() and - this = parent.getAChildExpr() - ) - } - /** * Holds if this expression is generated by the compiler and does not appear * explicitly in the source code. */ - final predicate isImplicit() { - compiler_generated(this) or - this.isExpandedAssignmentRValueDescendant() - } + final predicate isImplicit() { compiler_generated(this) } /** * Gets an expression that is the result of stripping (recursively) all @@ -168,7 +155,7 @@ class LocalVariableDeclExpr extends Expr, @local_var_decl_expr { string getName() { result = this.getVariable().getName() } /** Gets the initializer expression of this local variable declaration, if any. */ - Expr getInitializer() { result = this.getChild(0) } + Expr getInitializer() { result = this.getChild(1) } /** Holds if this local variable declaration has an initializer. */ predicate hasInitializer() { exists(this.getInitializer()) } @@ -188,7 +175,7 @@ class LocalVariableDeclExpr extends Expr, @local_var_decl_expr { /** Gets the variable access used in this declaration, if any. */ LocalVariableAccess getAccess() { - result = this.getChild(1) or + result = this.getChild(0) or result = this // `out` argument } diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Operation.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Operation.qll new file mode 100644 index 00000000000..3310fe4de1a --- /dev/null +++ b/csharp/ql/lib/semmle/code/csharp/exprs/Operation.qll @@ -0,0 +1,122 @@ +/** + * Provides classes for operations that also have compound assignment forms. + */ + +import Expr + +/** A binary operation that involves a null-coalescing operation. */ +abstract private class NullCoalescingOperationImpl extends BinaryOperation { } + +final class NullCoalescingOperation = NullCoalescingOperationImpl; + +private class AddNullCoalescingExpr extends NullCoalescingOperationImpl instanceof NullCoalescingExpr +{ } + +private class AddAssignCoalesceExpr extends NullCoalescingOperationImpl instanceof AssignCoalesceExpr +{ } + +/** A binary operations that involves an addition operation. */ +abstract private class AddOperationImpl extends BinaryOperation { } + +final class AddOperation = AddOperationImpl; + +private class AddAddExpr extends AddOperationImpl instanceof AddExpr { } + +private class AddAssignExpr extends AddOperationImpl instanceof AssignAddExpr { } + +/** A binary operation that involves a subtraction operation. */ +abstract private class SubOperationImpl extends BinaryOperation { } + +final class SubOperation = SubOperationImpl; + +private class AddSubExpr extends SubOperationImpl instanceof SubExpr { } + +private class AddSubAssignExpr extends SubOperationImpl instanceof AssignSubExpr { } + +/** A binary operation that involves a multiplication operation. */ +abstract private class MulOperationImpl extends BinaryOperation { } + +final class MulOperation = MulOperationImpl; + +private class AddMulExpr extends MulOperationImpl instanceof MulExpr { } + +private class AddMulAssignExpr extends MulOperationImpl instanceof AssignMulExpr { } + +/** A binary operation that involves a division operation. */ +abstract private class DivOperationImpl extends BinaryOperation { + /** Gets the denominator of this division operation. */ + Expr getDenominator() { result = this.getRightOperand() } +} + +final class DivOperation = DivOperationImpl; + +private class AddDivExpr extends DivOperationImpl instanceof DivExpr { } + +private class AddDivAssignExpr extends DivOperationImpl instanceof AssignDivExpr { } + +/** A binary operation that involves a remainder operation. */ +abstract private class RemOperationImpl extends BinaryOperation { } + +final class RemOperation = RemOperationImpl; + +private class AddRemExpr extends RemOperationImpl instanceof RemExpr { } + +private class AddRemAssignExpr extends RemOperationImpl instanceof AssignRemExpr { } + +/** A binary operation that involves a bitwise AND operation. */ +abstract private class BitwiseAndOperationImpl extends BinaryOperation { } + +final class BitwiseAndOperation = BitwiseAndOperationImpl; + +private class AddBitwiseAndExpr extends BitwiseAndOperationImpl instanceof BitwiseAndExpr { } + +private class AddAssignBitwiseAndExpr extends BitwiseAndOperationImpl instanceof AssignAndExpr { } + +/** A binary operation that involves a bitwise OR operation. */ +abstract private class BitwiseOrOperationImpl extends BinaryOperation { } + +final class BitwiseOrOperation = BitwiseOrOperationImpl; + +private class AddBitwiseOrExpr extends BitwiseOrOperationImpl instanceof BitwiseOrExpr { } + +private class AddAssignBitwiseOrExpr extends BitwiseOrOperationImpl instanceof AssignOrExpr { } + +/** A binary operation that involves a bitwise XOR operation. */ +abstract private class BitwiseXorOperationImpl extends BinaryOperation { } + +final class BitwiseXorOperation = BitwiseXorOperationImpl; + +private class AddBitwiseXorExpr extends BitwiseXorOperationImpl instanceof BitwiseXorExpr { } + +private class AddAssignBitwiseXorExpr extends BitwiseXorOperationImpl instanceof AssignXorExpr { } + +/** A binary operation that involves a left shift operation. */ +abstract private class LeftShiftOperationImpl extends BinaryOperation { } + +final class LeftShiftOperation = LeftShiftOperationImpl; + +private class AddLeftShiftExpr extends LeftShiftOperationImpl instanceof LeftShiftExpr { } + +private class AddAssignLeftShiftExpr extends LeftShiftOperationImpl instanceof AssignLeftShiftExpr { +} + +/** A binary operation that involves a right shift operation. */ +abstract private class RightShiftOperationImpl extends BinaryOperation { } + +final class RightShiftOperation = RightShiftOperationImpl; + +private class AddRightShiftExpr extends RightShiftOperationImpl instanceof RightShiftExpr { } + +private class AddAssignRightShiftExpr extends RightShiftOperationImpl instanceof AssignRightShiftExpr +{ } + +/** A binary operation that involves a unsigned right shift operation. */ +abstract private class UnsignedRightShiftOperationImpl extends BinaryOperation { } + +final class UnsignedRightShiftOperation = UnsignedRightShiftOperationImpl; + +private class AddUnsignedRightShiftExpr extends UnsignedRightShiftOperationImpl instanceof UnsignedRightShiftExpr +{ } + +private class AddAssignUnsignedRightShiftExpr extends UnsignedRightShiftOperationImpl instanceof AssignUnsignedRightShiftExpr +{ }